tauri_plugin_licensemanager/
models.rs1use std::time::UNIX_EPOCH;
4
5use serde::{Deserialize, Serialize};
6
7use crate::core::{
8 LatestAllowedVersion, LicenseError, LicenseErrorCode, LicenseManager, LicenseState,
9 OfflineState,
10};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(tag = "mode", rename_all = "lowercase")]
17pub enum OfflineStateDto {
18 Online,
20 Offline {
22 #[serde(rename = "validUntil")]
24 valid_until: Option<u64>,
25 #[serde(rename = "remainingMs")]
27 remaining_ms: u64,
28 },
29}
30
31impl From<OfflineState> for OfflineStateDto {
32 fn from(state: OfflineState) -> Self {
33 match state {
34 OfflineState::Online => Self::Online,
35 OfflineState::Offline {
36 valid_until,
37 remaining,
38 } => Self::Offline {
39 valid_until: valid_until.map(|t| {
40 t.duration_since(UNIX_EPOCH)
41 .map_or(0, |d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
42 }),
43 remaining_ms: u64::try_from(remaining.as_millis()).unwrap_or(u64::MAX),
44 },
45 }
46 }
47}
48
49fn latest_to_dto(latest: &LatestAllowedVersion) -> Option<Option<String>> {
51 latest.as_option().map(|v| v.map(str::to_owned))
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct Snapshot {
59 pub state: LicenseState,
61 pub offline: OfflineStateDto,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub latest_allowed_version: Option<Option<String>>,
67}
68
69impl Snapshot {
70 pub fn of(manager: &LicenseManager) -> Self {
72 Self {
73 state: manager.state(),
74 offline: manager.offline_state().into(),
75 latest_allowed_version: latest_to_dto(&manager.latest_allowed_version()),
76 }
77 }
78}
79
80#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub(crate) struct LatestAllowedVersionDto {
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub latest_allowed_version: Option<Option<String>>,
87}
88
89impl From<&LatestAllowedVersion> for LatestAllowedVersionDto {
90 fn from(latest: &LatestAllowedVersion) -> Self {
91 Self {
92 latest_allowed_version: latest_to_dto(latest),
93 }
94 }
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct LicenseErrorDto {
101 pub code: LicenseErrorCode,
103 pub message: String,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub status: Option<u16>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub latest_allowed_version: Option<Option<String>>,
111}
112
113impl From<LicenseError> for LicenseErrorDto {
114 fn from(error: LicenseError) -> Self {
115 Self {
116 code: error.code,
117 latest_allowed_version: latest_to_dto(&error.latest_allowed_version),
118 message: error.message,
119 status: error.status,
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use std::time::Duration;
127
128 use serde_json::json;
129
130 use super::*;
131
132 #[test]
133 fn serializes_offline_states() {
134 assert_eq!(
135 serde_json::to_value(OfflineStateDto::from(OfflineState::Online)).unwrap(),
136 json!({ "mode": "online" })
137 );
138 assert_eq!(
139 serde_json::to_value(OfflineStateDto::from(OfflineState::Offline {
140 valid_until: Some(UNIX_EPOCH + Duration::from_millis(1_767_225_600_000)),
141 remaining: Duration::from_millis(1234),
142 }))
143 .unwrap(),
144 json!({ "mode": "offline", "validUntil": 1_767_225_600_000u64, "remainingMs": 1234 })
145 );
146 assert_eq!(
147 serde_json::to_value(OfflineStateDto::from(OfflineState::Offline {
148 valid_until: None,
149 remaining: Duration::ZERO,
150 }))
151 .unwrap(),
152 json!({ "mode": "offline", "validUntil": null, "remainingMs": 0 })
153 );
154 }
155
156 #[test]
157 fn serializes_latest_allowed_version_absent_null_or_string() {
158 let dto = |latest: LatestAllowedVersion| {
159 serde_json::to_value(LatestAllowedVersionDto::from(&latest)).unwrap()
160 };
161 assert_eq!(dto(LatestAllowedVersion::NotApplicable), json!({}));
162 assert_eq!(
163 dto(LatestAllowedVersion::NoneAllowed),
164 json!({ "latestAllowedVersion": null })
165 );
166 assert_eq!(
167 dto(LatestAllowedVersion::Version("2.3.1".into())),
168 json!({ "latestAllowedVersion": "2.3.1" })
169 );
170
171 let snapshot = Snapshot {
172 state: LicenseState::VersionNotAllowed,
173 offline: OfflineStateDto::Online,
174 latest_allowed_version: Some(None),
175 };
176 assert_eq!(
177 serde_json::to_value(&snapshot).unwrap(),
178 json!({ "state": "VERSION_NOT_ALLOWED", "offline": { "mode": "online" }, "latestAllowedVersion": null })
179 );
180 let snapshot = Snapshot {
181 latest_allowed_version: None,
182 state: LicenseState::Active,
183 ..snapshot
184 };
185 assert_eq!(
186 serde_json::to_value(&snapshot).unwrap(),
187 json!({ "state": "ACTIVE", "offline": { "mode": "online" } })
188 );
189 }
190
191 #[test]
192 fn serializes_errors() {
193 let error = LicenseError {
194 code: LicenseErrorCode::VersionNotAllowed,
195 message: "nope".into(),
196 status: Some(403),
197 latest_allowed_version: LatestAllowedVersion::Version("2.3.1".into()),
198 };
199 assert_eq!(
200 serde_json::to_value(LicenseErrorDto::from(error)).unwrap(),
201 json!({ "code": "version_not_allowed", "message": "nope", "status": 403, "latestAllowedVersion": "2.3.1" })
202 );
203 let error = LicenseError::new(LicenseErrorCode::NetworkError, "offline");
204 assert_eq!(
205 serde_json::to_value(LicenseErrorDto::from(error)).unwrap(),
206 json!({ "code": "network_error", "message": "offline" })
207 );
208 }
209}