1use std::future::Future;
2use std::pin::Pin;
3
4pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ProviderErrorCode {
10 InvalidRequest,
11 NotFound,
12 Network,
13 Timeout,
14 Server,
15 PermissionDenied,
16 Internal,
17}
18
19impl ProviderErrorCode {
20 pub const fn biz_code(self) -> u32 {
21 match self {
22 Self::InvalidRequest => 1002,
23 Self::NotFound => 1003,
24 Self::Network => 5001,
25 Self::Timeout => 5002,
26 Self::Server => 5003,
27 Self::PermissionDenied => 3000,
28 Self::Internal => 1005,
29 }
30 }
31
32 pub const fn as_str(self) -> &'static str {
33 match self {
34 Self::InvalidRequest => "invalid_request",
35 Self::NotFound => "not_found",
36 Self::Network => "network",
37 Self::Timeout => "timeout",
38 Self::Server => "server",
39 Self::PermissionDenied => "permission_denied",
40 Self::Internal => "internal",
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
46pub struct ProviderError {
47 code: ProviderErrorCode,
48 detail: String,
49}
50
51impl ProviderError {
52 pub fn new(code: ProviderErrorCode, detail: impl Into<String>) -> Self {
53 Self {
54 code,
55 detail: detail.into(),
56 }
57 }
58
59 pub fn invalid_request(detail: impl Into<String>) -> Self {
60 Self::new(ProviderErrorCode::InvalidRequest, detail)
61 }
62
63 pub fn not_found(detail: impl Into<String>) -> Self {
64 Self::new(ProviderErrorCode::NotFound, detail)
65 }
66
67 pub fn network(detail: impl Into<String>) -> Self {
68 Self::new(ProviderErrorCode::Network, detail)
69 }
70
71 pub fn timeout(detail: impl Into<String>) -> Self {
72 Self::new(ProviderErrorCode::Timeout, detail)
73 }
74
75 pub fn server(detail: impl Into<String>) -> Self {
76 Self::new(ProviderErrorCode::Server, detail)
77 }
78
79 pub fn permission_denied(detail: impl Into<String>) -> Self {
80 Self::new(ProviderErrorCode::PermissionDenied, detail)
81 }
82
83 pub fn internal(detail: impl Into<String>) -> Self {
84 Self::new(ProviderErrorCode::Internal, detail)
85 }
86
87 pub const fn code(&self) -> ProviderErrorCode {
88 self.code
89 }
90
91 pub const fn biz_code(&self) -> u32 {
92 self.code.biz_code()
93 }
94
95 pub fn detail(&self) -> &str {
96 &self.detail
97 }
98}
99
100impl std::fmt::Display for ProviderError {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 write!(f, "[{}] {}", self.code.as_str(), self.detail)
103 }
104}
105
106impl std::error::Error for ProviderError {}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum FingerprintError {
111 DeviceIdUnavailable,
113}
114
115impl std::fmt::Display for FingerprintError {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 Self::DeviceIdUnavailable => write!(f, "device_id_unavailable"),
119 }
120 }
121}
122
123impl std::error::Error for FingerprintError {}
124
125pub trait FingerprintProvider: Send + Sync + 'static {
127 fn get_fingerprint(&self) -> Result<String, FingerprintError> {
129 Err(FingerprintError::DeviceIdUnavailable)
130 }
131}
132
133pub trait PushNotificationProvider: Send + Sync + 'static {
135 fn bind_push_token<'a>(&'a self, _token: String) -> BoxFuture<'a, Result<(), ProviderError>> {
137 Box::pin(async { Ok(()) })
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
143pub enum LxAppStatus {
144 #[default]
147 Unknown,
148 Published,
149 Maintain,
153 Delisted,
155 Suspended,
157}
158
159impl LxAppStatus {
160 pub const fn as_str(self) -> &'static str {
161 match self {
162 Self::Unknown => "unknown",
163 Self::Published => "published",
164 Self::Maintain => "maintain",
165 Self::Delisted => "delisted",
166 Self::Suspended => "suspended",
167 }
168 }
169
170 pub fn from_str_lossy(value: &str) -> Self {
177 match value.trim().to_ascii_lowercase().as_str() {
178 "published" => Self::Published,
179 "maintain" => Self::Maintain,
180 "delisted" => Self::Delisted,
181 "suspended" => Self::Suspended,
182 _ => Self::Unknown,
183 }
184 }
185
186 pub const fn blocks_open(self) -> bool {
193 matches!(self, Self::Suspended | Self::Maintain)
194 }
195}
196
197impl std::fmt::Display for LxAppStatus {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 f.write_str(self.as_str())
200 }
201}
202
203#[derive(Debug, Clone, Default)]
212pub struct LxAppRegistryInfo {
213 pub name: Option<String>,
215 pub description: Option<String>,
216 pub icon_url: Option<String>,
221 pub status: LxAppStatus,
222 pub permissions: Option<LxAppPermissions>,
226}
227
228#[derive(Debug, Clone, Default, PartialEq, Eq)]
235#[non_exhaustive]
236pub struct LxAppPermissions {
237 pub network: Option<LxAppNetworkPermission>,
238 pub privileges: Option<LxAppPrivilegePermission>,
239}
240
241impl LxAppPermissions {
242 pub fn all() -> Self {
244 Self::default()
245 }
246
247 pub fn network(trusted_domains: impl IntoIterator<Item = impl Into<String>>) -> Self {
249 Self {
250 network: Some(LxAppNetworkPermission::new(trusted_domains)),
251 privileges: None,
252 }
253 }
254
255 pub fn privileges(granted: impl IntoIterator<Item = impl Into<String>>) -> Self {
257 Self {
258 network: None,
259 privileges: Some(LxAppPrivilegePermission::new(granted)),
260 }
261 }
262
263 pub fn with_network(
264 mut self,
265 trusted_domains: impl IntoIterator<Item = impl Into<String>>,
266 ) -> Self {
267 self.network = Some(LxAppNetworkPermission::new(trusted_domains));
268 self
269 }
270
271 pub fn with_privileges(mut self, granted: impl IntoIterator<Item = impl Into<String>>) -> Self {
272 self.privileges = Some(LxAppPrivilegePermission::new(granted));
273 self
274 }
275}
276
277#[derive(Debug, Clone, Default, PartialEq, Eq)]
281#[non_exhaustive]
282pub struct LxAppNetworkPermission {
283 pub trusted_domains: Vec<String>,
284}
285
286impl LxAppNetworkPermission {
287 pub fn new(trusted_domains: impl IntoIterator<Item = impl Into<String>>) -> Self {
288 Self {
289 trusted_domains: trusted_domains.into_iter().map(Into::into).collect(),
290 }
291 }
292}
293
294#[derive(Debug, Clone, Default, PartialEq, Eq)]
297#[non_exhaustive]
298pub struct LxAppPrivilegePermission {
299 pub granted: Vec<String>,
300}
301
302impl LxAppPrivilegePermission {
303 pub fn new(granted: impl IntoIterator<Item = impl Into<String>>) -> Self {
304 Self {
305 granted: granted.into_iter().map(Into::into).collect(),
306 }
307 }
308}
309
310pub trait LxAppRegistryProvider: Send + Sync + 'static {
320 fn fetch_registry_info<'a>(
334 &'a self,
335 _app: LxAppRegistryRequest<'a>,
336 ) -> BoxFuture<'a, Result<Option<LxAppRegistryInfo>, ProviderError>> {
337 Box::pin(async { Ok(None) })
338 }
339}
340
341#[derive(Debug, Clone, Copy)]
344#[non_exhaustive]
345pub struct LxAppRegistryRequest<'a> {
346 pub appid: &'a str,
347 pub channel: LxAppChannel,
350}
351
352impl<'a> LxAppRegistryRequest<'a> {
353 pub fn new(appid: &'a str, channel: LxAppChannel) -> Self {
354 Self { appid, channel }
355 }
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
364pub enum LxAppChannel {
365 #[default]
366 Release,
367 Preview,
368 Draft,
369}
370
371impl LxAppChannel {
372 pub const fn as_str(self) -> &'static str {
373 match self {
374 Self::Release => "release",
375 Self::Preview => "preview",
376 Self::Draft => "draft",
377 }
378 }
379}
380
381impl std::fmt::Display for LxAppChannel {
382 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 f.write_str(self.as_str())
384 }
385}
386
387#[cfg(test)]
388mod registry_tests {
389 use super::LxAppStatus;
390
391 #[test]
392 fn only_the_states_that_mean_do_not_open_block() {
393 assert!(LxAppStatus::Suspended.blocks_open());
396 assert!(LxAppStatus::Maintain.blocks_open());
397 assert!(!LxAppStatus::Delisted.blocks_open());
399 assert!(!LxAppStatus::Published.blocks_open());
400 assert!(!LxAppStatus::Unknown.blocks_open());
402 assert_eq!(
403 LxAppStatus::from_str_lossy("maintain"),
404 LxAppStatus::Maintain
405 );
406 }
407
408 #[test]
409 fn status_parsing_is_case_insensitive_because_unknown_never_blocks() {
410 assert_eq!(
411 LxAppStatus::from_str_lossy("suspended"),
412 LxAppStatus::Suspended
413 );
414 assert_eq!(
415 LxAppStatus::from_str_lossy("Suspended"),
416 LxAppStatus::Suspended
417 );
418 assert_eq!(
419 LxAppStatus::from_str_lossy(" SUSPENDED "),
420 LxAppStatus::Suspended
421 );
422 assert!(LxAppStatus::from_str_lossy("SUSPENDED").blocks_open());
423
424 assert_eq!(
425 LxAppStatus::from_str_lossy("Delisted"),
426 LxAppStatus::Delisted
427 );
428 assert_eq!(LxAppStatus::from_str_lossy(""), LxAppStatus::Unknown);
429 assert_eq!(LxAppStatus::from_str_lossy("retired"), LxAppStatus::Unknown);
430 }
431}