use serde::{Deserialize, Deserializer, Serialize};
pub const SCHEMA_VERSION: u32 = 1;
pub type Extra = serde_json::Map<String, serde_json::Value>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Manifest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema_version: Option<u32>,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub app_id: Option<String>,
pub version: String,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub tag: Option<String>,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub channel: Option<String>,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub published_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires: Option<String>,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub release_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes_url: Option<String>,
#[serde(default)]
pub assets: Vec<ManifestAsset>,
#[serde(flatten)]
pub extra: Extra,
}
impl Manifest {
pub fn new(version: impl Into<String>) -> Self {
Self {
schema_version: Some(SCHEMA_VERSION),
app_id: None,
version: version.into(),
tag: None,
channel: None,
published_at: None,
expires: None,
release_url: None,
notes: None,
notes_url: None,
assets: Vec::new(),
extra: Extra::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ManifestAsset {
pub name: String,
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub os: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ed25519_signature: Option<String>,
#[serde(
default,
deserialize_with = "lenient",
skip_serializing_if = "Option::is_none"
)]
pub minimum_os_version: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deltas: Vec<ManifestDelta>,
#[serde(flatten)]
pub extra: Extra,
}
impl ManifestAsset {
pub fn new(name: impl Into<String>, url: impl Into<String>) -> Self {
Self {
name: name.into(),
url: url.into(),
os: None,
arch: None,
format: None,
content_type: None,
size: None,
sha256: None,
signature: None,
signature_url: None,
ed25519_signature: None,
minimum_os_version: None,
deltas: Vec::new(),
extra: Extra::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ManifestDelta {
pub from: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ed25519_signature: Option<String>,
#[serde(flatten)]
pub extra: Extra,
}
impl ManifestDelta {
pub fn new(from: impl Into<String>, url: impl Into<String>) -> Self {
Self {
from: from.into(),
to: None,
url: url.into(),
name: None,
format: None,
size: None,
sha256: None,
signature: None,
signature_url: None,
ed25519_signature: None,
extra: Extra::new(),
}
}
}
fn lenient<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<String>, D::Error> {
Ok(match serde_json::Value::deserialize(deserializer)? {
serde_json::Value::String(s) => Some(s),
_ => None,
})
}