Skip to main content

storekit/
error.rs

1use core::ffi::c_char;
2use std::fmt;
3
4use serde::Deserialize;
5
6use crate::ffi;
7use crate::private::take_string;
8
9#[derive(Debug, Clone)]
10pub enum StoreKitError {
11    InvalidArgument(String),
12    TimedOut(String),
13    NotSupported(String),
14    Framework(StoreKitFrameworkError),
15    Verification(VerificationFailure),
16    Unknown(String),
17}
18
19impl fmt::Display for StoreKitError {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::InvalidArgument(message)
23            | Self::TimedOut(message)
24            | Self::NotSupported(message)
25            | Self::Unknown(message) => formatter.write_str(message),
26            Self::Framework(error) => write!(
27                formatter,
28                "{} (domain={}, code={})",
29                error.localized_description, error.domain, error.code
30            ),
31            Self::Verification(error) => write!(
32                formatter,
33                "{} ({})",
34                error.localized_description,
35                error.code.as_str()
36            ),
37        }
38    }
39}
40
41impl std::error::Error for StoreKitError {}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct StoreKitFrameworkError {
45    pub domain: String,
46    pub code: i64,
47    pub localized_description: String,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct VerificationFailure {
52    pub code: VerificationErrorCode,
53    pub localized_description: String,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum VerificationErrorCode {
58    RevokedCertificate,
59    InvalidCertificateChain,
60    InvalidDeviceVerification,
61    InvalidEncoding,
62    InvalidSignature,
63    MissingRequiredProperties,
64    Unknown(String),
65}
66
67impl VerificationErrorCode {
68    pub fn as_str(&self) -> &str {
69        match self {
70            Self::RevokedCertificate => "revokedCertificate",
71            Self::InvalidCertificateChain => "invalidCertificateChain",
72            Self::InvalidDeviceVerification => "invalidDeviceVerification",
73            Self::InvalidEncoding => "invalidEncoding",
74            Self::InvalidSignature => "invalidSignature",
75            Self::MissingRequiredProperties => "missingRequiredProperties",
76            Self::Unknown(value) => value.as_str(),
77        }
78    }
79
80    pub(crate) fn from_raw(raw: String) -> Self {
81        match raw.as_str() {
82            "revokedCertificate" => Self::RevokedCertificate,
83            "invalidCertificateChain" => Self::InvalidCertificateChain,
84            "invalidDeviceVerification" => Self::InvalidDeviceVerification,
85            "invalidEncoding" => Self::InvalidEncoding,
86            "invalidSignature" => Self::InvalidSignature,
87            "missingRequiredProperties" => Self::MissingRequiredProperties,
88            _ => Self::Unknown(raw),
89        }
90    }
91}
92
93#[derive(Debug, Deserialize)]
94pub(crate) struct VerificationErrorPayload {
95    #[allow(dead_code)]
96    kind: String,
97    code: String,
98    #[serde(rename = "localizedDescription")]
99    localized_description: String,
100}
101
102impl VerificationFailure {
103    pub(crate) fn from_payload(payload: VerificationErrorPayload) -> Self {
104        Self {
105            code: VerificationErrorCode::from_raw(payload.code),
106            localized_description: payload.localized_description,
107        }
108    }
109}
110
111#[derive(Debug, Deserialize)]
112struct FrameworkErrorPayload {
113    #[allow(dead_code)]
114    kind: String,
115    domain: String,
116    code: i64,
117    #[serde(rename = "localizedDescription")]
118    localized_description: String,
119}
120
121pub(crate) unsafe fn from_swift(status: i32, err_msg: *mut c_char) -> StoreKitError {
122    let message = take_string(err_msg);
123    match status {
124        ffi::status::INVALID_ARGUMENT => StoreKitError::InvalidArgument(
125            message.unwrap_or_else(|| "StoreKit reported an invalid argument".to_owned()),
126        ),
127        ffi::status::TIMED_OUT => StoreKitError::TimedOut(
128            message.unwrap_or_else(|| "StoreKit operation timed out".to_owned()),
129        ),
130        ffi::status::NOT_SUPPORTED => StoreKitError::NotSupported(
131            message.unwrap_or_else(|| "StoreKit operation is not supported".to_owned()),
132        ),
133        ffi::status::FRAMEWORK_ERROR => parse_framework_error(message),
134        ffi::status::VERIFICATION_ERROR => parse_verification_error(message),
135        _ => StoreKitError::Unknown(
136            message
137                .unwrap_or_else(|| format!("StoreKit bridge returned unexpected status {status}")),
138        ),
139    }
140}
141
142fn parse_framework_error(message: Option<String>) -> StoreKitError {
143    message.map_or_else(
144        || {
145            StoreKitError::Framework(StoreKitFrameworkError {
146                domain: "StoreKit".to_owned(),
147                code: i64::from(ffi::status::FRAMEWORK_ERROR),
148                localized_description: "StoreKit framework error".to_owned(),
149            })
150        },
151        |json| {
152            serde_json::from_str::<FrameworkErrorPayload>(&json).map_or_else(
153                |_| {
154                    StoreKitError::Framework(StoreKitFrameworkError {
155                        domain: "StoreKit".to_owned(),
156                        code: i64::from(ffi::status::FRAMEWORK_ERROR),
157                        localized_description: json,
158                    })
159                },
160                |payload| {
161                    StoreKitError::Framework(StoreKitFrameworkError {
162                        domain: payload.domain,
163                        code: payload.code,
164                        localized_description: payload.localized_description,
165                    })
166                },
167            )
168        },
169    )
170}
171
172fn parse_verification_error(message: Option<String>) -> StoreKitError {
173    message.map_or_else(
174        || {
175            StoreKitError::Verification(VerificationFailure {
176                code: VerificationErrorCode::Unknown("unknown".to_owned()),
177                localized_description: "StoreKit verification failed".to_owned(),
178            })
179        },
180        |json| {
181            serde_json::from_str::<VerificationErrorPayload>(&json).map_or_else(
182                |_| {
183                    StoreKitError::Verification(VerificationFailure {
184                        code: VerificationErrorCode::Unknown("unknown".to_owned()),
185                        localized_description: json,
186                    })
187                },
188                |payload| StoreKitError::Verification(VerificationFailure::from_payload(payload)),
189            )
190        },
191    )
192}