Skip to main content

storekit/
storefront.rs

1use core::ffi::c_void;
2use core::ptr;
3use std::ptr::NonNull;
4use std::time::Duration;
5
6use serde::Deserialize;
7
8use crate::error::StoreKitError;
9use crate::ffi;
10use crate::private::{
11    duration_to_timeout_ms, error_from_status, parse_json_ptr, parse_optional_json_ptr,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15/// Wraps `StoreKit.Storefront`.
16pub struct Storefront {
17    /// Country code reported by `StoreKit`.
18    pub country_code: String,
19    /// `StoreKit` identifier for this value.
20    pub id: String,
21    /// Currency code reported by `StoreKit`.
22    pub currency_code: Option<String>,
23}
24
25impl Storefront {
26    /// Fetches the current `StoreKit.Storefront`.
27    pub fn current() -> Result<Option<Self>, StoreKitError> {
28        let mut storefront_json = ptr::null_mut();
29        let mut error_message = ptr::null_mut();
30        let status =
31            unsafe { ffi::sk_storefront_current_json(&mut storefront_json, &mut error_message) };
32        if status != ffi::status::OK {
33            return Err(unsafe { error_from_status(status, error_message) });
34        }
35        unsafe { parse_optional_json_ptr::<StorefrontPayload>(storefront_json, "storefront") }
36            .map(|payload| payload.map(StorefrontPayload::into_storefront))
37    }
38
39    /// Creates a stream backed by `StoreKit.Storefront` updates.
40    pub fn updates() -> Result<StorefrontStream, StoreKitError> {
41        StorefrontStream::new()
42    }
43}
44
45#[derive(Debug)]
46/// Wraps the `StoreKit` storefront update stream.
47pub struct StorefrontStream {
48    handle: NonNull<c_void>,
49    finished: bool,
50}
51
52impl Drop for StorefrontStream {
53    fn drop(&mut self) {
54        unsafe { ffi::sk_storefront_stream_release(self.handle.as_ptr()) };
55    }
56}
57
58impl StorefrontStream {
59    fn new() -> Result<Self, StoreKitError> {
60        let mut error_message = ptr::null_mut();
61        let handle = unsafe { ffi::sk_storefront_stream_create(&mut error_message) };
62        let handle = NonNull::new(handle)
63            .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
64        Ok(Self {
65            handle,
66            finished: false,
67        })
68    }
69
70    /// Returns whether this `StoreKit` stream has reached the end of the sequence.
71    pub const fn is_finished(&self) -> bool {
72        self.finished
73    }
74
75    #[allow(clippy::should_implement_trait)]
76    /// Waits for the next value from the `StoreKit` stream using the default timeout.
77    pub fn next(&mut self) -> Result<Option<Storefront>, StoreKitError> {
78        self.next_timeout(Duration::from_secs(30))
79    }
80
81    /// Waits for the next value from the `StoreKit` stream up to the supplied timeout.
82    pub fn next_timeout(&mut self, timeout: Duration) -> Result<Option<Storefront>, StoreKitError> {
83        let mut storefront_json = ptr::null_mut();
84        let mut error_message = ptr::null_mut();
85        let status = unsafe {
86            ffi::sk_storefront_stream_next(
87                self.handle.as_ptr(),
88                duration_to_timeout_ms(timeout),
89                &mut storefront_json,
90                &mut error_message,
91            )
92        };
93
94        match status {
95            ffi::status::OK => {
96                let payload =
97                    unsafe { parse_json_ptr::<StorefrontPayload>(storefront_json, "storefront") }?;
98                Ok(Some(payload.into_storefront()))
99            }
100            ffi::status::END_OF_STREAM => {
101                self.finished = true;
102                Ok(None)
103            }
104            ffi::status::TIMED_OUT => Ok(None),
105            _ => Err(unsafe { error_from_status(status, error_message) }),
106        }
107    }
108}
109
110#[derive(Debug, Deserialize)]
111pub(crate) struct StorefrontPayload {
112    #[serde(rename = "countryCode")]
113    country_code: String,
114    id: String,
115    #[serde(rename = "currencyCode")]
116    currency_code: Option<String>,
117}
118
119impl StorefrontPayload {
120    pub(crate) fn into_storefront(self) -> Storefront {
121        Storefront {
122            country_code: self.country_code,
123            id: self.id,
124            currency_code: self.currency_code,
125        }
126    }
127}