smbcloud_ascapi_aso/
app.rs1use async_trait::async_trait;
10use reqwest::Method;
11use serde::{Deserialize, Serialize};
12use smbcloud_ascapi_core::jsonapi::{Document, Resource, UpdateBody, UpdateData};
13use smbcloud_ascapi_core::Client;
14use smbcloud_ascapi_core::Result;
15
16pub const RESOURCE_TYPE: &str = "apps";
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct AppAttributes {
21 pub name: Option<String>,
22 pub bundle_id: Option<String>,
23 pub sku: Option<String>,
24 pub primary_locale: Option<String>,
25 pub is_or_ever_was_made_for_kids: Option<bool>,
26 pub content_rights_declaration: Option<String>,
27}
28
29pub type App = Resource<AppAttributes>;
30
31#[derive(Debug, Clone, Default, Serialize)]
32#[serde(rename_all = "camelCase")]
33pub struct AppUpdateAttributes {
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub primary_locale: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub content_rights_declaration: Option<String>,
38}
39
40#[async_trait]
47pub trait AppsApi {
48 async fn list_apps(&self, filter_bundle_id: Option<&str>) -> Result<Vec<App>>;
57
58 async fn get_app(&self, app_id: &str) -> Result<App>;
59
60 async fn update_app(&self, app_id: &str, attributes: AppUpdateAttributes) -> Result<App>;
61}
62
63#[async_trait]
64impl AppsApi for Client {
65 async fn list_apps(&self, filter_bundle_id: Option<&str>) -> Result<Vec<App>> {
66 let mut query = Vec::new();
67 if let Some(bundle_id) = filter_bundle_id {
68 query.push(("filter[bundleId]", bundle_id));
69 }
70 let apps = self.list_all::<AppAttributes>("/v1/apps", &query).await?;
71 Ok(match filter_bundle_id {
72 Some(bundle_id) => exact_bundle_id(apps, bundle_id),
73 None => apps,
74 })
75 }
76
77 async fn get_app(&self, app_id: &str) -> Result<App> {
78 let path = format!("/v1/apps/{app_id}");
79 let doc: Document<AppAttributes> =
80 self.request(Method::GET, &path, &[], None::<&()>).await?;
81 Ok(doc.data)
82 }
83
84 async fn update_app(&self, app_id: &str, attributes: AppUpdateAttributes) -> Result<App> {
85 let path = format!("/v1/apps/{app_id}");
86 let body = UpdateBody {
87 data: UpdateData {
88 resource_type: RESOURCE_TYPE,
89 id: app_id.to_string(),
90 attributes,
91 },
92 };
93 let doc: Document<AppAttributes> =
94 self.request(Method::PATCH, &path, &[], Some(&body)).await?;
95 Ok(doc.data)
96 }
97}
98
99fn exact_bundle_id(apps: Vec<App>, bundle_id: &str) -> Vec<App> {
101 apps.into_iter()
102 .filter(|app| app.attributes.bundle_id.as_deref() == Some(bundle_id))
103 .collect()
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use smbcloud_ascapi_core::jsonapi::ListDocument;
110
111 #[test]
114 fn a_bundle_id_filter_keeps_only_the_exact_match() {
115 let doc: ListDocument<AppAttributes> = serde_json::from_value(serde_json::json!({
116 "data": [
117 { "id": "6787445875", "type": "apps",
118 "attributes": { "bundleId": "ai.splitfire.KaroKoweJam", "name": "KaroKowe Jam" } },
119 { "id": "6756801861", "type": "apps",
120 "attributes": { "bundleId": "ai.splitfire.KaroKowe", "name": "Karaoke KaroKowe" } }
121 ]
122 }))
123 .unwrap();
124 let apps = exact_bundle_id(doc.data, "ai.splitfire.KaroKowe");
125 assert_eq!(apps.len(), 1);
126 assert_eq!(apps[0].id, "6756801861");
127 }
128}