#[cfg(test)]
mod tests;
use crate::protocol::Cohort;
use serde::Deserialize;
use serde_json::{Map, Value};
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Response {
#[serde(rename = "protocol")]
pub protocol_version: String,
pub server: Option<String>,
pub daystart: Option<DayStart>,
#[serde(rename = "app")]
pub apps: Vec<App>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct DayStart {
pub elapsed_days: Option<u32>,
pub elapsed_seconds: Option<u32>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct App {
#[serde(rename = "appid")]
pub id: String,
pub status: OmahaStatus,
#[serde(flatten)]
pub cohort: Cohort,
pub ping: Option<Ping>,
#[serde(rename = "updatecheck")]
pub update_check: Option<UpdateCheck>,
#[serde(rename = "event")]
pub events: Option<Vec<Event>>,
#[serde(flatten)]
pub extra_attributes: Map<String, Value>,
}
impl App {
pub fn get_manifest_version(&self) -> Option<String> {
self.update_check.as_ref().and_then(|update_check| {
update_check
.manifest
.as_ref()
.map(|manifest| manifest.version.clone())
})
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
#[serde(field_identifier, rename_all = "lowercase")]
pub enum OmahaStatus {
#[default]
Ok,
Restricted,
NoUpdate,
Error(String),
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct Ping {
status: OmahaStatus,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct Event {
pub status: OmahaStatus,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct UpdateCheck {
pub status: OmahaStatus,
pub info: Option<String>,
pub urls: Option<URLs>,
pub manifest: Option<Manifest>,
#[serde(flatten)]
pub extra_attributes: Map<String, Value>,
}
impl UpdateCheck {
pub fn ok(urls: impl IntoIterator<Item = impl Into<String>>) -> Self {
UpdateCheck {
urls: Some(URLs::new(urls.into_iter().map(Into::into).collect())),
..UpdateCheck::default()
}
}
pub fn no_update() -> Self {
UpdateCheck {
status: OmahaStatus::NoUpdate,
..UpdateCheck::default()
}
}
pub fn get_all_url_codebases(&self) -> impl Iterator<Item = &str> {
self.urls
.iter()
.flat_map(|urls| &urls.url)
.map(|url| url.codebase.as_str())
}
pub fn get_all_packages(&self) -> impl Iterator<Item = &Package> {
self.manifest.iter().flat_map(|m| &m.packages.package)
}
pub fn get_all_full_urls(&self) -> impl Iterator<Item = String> + '_ {
self.get_all_url_codebases().flat_map(move |codebase| {
self.get_all_packages()
.map(move |package| format!("{}{}", codebase, package.name))
})
}
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct URLs {
pub url: Vec<URL>,
}
impl URLs {
pub fn new(urls: Vec<String>) -> Self {
URLs {
url: urls.into_iter().map(|url| URL { codebase: url }).collect(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct URL {
pub codebase: String,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Manifest {
pub version: String,
pub actions: Actions,
pub packages: Packages,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Actions {
pub action: Vec<Action>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Action {
pub event: Option<String>,
pub run: Option<String>,
#[serde(flatten)]
pub extra_attributes: Map<String, Value>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Packages {
pub package: Vec<Package>,
}
impl Packages {
pub fn new(package: Vec<Package>) -> Self {
Self { package }
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Package {
pub name: String,
pub required: bool,
pub size: Option<u64>,
pub hash: Option<String>,
pub hash_sha256: Option<String>,
#[serde(rename = "fp")]
pub fingerprint: String,
#[serde(flatten)]
pub extra_attributes: Map<String, Value>,
}
impl Package {
pub fn with_name(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Self::default()
}
}
}
pub fn parse_json_response(json: &[u8]) -> serde_json::Result<Response> {
#[derive(Deserialize)]
struct ResponseWrapper {
response: Response,
}
let wrapper: ResponseWrapper = parse_safe_json(json)?;
Ok(wrapper.response)
}
fn parse_safe_json<'a, T>(raw: &'a [u8]) -> serde_json::Result<T>
where
T: Deserialize<'a>,
{
let safety_prefix = b")]}'\n";
if raw.starts_with(safety_prefix) {
serde_json::from_slice(&raw[safety_prefix.len()..])
} else {
serde_json::from_slice(raw)
}
}