main
rs 117 lines 3.79 KB
Raw
1 use serde::{Deserialize, Serialize};
2
3 /// Release notes for one language on a track release.
4 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5 pub struct LocalizedText {
6 pub language: String,
7 pub text: String,
8 }
9
10 /// One release on a track. Fields this crate does not edit are carried
11 /// through untouched so a `tracks.update` does not drop them.
12 #[derive(Debug, Clone, Serialize, Deserialize)]
13 #[serde(rename_all = "camelCase")]
14 pub struct TrackRelease {
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub name: Option<String>,
17 #[serde(default)]
18 pub version_codes: Vec<String>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub status: Option<String>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub user_fraction: Option<f64>,
23 #[serde(default)]
24 pub release_notes: Vec<LocalizedText>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub country_targeting: Option<serde_json::Value>,
27 }
28
29 #[derive(Debug, Clone, Serialize, Deserialize)]
30 pub struct Track {
31 pub track: String,
32 #[serde(default)]
33 pub releases: Vec<TrackRelease>,
34 }
35
36 impl Track {
37 /// Set `locale`'s release notes on the release carrying `version_code`,
38 /// replacing any existing note for that language. Returns an error if no
39 /// release carries the code, or the text is over Play's limit.
40 pub fn set_release_notes(
41 &mut self,
42 version_code: &str,
43 locale: &str,
44 text: &str,
45 ) -> std::result::Result<(), String> {
46 let len = text.chars().count();
47 if len > crate::listing::RELEASE_NOTES_MAX_LEN {
48 return Err(format!(
49 "release notes are {len} characters; Google Play accepts at most {}",
50 crate::listing::RELEASE_NOTES_MAX_LEN
51 ));
52 }
53 let release = self
54 .releases
55 .iter_mut()
56 .find(|r| r.version_codes.iter().any(|c| c == version_code))
57 .ok_or_else(|| {
58 format!(
59 "no release on track {} carries version code {version_code}",
60 self.track
61 )
62 })?;
63 release.release_notes.retain(|n| n.language != locale);
64 release.release_notes.push(LocalizedText {
65 language: locale.to_string(),
66 text: text.to_string(),
67 });
68 Ok(())
69 }
70 }
71
72 #[cfg(test)]
73 mod tests {
74 use super::*;
75
76 fn track() -> Track {
77 serde_json::from_str(
78 r#"{"track":"internal","releases":[{"name":"1.2.0","versionCodes":["120"],
79 "status":"draft","releaseNotes":[{"language":"en-US","text":"old"},
80 {"language":"id","text":"lama"}]}]}"#,
81 )
82 .unwrap()
83 }
84
85 #[test]
86 fn notes_replace_only_the_given_language() {
87 let mut t = track();
88 t.set_release_notes("120", "en-US", "new").unwrap();
89 let notes = &t.releases[0].release_notes;
90 assert_eq!(notes.len(), 2);
91 assert!(notes.iter().any(|n| n.language == "id" && n.text == "lama"));
92 assert!(notes
93 .iter()
94 .any(|n| n.language == "en-US" && n.text == "new"));
95 }
96
97 #[test]
98 fn an_unknown_version_code_is_an_error() {
99 assert!(track().set_release_notes("999", "en-US", "x").is_err());
100 }
101
102 #[test]
103 fn notes_over_five_hundred_characters_are_rejected() {
104 assert!(track()
105 .set_release_notes("120", "en-US", &"x".repeat(501))
106 .is_err());
107 }
108
109 #[test]
110 fn untouched_release_fields_survive_a_round_trip() {
111 let mut t = track();
112 t.set_release_notes("120", "id", "baru").unwrap();
113 let value = serde_json::to_value(&t).unwrap();
114 assert_eq!(value["releases"][0]["status"], "draft");
115 assert_eq!(value["releases"][0]["name"], "1.2.0");
116 }
117 }