1#[cfg(test)]
10mod tests;
11
12use crate::protocol::Cohort;
13use serde::Deserialize;
14use serde_json::{Map, Value};
15
16#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
22pub struct Response {
23 #[serde(rename = "protocol")]
28 pub protocol_version: String,
29
30 pub server: Option<String>,
32
33 pub daystart: Option<DayStart>,
35
36 #[serde(rename = "app")]
40 pub apps: Vec<App>,
41}
42
43#[derive(Clone, Debug, Deserialize, PartialEq)]
44pub struct DayStart {
45 pub elapsed_days: Option<u32>,
48 pub elapsed_seconds: Option<u32>,
51}
52
53#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
54pub struct App {
55 #[serde(rename = "appid")]
56 pub id: String,
57
58 pub status: OmahaStatus,
60
61 #[serde(flatten)]
66 pub cohort: Cohort,
67
68 pub ping: Option<Ping>,
70
71 #[serde(rename = "updatecheck")]
73 pub update_check: Option<UpdateCheck>,
74
75 #[serde(rename = "event")]
77 pub events: Option<Vec<Event>>,
78
79 #[serde(flatten)]
81 pub extra_attributes: Map<String, Value>,
82}
83
84impl App {
85 pub fn get_manifest_version(&self) -> Option<String> {
86 self.update_check.as_ref().and_then(|update_check| {
87 update_check
88 .manifest
89 .as_ref()
90 .map(|manifest| manifest.version.clone())
91 })
92 }
93}
94
95#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
96#[serde(field_identifier, rename_all = "lowercase")]
97pub enum OmahaStatus {
98 #[default]
99 Ok,
100 Restricted,
103 NoUpdate,
105 Error(String),
106}
107
108#[derive(Clone, Debug, Deserialize, PartialEq)]
109pub struct Ping {
110 status: OmahaStatus,
112}
113
114#[derive(Clone, Debug, Deserialize, PartialEq)]
115pub struct Event {
116 pub status: OmahaStatus,
118}
119
120#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
121pub struct UpdateCheck {
122 pub status: OmahaStatus,
124 pub info: Option<String>,
126
127 pub urls: Option<URLs>,
129
130 pub manifest: Option<Manifest>,
132
133 #[serde(flatten)]
135 pub extra_attributes: Map<String, Value>,
136}
137
138impl UpdateCheck {
139 pub fn ok(urls: impl IntoIterator<Item = impl Into<String>>) -> Self {
140 UpdateCheck {
141 urls: Some(URLs::new(urls.into_iter().map(Into::into).collect())),
142 ..UpdateCheck::default()
143 }
144 }
145
146 pub fn no_update() -> Self {
147 UpdateCheck {
148 status: OmahaStatus::NoUpdate,
149 ..UpdateCheck::default()
150 }
151 }
152
153 pub fn get_all_url_codebases(&self) -> impl Iterator<Item = &str> {
155 self.urls
156 .iter()
157 .flat_map(|urls| &urls.url)
158 .map(|url| url.codebase.as_str())
159 }
160
161 pub fn get_all_packages(&self) -> impl Iterator<Item = &Package> {
163 self.manifest.iter().flat_map(|m| &m.packages.package)
164 }
165
166 pub fn get_all_full_urls(&self) -> impl Iterator<Item = String> + '_ {
168 self.get_all_url_codebases().flat_map(move |codebase| {
169 self.get_all_packages()
170 .map(move |package| format!("{}{}", codebase, package.name))
171 })
172 }
173}
174
175#[derive(Clone, Debug, Deserialize, PartialEq)]
177pub struct URLs {
178 pub url: Vec<URL>,
179}
180
181impl URLs {
182 pub fn new(urls: Vec<String>) -> Self {
183 URLs {
184 url: urls.into_iter().map(|url| URL { codebase: url }).collect(),
185 }
186 }
187}
188
189#[derive(Clone, Debug, Deserialize, PartialEq)]
190pub struct URL {
191 pub codebase: String,
193}
194
195#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
196pub struct Manifest {
197 pub version: String,
198
199 pub actions: Actions,
200 pub packages: Packages,
201}
202
203#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
205pub struct Actions {
206 pub action: Vec<Action>,
207}
208
209#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
210pub struct Action {
211 pub event: Option<String>,
213
214 pub run: Option<String>,
216
217 #[serde(flatten)]
218 pub extra_attributes: Map<String, Value>,
219}
220
221#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
223pub struct Packages {
224 pub package: Vec<Package>,
225}
226
227impl Packages {
228 pub fn new(package: Vec<Package>) -> Self {
229 Self { package }
230 }
231}
232
233#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
234pub struct Package {
235 pub name: String,
237 pub required: bool,
238 pub size: Option<u64>,
239 pub hash: Option<String>,
241 pub hash_sha256: Option<String>,
243
244 #[serde(rename = "fp")]
246 pub fingerprint: String,
247
248 #[serde(flatten)]
249 pub extra_attributes: Map<String, Value>,
250}
251
252impl Package {
253 pub fn with_name(name: impl Into<String>) -> Self {
254 Self {
255 name: name.into(),
256 ..Self::default()
257 }
258 }
259}
260
261pub fn parse_json_response(json: &[u8]) -> serde_json::Result<Response> {
263 #[derive(Deserialize)]
264 struct ResponseWrapper {
265 response: Response,
266 }
267
268 let wrapper: ResponseWrapper = parse_safe_json(json)?;
269 Ok(wrapper.response)
270}
271
272fn parse_safe_json<'a, T>(raw: &'a [u8]) -> serde_json::Result<T>
279where
280 T: Deserialize<'a>,
281{
282 let safety_prefix = b")]}'\n";
283 if raw.starts_with(safety_prefix) {
286 serde_json::from_slice(&raw[safety_prefix.len()..])
287 } else {
288 serde_json::from_slice(raw)
289 }
290}