smbcloud_ascapi_aso/
bundle_id.rs1use async_trait::async_trait;
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4use smbcloud_ascapi_core::jsonapi::{CreateBody, CreateData, Document, Resource};
5use smbcloud_ascapi_core::Client;
6use smbcloud_ascapi_core::Result;
7
8pub const RESOURCE_TYPE: &str = "bundleIds";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum BundleIdPlatform {
19 #[serde(rename = "IOS")]
20 Ios,
21 #[serde(rename = "MAC_OS")]
22 MacOs,
23 #[serde(rename = "UNIVERSAL")]
24 Universal,
25 #[serde(rename = "SERVICES")]
34 Services,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct BundleIdAttributes {
40 pub name: Option<String>,
41 pub identifier: Option<String>,
42 pub platform: Option<BundleIdPlatform>,
43 pub seed_id: Option<String>,
44}
45
46pub type BundleId = Resource<BundleIdAttributes>;
47
48#[derive(Debug, Clone, Serialize)]
49#[serde(rename_all = "camelCase")]
50pub struct BundleIdCreateAttributes {
51 pub identifier: String,
52 pub name: String,
53 pub platform: BundleIdPlatform,
54}
55
56#[async_trait]
63pub trait BundleIdsApi {
64 async fn list_bundle_ids(&self, filter_identifier: Option<&str>) -> Result<Vec<BundleId>>;
75
76 async fn create_bundle_id(&self, attributes: BundleIdCreateAttributes) -> Result<BundleId>;
79}
80
81#[async_trait]
82impl BundleIdsApi for Client {
83 async fn list_bundle_ids(&self, filter_identifier: Option<&str>) -> Result<Vec<BundleId>> {
84 let mut query = Vec::new();
85 if let Some(identifier) = filter_identifier {
86 query.push(("filter[identifier]", identifier));
87 }
88 query.push(("limit", "200"));
92
93 let bundle_ids = self
94 .list_all::<BundleIdAttributes>("/v1/bundleIds", &query)
95 .await?;
96 Ok(match filter_identifier {
97 Some(identifier) => exact_identifier(bundle_ids, identifier),
98 None => bundle_ids,
99 })
100 }
101
102 async fn create_bundle_id(&self, attributes: BundleIdCreateAttributes) -> Result<BundleId> {
103 let body = CreateBody {
104 data: CreateData {
105 resource_type: RESOURCE_TYPE,
106 attributes,
107 relationships: None::<()>,
108 },
109 };
110 let doc: Document<BundleIdAttributes> = self
111 .request(Method::POST, "/v1/bundleIds", &[], Some(&body))
112 .await?;
113 Ok(doc.data)
114 }
115}
116
117fn exact_identifier(bundle_ids: Vec<BundleId>, identifier: &str) -> Vec<BundleId> {
119 bundle_ids
120 .into_iter()
121 .filter(|bundle_id| bundle_id.attributes.identifier.as_deref() == Some(identifier))
122 .collect()
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use smbcloud_ascapi_core::jsonapi::ListDocument;
129
130 #[test]
134 fn an_identifier_filter_keeps_only_the_exact_match() {
135 let doc: ListDocument<BundleIdAttributes> = serde_json::from_value(serde_json::json!({
136 "data": [
137 { "id": "A", "type": "bundleIds", "attributes": { "identifier": "ai.splitfire.SplitfireAIAuth", "platform": "SERVICES" } },
138 { "id": "B", "type": "bundleIds", "attributes": { "identifier": "ai.splitfire.SplitfireAI", "platform": "UNIVERSAL" } },
139 { "id": "C", "type": "bundleIds", "attributes": { "identifier": "ai.splitfire.SplitfireAI.topshelf", "platform": "UNIVERSAL" } },
140 { "id": "D", "type": "bundleIds", "attributes": { "identifier": "ai.splitfire.SplitFireAIWatch", "platform": "UNIVERSAL" } }
141 ]
142 }))
143 .unwrap();
144 let exact = exact_identifier(doc.data.clone(), "ai.splitfire.SplitfireAI");
145 assert_eq!(
146 exact.iter().map(|b| b.id.as_str()).collect::<Vec<_>>(),
147 ["B"]
148 );
149 assert!(exact_identifier(doc.data, "ai.splitfire.Splitfire").is_empty());
150 }
151
152 #[test]
153 fn a_services_identifier_does_not_break_the_list() {
154 let body = serde_json::json!({
159 "data": [
160 {
161 "id": "AAA",
162 "type": "bundleIds",
163 "attributes": {
164 "name": "An app",
165 "identifier": "xyz.example.app",
166 "platform": "UNIVERSAL",
167 "seedId": "TEAMID"
168 }
169 },
170 {
171 "id": "BBB",
172 "type": "bundleIds",
173 "attributes": {
174 "name": "Sign in with Apple",
175 "identifier": "xyz.example.service",
176 "platform": "SERVICES",
177 "seedId": "TEAMID"
178 }
179 }
180 ]
181 });
182
183 let doc: ListDocument<BundleIdAttributes> =
184 serde_json::from_value(body).expect("a Services identifier must not fail the list");
185 assert_eq!(doc.data.len(), 2);
186 assert_eq!(
187 doc.data[1].attributes.platform,
188 Some(BundleIdPlatform::Services)
189 );
190 }
191}