Skip to main content

release_hub/
release.rs

1//! Neutral release and update models shared across release sources.
2
3use http::HeaderMap;
4use semver::Version;
5use serde::{Deserialize, Deserializer, Serialize, de::Error as DeError};
6use std::{collections::HashMap, ffi::OsString, path::PathBuf, time::Duration};
7use time::OffsetDateTime;
8use url::Url;
9
10use crate::InstallerKind;
11
12/// Target-specific release payload returned by a manifest.
13#[derive(Debug, Deserialize, Serialize, Clone)]
14pub struct ReleaseManifestPlatform {
15    /// Download URL for the artifact.
16    pub url: Url,
17    /// Detached minisign signature for the artifact.
18    pub signature: String,
19}
20
21/// Release payload shape supported by the updater manifests.
22#[derive(Debug, Deserialize, Serialize, Clone)]
23#[serde(untagged)]
24pub enum RemoteReleaseInner {
25    /// Single-target payload where one artifact implicitly applies to the
26    /// active target.
27    Dynamic(ReleaseManifestPlatform),
28    /// Multi-target payload keyed by canonical target string.
29    Static {
30        /// Mapping from target string to downloadable artifact metadata.
31        platforms: HashMap<String, ReleaseManifestPlatform>,
32    },
33}
34
35/// Neutral release model shared by all configured release sources.
36#[derive(Debug, Serialize, Clone)]
37pub struct RemoteRelease {
38    /// Remote version advertised by the source.
39    pub version: Version,
40    /// Optional release notes or body text.
41    pub notes: Option<String>,
42    /// Optional publication timestamp.
43    pub pub_date: Option<OffsetDateTime>,
44    /// Target-specific artifact metadata.
45    #[serde(flatten)]
46    pub data: RemoteReleaseInner,
47    /// Additional headers required when downloading the selected artifact.
48    #[serde(skip)]
49    pub download_headers: HeaderMap,
50}
51
52impl<'de> Deserialize<'de> for RemoteRelease {
53    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54    where
55        D: Deserializer<'de>,
56    {
57        #[derive(Deserialize)]
58        struct InnerRemoteRelease {
59            #[serde(alias = "name")]
60            version: Version,
61            notes: Option<String>,
62            pub_date: Option<String>,
63            platforms: Option<HashMap<String, ReleaseManifestPlatform>>,
64            url: Option<Url>,
65            signature: Option<String>,
66        }
67
68        let release = InnerRemoteRelease::deserialize(deserializer)?;
69        let pub_date = match release.pub_date {
70            Some(date) => Some(
71                OffsetDateTime::parse(&date, &time::format_description::well_known::Rfc3339)
72                    .map_err(|error| {
73                        DeError::custom(format!("invalid value for `pub_date`: {error}"))
74                    })?,
75            ),
76            None => None,
77        };
78
79        let data = match release.platforms {
80            Some(platforms) => RemoteReleaseInner::Static { platforms },
81            None => RemoteReleaseInner::Dynamic(ReleaseManifestPlatform {
82                url: release.url.ok_or_else(|| {
83                    DeError::custom("the `url` field was not set on the updater response")
84                })?,
85                signature: release.signature.ok_or_else(|| {
86                    DeError::custom("the `signature` field was not set on the updater response")
87                })?,
88            }),
89        };
90
91        Ok(Self {
92            version: release.version,
93            notes: release.notes,
94            pub_date,
95            data,
96            download_headers: HeaderMap::new(),
97        })
98    }
99}
100
101impl RemoteRelease {
102    /// Returns the download URL for the requested target.
103    ///
104    /// Dynamic releases always return the single embedded artifact URL, while
105    /// static releases look up the target in their `platforms` map.
106    pub fn download_url(&self, target: &str) -> crate::Result<&Url> {
107        match &self.data {
108            RemoteReleaseInner::Dynamic(platform) => Ok(&platform.url),
109            RemoteReleaseInner::Static { platforms } => platforms
110                .get(target)
111                .map(|platform| &platform.url)
112                .ok_or_else(|| crate::Error::TargetNotFound(target.into())),
113        }
114    }
115
116    /// Returns the detached signature for the requested target.
117    pub fn signature(&self, target: &str) -> crate::Result<&String> {
118        match &self.data {
119            RemoteReleaseInner::Dynamic(platform) => Ok(&platform.signature),
120            RemoteReleaseInner::Static { platforms } => platforms
121                .get(target)
122                .map(|platform| &platform.signature)
123                .ok_or_else(|| crate::Error::TargetNotFound(target.into())),
124        }
125    }
126}
127
128/// Ready-to-download update candidate produced by [`crate::Updater::check`].
129///
130/// This is the fully resolved, target-specific update payload after source
131/// selection, manifest decoding, and installer-kind detection.
132#[derive(Debug, Clone)]
133pub struct Update {
134    /// Current application version.
135    pub current_version: Version,
136    /// Target release version.
137    pub version: Version,
138    /// Optional release publication date.
139    pub date: Option<OffsetDateTime>,
140    /// Optional release body or notes.
141    pub body: Option<String>,
142    /// Raw serialized release payload for advanced consumers.
143    pub raw_json: serde_json::Value,
144    /// Concrete artifact download URL.
145    pub download_url: Url,
146    /// Detached minisign signature for the selected artifact.
147    pub signature: String,
148    /// Minisign public key used for verification.
149    pub pubkey: String,
150    /// Selected target string.
151    pub target: String,
152    /// Installer format chosen for the selected artifact.
153    pub installer_kind: InstallerKind,
154    /// HTTP headers propagated from the updater builder.
155    pub headers: HeaderMap,
156    /// Optional download timeout.
157    pub timeout: Option<Duration>,
158    /// Optional proxy configuration.
159    pub proxy: Option<Url>,
160    /// Whether proxy configuration should be ignored.
161    pub no_proxy: bool,
162    /// Whether invalid TLS certificates should be accepted.
163    pub dangerous_accept_invalid_certs: bool,
164    /// Whether invalid TLS hostnames should be accepted.
165    pub dangerous_accept_invalid_hostnames: bool,
166    /// Final installation target path.
167    pub extract_path: PathBuf,
168    /// Application name used by platform backends.
169    pub app_name: String,
170    /// Windows installer arguments propagated from configuration and builder overrides.
171    pub installer_args: Vec<OsString>,
172}