Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
a74924e
Branches
Tags
main
apple-crates
xcode
src
v1
bundle_id_capabilities.rs
Refactor and unify a lot of things for another project and implement airdrop support
Dadoum
commited
a74924e
at 2026-10-25 22:15:58
bundle_id_capabilities.rs
Blame
History
Raw
use super::{Page, PagedDocumentLinks, Relationship, ResourceIdentifier, null_as_default}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum CapabilityType { #[serde(rename = "ICLOUD")] ICloud, #[serde(rename = "IN_APP_PURCHASE")] InAppPurchase, #[serde(rename = "GAME_CENTER")] GameCenter, #[serde(rename = "PUSH_NOTIFICATIONS")] PushNotifications, #[serde(rename = "WALLET")] Wallet, #[serde(rename = "INTER_APP_AUDIO")] InterAppAudio, #[serde(rename = "MAPS")] Maps, #[serde(rename = "ASSOCIATED_DOMAINS")] AssociatedDomains, #[serde(rename = "PERSONAL_VPN")] PersonalVPN, #[serde(rename = "APP_GROUPS")] AppGroups, #[serde(rename = "HEALTHKIT")] HealthKit, #[serde(rename = "HOMEKIT")] HomeKit, #[serde(rename = "WIRELESS_ACCESSORY_CONFIGURATION")] WirelessAccessoryConfiguration, #[serde(rename = "APPLE_PAY")] ApplePay, #[serde(rename = "DATA_PROTECTION")] DataProtection, #[serde(rename = "SIRIKIT")] SiriKit, #[serde(rename = "NETWORK_EXTENSIONS")] NetworkExtensions, #[serde(rename = "MULTIPATH")] Multipath, #[serde(rename = "HOT_SPOT")] HotSpot, #[serde(rename = "NFC_TAG_READING")] NFCTagReading, #[serde(rename = "CLASSKIT")] ClassKit, #[serde(rename = "AUTOFILL_CREDENTIAL_PROVIDER")] AutoFillCredentialProvider, #[serde(rename = "ACCESS_WIFI_INFORMATION")] AccessWiFiInformation, #[serde(rename = "NETWORK_CUSTOM_PROTOCOL")] NetworkCustomProtocol, #[serde(rename = "COREMEDIA_HLS_LOW_LATENCY")] CoreMediaHLSLowLatency, #[serde(rename = "SYSTEM_EXTENSION_INSTALL")] SystemExtensionInstall, #[serde(rename = "USER_MANAGEMENT")] UserManagement, #[serde(rename = "APPLE_ID_AUTH")] AppleIDAuth, /// Apple accepts more than it documents, such as `WIFI_AWARE`. #[serde(untagged)] Other(String), } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum CapabilitySettingKey { #[serde(rename = "ICLOUD_VERSION")] ICloudVersion, #[serde(rename = "DATA_PROTECTION_PERMISSION_LEVEL")] DataProtectionPermissionLevel, #[serde(rename = "APPLE_ID_AUTH_APP_CONSENT")] AppleIDAuthAppConsent, #[serde(untagged)] Other(String), } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum CapabilityOptionKey { #[serde(rename = "XCODE_5")] Xcode5, #[serde(rename = "XCODE_6")] Xcode6, #[serde(rename = "COMPLETE_PROTECTION")] CompleteProtection, #[serde(rename = "PROTECTED_UNLESS_OPEN")] ProtectedUnlessOpen, #[serde(rename = "PROTECTED_UNTIL_FIRST_USER_AUTH")] ProtectedUntilFirstUserAuth, #[serde(rename = "PRIMARY_APP_CONSENT")] PrimaryAppConsent, #[serde(untagged)] Other(String), } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum AllowedInstances { #[serde(rename = "ENTRY")] Entry, #[serde(rename = "SINGLE")] Single, #[serde(rename = "MULTIPLE")] Multiple, #[serde(untagged)] Other(String), } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CapabilityOption { #[serde(skip_serializing_if = "Option::is_none")] pub key: Option<CapabilityOptionKey>, #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub enabled_by_default: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub enabled: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub supports_wildcard: Option<bool>, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CapabilitySetting { #[serde(skip_serializing_if = "Option::is_none")] pub key: Option<CapabilitySettingKey>, #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub enabled_by_default: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub visible: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub allowed_instances: Option<AllowedInstances>, #[serde(skip_serializing_if = "Option::is_none")] pub min_instances: Option<u32>, #[serde( default, deserialize_with = "null_as_default", skip_serializing_if = "Vec::is_empty" )] pub options: Vec<CapabilityOption>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BundleIDCapabilityAttributes { pub capability_type: Option<CapabilityType>, #[serde(default, deserialize_with = "null_as_default")] pub settings: Vec<CapabilitySetting>, } #[derive(Debug, Deserialize)] pub struct BundleIDCapability { pub id: String, pub attributes: BundleIDCapabilityAttributes, #[serde(default)] pub relationships: BundleIDCapabilityRelationships, } #[derive(Debug, Default, Deserialize)] pub struct BundleIDCapabilityRelationships { #[serde(default)] pub capability: Relationship<Option<ResourceIdentifier<CapabilityType>>>, } impl BundleIDCapability { /// Some responses identify the capability through its relationship or resource ID. pub fn capability_type(&self, bundle_id_id: &str) -> Option<CapabilityType> { self.attributes .capability_type .clone() .or_else(|| { self.relationships .capability .data .as_ref() .map(|capability| capability.id.clone()) }) .or_else(|| { let suffix = self.id.strip_prefix(bundle_id_id)?.strip_prefix('_')?; if suffix.is_empty() { return None; } serde_json::from_value(serde_json::Value::String(suffix.to_string())).ok() }) } } #[derive(Debug, Deserialize)] pub struct BundleIDCapabilitiesResponse { pub data: Vec<BundleIDCapability>, pub links: PagedDocumentLinks, } #[derive(Debug, Deserialize)] pub struct BundleIDCapabilityResponse { pub data: BundleIDCapability, } #[derive(Debug, Default, Serialize)] pub struct ListBundleIDCapabilitiesRequest { #[serde(skip)] pub bundle_id_id: String, #[serde(flatten)] pub page: Page, } impl_developer_api_request!( ListBundleIDCapabilitiesRequest, GET, |request| format!("bundleIds/{}/bundleIdCapabilities", request.bundle_id_id), BundleIDCapabilitiesResponse ); #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BundleIDCapabilityCreateAttributes { pub capability_type: CapabilityType, pub settings: Vec<CapabilitySetting>, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BundleIDCapabilityCreateRelationships { pub bundle_id: Relationship<ResourceIdentifier>, /// Only Xcode's API wants this, and it repeats the capability type. pub capability: Relationship<ResourceIdentifier<CapabilityType>>, } #[derive(Debug, Serialize)] #[serde(tag = "type", rename = "bundleIdCapabilities")] pub struct CreateBundleIDCapabilityRequest { pub attributes: BundleIDCapabilityCreateAttributes, pub relationships: BundleIDCapabilityCreateRelationships, } impl_developer_api_request!( CreateBundleIDCapabilityRequest, POST, "bundleIdCapabilities", BundleIDCapabilityResponse ); #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BundleIDCapabilityUpdateAttributes { #[serde(skip_serializing_if = "Option::is_none")] pub capability_type: Option<CapabilityType>, #[serde(skip_serializing_if = "Option::is_none")] pub settings: Option<Vec<CapabilitySetting>>, } #[derive(Debug, Serialize)] #[serde(tag = "type", rename = "bundleIdCapabilities")] pub struct UpdateBundleIDCapabilityRequest { pub id: String, pub attributes: BundleIDCapabilityUpdateAttributes, } impl_developer_api_request!( UpdateBundleIDCapabilityRequest, PATCH, |request| format!("bundleIdCapabilities/{}", request.id), BundleIDCapabilityResponse ); #[derive(Debug, Serialize)] pub struct DeleteBundleIDCapabilityRequest { #[serde(skip)] pub bundle_id_capability_id: String, } impl_developer_api_request!(DeleteBundleIDCapabilityRequest, DELETE, |request| format!( "bundleIdCapabilities/{}", request.bundle_id_capability_id )); #[cfg(test)] mod tests { use super::*; use crate::v1::{DeveloperAPIRequest, ResourceType}; #[test] fn bundle_id_capabilities() { let response: BundleIDCapabilitiesResponse = serde_json::from_str( r#"{ "data": [{ "type": "bundleIdCapabilities", "id": "8T4D9KSP2Q_ICLOUD", "attributes": { "capabilityType": "ICLOUD", "settings": [{ "key": "ICLOUD_VERSION", "name": "iCloud", "description": "Choose the iCloud services this app uses.", "enabledByDefault": true, "visible": true, "allowedInstances": "SINGLE", "minInstances": 1, "options": [{ "key": "XCODE_6", "name": "Include CloudKit support (requires Xcode 6)", "enabledByDefault": true, "enabled": true, "supportsWildcard": false }] }] }, "links": { "self": "https://api.appstoreconnect.apple.com/v1/bundleIdCapabilities/8T4D9KSP2Q_ICLOUD" } }, { "type": "bundleIdCapabilities", "id": "8T4D9KSP2Q_WIFI_AWARE", "attributes": { "capabilityType": "WIFI_AWARE" } }], "links": { "self": "https://api.appstoreconnect.apple.com/v1/bundleIds/8T4D9KSP2Q/bundleIdCapabilities" }, "meta": { "paging": { "total": 2, "limit": 20 } } }"#, ) .unwrap(); let attributes = &response.data[0].attributes; assert_eq!(attributes.capability_type, Some(CapabilityType::ICloud)); assert_eq!( attributes.settings[0].key, Some(CapabilitySettingKey::ICloudVersion) ); assert_eq!( attributes.settings[0].allowed_instances, Some(AllowedInstances::Single) ); assert_eq!( attributes.settings[0].options[0].key, Some(CapabilityOptionKey::Xcode6) ); assert_eq!( response.data[1].attributes.capability_type, Some(CapabilityType::Other(String::from("WIFI_AWARE"))) ); assert!(response.links.next_page().is_none()); } #[test] fn capability_types_can_be_missing_null_or_relationships() { let response: BundleIDCapabilitiesResponse = serde_json::from_str(r#"{ "data": [ {"id":"APP_HEALTHKIT", "attributes":{"settings":null}}, {"id":"opaque", "attributes":{"capabilityType":null}, "relationships":{"capability":{"data":{"type":"capabilities", "id":"APP_GROUPS"}}}}, {"id":"APP_WIFI_AWARE", "attributes":{}}, {"id":"opaque-unknown", "attributes":{}}, {"id":"APP_", "attributes":{}} ], "links":{} }"#).unwrap(); assert_eq!( response.data[0].capability_type("APP"), Some(CapabilityType::HealthKit) ); assert!(response.data[0].attributes.settings.is_empty()); assert_eq!( response.data[1].capability_type("APP"), Some(CapabilityType::AppGroups) ); assert_eq!( response.data[2].capability_type("APP"), Some(CapabilityType::Other("WIFI_AWARE".into())) ); assert_eq!(response.data[3].capability_type("APP"), None); assert_eq!(response.data[4].capability_type("APP"), None); assert_eq!(response.data[0].capability_type("OTHER_APP"), None); } #[test] fn create_bundle_id_capability() { let request = CreateBundleIDCapabilityRequest { attributes: BundleIDCapabilityCreateAttributes { capability_type: CapabilityType::DataProtection, settings: vec![CapabilitySetting { key: Some(CapabilitySettingKey::DataProtectionPermissionLevel), options: vec![CapabilityOption { key: Some(CapabilityOptionKey::CompleteProtection), ..Default::default() }], ..Default::default() }], }, relationships: BundleIDCapabilityCreateRelationships { bundle_id: Relationship { data: ResourceIdentifier { kind: ResourceType::BundleIDs, id: String::from("8T4D9KSP2Q"), }, }, capability: Relationship { data: ResourceIdentifier { kind: ResourceType::Capabilities, id: CapabilityType::DataProtection, }, }, }, }; assert_eq!( serde_json::to_value(&request).unwrap(), serde_json::json!({ "type": "bundleIdCapabilities", "attributes": { "capabilityType": "DATA_PROTECTION", "settings": [{ "key": "DATA_PROTECTION_PERMISSION_LEVEL", "options": [{ "key": "COMPLETE_PROTECTION" }] }] }, "relationships": { "bundleId": { "data": { "type": "bundleIds", "id": "8T4D9KSP2Q" } }, "capability": { "data": { "type": "capabilities", "id": "DATA_PROTECTION" } } } }) ); let response: BundleIDCapabilityResponse = serde_json::from_str( r#"{ "data": { "type": "bundleIdCapabilities", "id": "8T4D9KSP2Q_DATA_PROTECTION", "attributes": { "capabilityType": "DATA_PROTECTION", "settings": [{ "key": "DATA_PROTECTION_PERMISSION_LEVEL", "options": [{ "key": "COMPLETE_PROTECTION", "enabled": true }] }] }, "links": { "self": "https://api.appstoreconnect.apple.com/v1/bundleIdCapabilities/8T4D9KSP2Q_DATA_PROTECTION" } }, "links": { "self": "https://api.appstoreconnect.apple.com/v1/bundleIdCapabilities" } }"#, ) .unwrap(); assert_eq!(response.data.id, "8T4D9KSP2Q_DATA_PROTECTION"); assert_eq!( response.data.attributes.settings[0].options[0].enabled, Some(true) ); } #[test] fn update_and_delete_bundle_id_capability() { let request = UpdateBundleIDCapabilityRequest { id: String::from("8T4D9KSP2Q_ICLOUD"), attributes: BundleIDCapabilityUpdateAttributes { capability_type: Some(CapabilityType::ICloud), settings: Some(Vec::new()), }, }; assert_eq!(request.path(), "bundleIdCapabilities/8T4D9KSP2Q_ICLOUD"); assert_eq!( serde_json::to_value(&request).unwrap(), serde_json::json!({ "type": "bundleIdCapabilities", "id": "8T4D9KSP2Q_ICLOUD", "attributes": { "capabilityType": "ICLOUD", "settings": [] } }) ); let request = ListBundleIDCapabilitiesRequest { bundle_id_id: String::from("8T4D9KSP2Q"), ..Default::default() }; assert_eq!(request.path(), "bundleIds/8T4D9KSP2Q/bundleIdCapabilities"); let request = DeleteBundleIDCapabilityRequest { bundle_id_capability_id: String::from("8T4D9KSP2Q_ICLOUD"), }; assert_eq!(request.path(), "bundleIdCapabilities/8T4D9KSP2Q_ICLOUD"); } }