Skip to main content

swarmhive_api_types/
update.rs

1//! Tauri v2 updater「dynamic update server」兼容响应 DTO。
2//!
3//! 有更新时 `GET /api/v1/updates/tauri/:app_slug` 返回 flat shape
4//! `{version, pub_date?, url, signature, notes?, swarmhive}`;无更新返回 `204`。
5//! 顶层 `url` + `signature` 是 Tauri dynamic 模式专用形态(非 static 文件的
6//! platforms map)。
7
8use serde::{Deserialize, Serialize};
9use utoipa::ToSchema;
10
11/// 升级强制度。`force` = `min_version > current_version`,客户端必须升级。
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
13#[serde(rename_all = "lowercase")]
14pub enum UpgradeType {
15    Prompt,
16    Force,
17}
18
19/// SwarmHive 私有扩展命名空间——不属于 Tauri 官方契约。updater 用 serde 忽略
20/// 未知字段,故放独立命名空间既不破坏兼容、又避免与未来 Tauri 标准字段撞名。
21#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
22pub struct TauriUpdateExtensions {
23    pub upgrade_type: UpgradeType,
24    /// 强制更新下限(semver);None = 无下限。
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub min_version: Option<String>,
27    /// 该 release 的灰度放量百分比(1-100,已 unwrap_or(100))。
28    pub rollout_percent: i16,
29    /// 命中的 channel 名(显式 query 或 app 默认)。
30    pub channel: String,
31}
32
33/// Tauri updater dynamic endpoint 的 200 响应体(flat shape)。
34///
35/// `version` / `url` / `signature` 必填;`pub_date`(RFC 3339)/ `notes` 可选。
36/// `signature` 是 minisign `.sig` 文件的**完整原文**(多行字符串),不是 url / 路径。
37#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
38pub struct TauriUpdateResponse {
39    pub version: String,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub pub_date: Option<String>,
42    pub url: String,
43    pub signature: String,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub notes: Option<String>,
46    pub swarmhive: TauriUpdateExtensions,
47}
48
49/// RN Android 更新检查 `GET /api/v1/updates/android/:app_slug` 的响应体(扁平)。
50///
51/// 与 Tauri 的 204-absence 不同:RN 统一 200,用 `has_update` boolean 区分。
52/// `has_update:false` 时其余字段全部省略(尤其 `download_url`,避免 SDK 误下载)。
53/// `signature` 不在此——RN 用 `sha256` 做传输完整性预校验,APK 真伪由 Android
54/// 安装器在安装时验 v2/v3 签名兜底(不加 minisign)。
55#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
56pub struct AndroidUpdateResponse {
57    pub has_update: bool,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub version_name: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub version_code: Option<i64>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub upgrade_type: Option<UpgradeType>,
64    /// 强更下限(整数 versionCode);None = 无下限(upgrade_type=prompt)。
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub min_version_code: Option<i64>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub download_url: Option<String>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub release_notes: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub size_bytes: Option<i64>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub sha256: Option<String>,
75}
76
77impl AndroidUpdateResponse {
78    /// 无更新:只序列化 `{"has_update": false}`。
79    pub fn no_update() -> Self {
80        Self {
81            has_update: false,
82            version_name: None,
83            version_code: None,
84            upgrade_type: None,
85            min_version_code: None,
86            download_url: None,
87            release_notes: None,
88            size_bytes: None,
89            sha256: None,
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn upgrade_type_wire_is_lowercase() {
100        assert_eq!(
101            serde_json::to_string(&UpgradeType::Prompt).unwrap(),
102            "\"prompt\""
103        );
104        assert_eq!(
105            serde_json::to_string(&UpgradeType::Force).unwrap(),
106            "\"force\""
107        );
108        assert_eq!(
109            serde_json::from_str::<UpgradeType>("\"force\"").unwrap(),
110            UpgradeType::Force
111        );
112    }
113
114    #[test]
115    fn android_no_update_serializes_minimally() {
116        // 无更新只出 has_update,绝不带 download_url(避免 SDK 误下载)。
117        let v = serde_json::to_value(AndroidUpdateResponse::no_update()).unwrap();
118        assert_eq!(v, serde_json::json!({ "has_update": false }));
119    }
120}