feat: define manifest models as structs

This commit is contained in:
Erica Marigold 2025-05-02 18:03:51 +01:00
parent f1cc4be4b1
commit 106fee3d46
Signed by: DevComp
SSH key fingerprint: SHA256:jD3oMT4WL3WHPJQbrjC3l5feNCnkv7ndW8nYaHX5wFw
8 changed files with 1347 additions and 47 deletions

1169
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -39,3 +39,11 @@ libc = "0.2.172"
strum = "0.27.1"
strum_macros = "0.27.1"
lazy_static = "1.5.0"
reqwest = "0.12.15"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
semver = { version = "1.0.26", features = ["serde"] }
uuid = { version = "1.16.0", features = ["serde"] }
url = { version = "2.5.4", features = ["serde"] }
langtag = { version = "0.4.0", features = ["serde"] }
serde_with = "3.12.0"

View file

@ -1,9 +1,11 @@
use std::env::consts;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};
#[derive(Debug, Clone, Copy, EnumString, Display)]
#[derive(Debug, Clone, Copy, EnumString, Display, Deserialize, Serialize)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum Arch {
X86,
X64,
@ -17,4 +19,4 @@ impl Default for Arch {
_ => unreachable!(),
}
}
}
}

View file

@ -8,6 +8,7 @@ mod app;
pub(crate) mod mode;
mod errors;
mod logging;
mod manifest;
pub const APP_NAME: &str = concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"));

56
src/manifest/channel.rs Executable file
View file

@ -0,0 +1,56 @@
use langtag::LangTag;
use serde::Deserialize;
use serde_with::serde_as;
use url::Url;
use crate::{ManifestInfo, app::arch::Arch};
use super::{ChannelItemType, Payload};
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Manifest<'manifest> {
pub manifest_version: String,
pub info: ChannelManifestInfo,
#[serde(borrow)]
pub channel_items: Vec<ChannelItem<'manifest>>,
}
ManifestInfo! {
#[derive(Debug, Clone)]
pub struct ChannelManifestInfo {
pub commit_id: String,
pub community_or_lower_flight_id: String,
pub professional_or_greater_flight_id: String,
pub q_build_session_id: String,
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde_as]
pub struct ChannelItem<'lang> {
pub id: String,
pub version: String,
pub r#type: ChannelItemType,
pub payloads: Option<Vec<Payload>>,
#[serde(borrow)]
pub localized_resoures: Option<Vec<LocalizedResource<'lang>>>,
#[serde_as(as = "FromStr")]
pub chip: Arch,
#[serde_as(as = "FromStr")]
pub product_arch: Arch,
pub release_notes: Url,
pub supports_download_then_update: bool,
// TODO: requirements
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalizedResource<'lang> {
#[serde(borrow)]
pub language: &'lang LangTag,
pub title: String,
pub description: String,
pub license: Url,
}

48
src/manifest/installer/mod.rs Executable file
View file

@ -0,0 +1,48 @@
use serde::Deserialize;
use signer::Signer;
use crate::ManifestInfo;
use super::Payload;
pub mod signer;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Manifest {
pub manifest_version: String,
pub engine_version: String,
pub info: InstallerManifestInfo,
pub signers: Vec<Signer>,
pub packages: Vec<Package>,
}
ManifestInfo! {
#[derive(Debug, Clone)]
pub struct InstallerManifestInfo {}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Package {
id: String,
version: String,
r#type: PackageType,
payloads: Vec<Payload>,
install_sizes: InstallSizes,
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub enum PackageType {
Component,
Msi,
Exe,
Vsix,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstallSizes {
pub target_drive: u64,
}

View file

@ -0,0 +1,46 @@
use serde::Deserialize;
use serde_with::serde_as;
#[derive(Debug, Clone, Deserialize)]
#[serde_as]
pub struct Signer {
#[serde_as(as = "FromStr")]
#[serde(rename = "$id")]
pub id: usize,
#[serde(rename = "camelCase")]
pub subject_name: String, // TODO: Use a RFC2253 parser
}
#[derive(Debug, Clone, Copy)]
pub struct SignerRef(usize);
impl SignerRef {
pub fn new(id: usize) -> Self {
Self(id)
}
pub fn downcast(self, signers: Vec<Signer>) -> Signer {
signers[self.0].clone()
}
}
impl<'de> Deserialize<'de> for SignerRef {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct RefWrapper {
#[serde(rename = "$ref")]
reference: String,
}
let wrapper = RefWrapper::deserialize(deserializer)?;
let id = wrapper
.reference
.parse::<usize>()
.map_err(serde::de::Error::custom)?;
Ok(SignerRef(id))
}
}

60
src/manifest/mod.rs Executable file
View file

@ -0,0 +1,60 @@
#![allow(dead_code)]
use installer::signer::SignerRef;
use serde::Deserialize;
use url::Url;
pub mod channel;
pub mod installer;
#[macro_export]
macro_rules! ManifestInfo {
(#[derive($($derive:meta),*)] $pub:vis struct $name:ident { $($fpub:vis $field:ident : $type:ty,)* }) => {
#[derive(serde::Deserialize, $($derive),*)]
#[serde(rename_all = "camelCase")]
#[serde_with::serde_as]
$pub struct $name {
pub id: String,
pub build_branch: String,
pub build_version: String,
pub local_build: String,
pub manifest_name: String,
pub manifest_type: crate::manifest::ManifestType,
pub product_display_version: String,
pub product_line: String,
pub product_line_version: String,
pub product_milestone: String,
#[serde_as(as = "FromStr")]
pub product_milestone_is_prerelease: bool,
pub product_name: String,
#[serde_as(as = "FromStr")]
pub product_patch_version: u32,
pub product_semantic_version: semver::Version,
$($fpub $field : $type,)*
}
}
}
#[derive(Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum ManifestType {
Channel,
Installer,
}
#[derive(Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "PascalCase")]
pub enum ChannelItemType {
Manifest,
ChannelProduct,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Payload {
pub file_name: String,
pub sha256: String,
pub size: usize,
pub url: Url,
pub signer: Option<Vec<SignerRef>>,
}