apple 0.2.0

A Rust library for Apple Sign-In authentication, CloudKit Web Services, and App Store Server API
Documentation
use crate::cloudkit::client::CloudKitClient;
use crate::cloudkit::types::DatabaseType;
use crate::error::AppleError;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

#[derive(Debug, Serialize)]
struct AssetUploadRequest {
    tokens: Vec<AssetUploadToken>,
    #[serde(rename = "zoneID", skip_serializing_if = "Option::is_none")]
    zone_id: Option<crate::cloudkit::types::ZoneID>,
}

#[derive(Debug, Serialize)]
struct AssetUploadToken {
    #[serde(rename = "recordName")]
    record_name: String,
    #[serde(rename = "recordType")]
    record_type: String,
    #[serde(rename = "fieldName")]
    field_name: String,
}

#[derive(Debug, Deserialize)]
pub struct AssetUploadResponse {
    pub tokens: Vec<AssetTokenInfo>,
}

#[derive(Debug, Deserialize)]
pub struct AssetTokenInfo {
    #[serde(rename = "recordName")]
    pub record_name: Option<String>,
    #[serde(rename = "fieldName")]
    pub field_name: Option<String>,
    pub url: Option<String>,
}

impl CloudKitClient {
    pub async fn request_asset_upload(
        &self,
        db: &DatabaseType,
        record_name: &str,
        record_type: &str,
        field_name: &str,
        zone_id: Option<crate::cloudkit::types::ZoneID>,
    ) -> Result<AssetUploadResponse, AppleError> {
        let url = self.build_url(db, "assets/upload");
        let request = AssetUploadRequest {
            tokens: vec![AssetUploadToken {
                record_name: record_name.to_string(),
                record_type: record_type.to_string(),
                field_name: field_name.to_string(),
            }],
            zone_id,
        };

        self.signed_post(&url, &request).await
    }

    pub async fn upload_asset(
        &self,
        upload_url: &str,
        data: &[u8],
    ) -> Result<AssetUploadResult, AppleError> {
        let checksum = {
            let mut hasher = Sha256::new();
            hasher.update(data);
            STANDARD.encode(hasher.finalize())
        };

        let res = self
            .http_client
            .post(upload_url)
            .header("Content-Type", "application/octet-stream")
            .body(data.to_vec())
            .send()
            .await
            .map_err(|e| AppleError::HttpError(e.to_string()))?;

        let status = res.status();
        let response_body = res
            .text()
            .await
            .map_err(|e| AppleError::HttpError(e.to_string()))?;

        if !status.is_success() {
            return Err(AppleError::HttpError(format!(
                "Asset upload failed with status {}: {}",
                status, response_body
            )));
        }

        let result: AssetUploadResult = serde_json::from_str(&response_body)
            .map_err(|e| AppleError::JsonError(e.to_string()))?;

        Ok(AssetUploadResult {
            file_checksum: result.file_checksum.or(Some(checksum)),
            size: result.size.or(Some(data.len() as u64)),
            receipt: result.receipt,
        })
    }
}

#[derive(Debug, Deserialize)]
pub struct AssetUploadResult {
    #[serde(rename = "fileChecksum")]
    pub file_checksum: Option<String>,
    pub size: Option<u64>,
    pub receipt: Option<String>,
}