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)]
15pub struct Storefront {
16    pub country_code: String,
17    pub id: String,
18    pub currency_code: Option<String>,
19}
20
21impl Storefront {
22    pub fn current() -> Result<Option<Self>, StoreKitError> {
23        let mut storefront_json = ptr::null_mut();
24        let mut error_message = ptr::null_mut();
25        let status =
26            unsafe { ffi::sk_storefront_current_json(&mut storefront_json, &mut error_message) };
27        if status != ffi::status::OK {
28            return Err(unsafe { error_from_status(status, error_message) });
29        }
30        unsafe { parse_optional_json_ptr::<StorefrontPayload>(storefront_json, "storefront") }
31            .map(|payload| payload.map(StorefrontPayload::into_storefront))
32    }
33
34    pub fn updates() -> Result<StorefrontStream, StoreKitError> {
35        StorefrontStream::new()
36    }
37}
38
39#[derive(Debug)]
40pub struct StorefrontStream {
41    handle: NonNull<c_void>,
42    finished: bool,
43}
44
45impl Drop for StorefrontStream {
46    fn drop(&mut self) {
47        unsafe { ffi::sk_storefront_stream_release(self.handle.as_ptr()) };
48    }
49}
50
51impl StorefrontStream {
52    fn new() -> Result<Self, StoreKitError> {
53        let mut error_message = ptr::null_mut();
54        let handle = unsafe { ffi::sk_storefront_stream_create(&mut error_message) };
55        let handle = NonNull::new(handle)
56            .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
57        Ok(Self {
58            handle,
59            finished: false,
60        })
61    }
62
63    pub const fn is_finished(&self) -> bool {
64        self.finished
65    }
66
67    #[allow(clippy::should_implement_trait)]
68    pub fn next(&mut self) -> Result<Option<Storefront>, StoreKitError> {
69        self.next_timeout(Duration::from_secs(30))
70    }
71
72    pub fn next_timeout(&mut self, timeout: Duration) -> Result<Option<Storefront>, StoreKitError> {
73        let mut storefront_json = ptr::null_mut();
74        let mut error_message = ptr::null_mut();
75        let status = unsafe {
76            ffi::sk_storefront_stream_next(
77                self.handle.as_ptr(),
78                duration_to_timeout_ms(timeout),
79                &mut storefront_json,
80                &mut error_message,
81            )
82        };
83
84        match status {
85            ffi::status::OK => {
86                let payload =
87                    unsafe { parse_json_ptr::<StorefrontPayload>(storefront_json, "storefront") }?;
88                Ok(Some(payload.into_storefront()))
89            }
90            ffi::status::END_OF_STREAM => {
91                self.finished = true;
92                Ok(None)
93            }
94            ffi::status::TIMED_OUT => Ok(None),
95            _ => Err(unsafe { error_from_status(status, error_message) }),
96        }
97    }
98}
99
100#[derive(Debug, Deserialize)]
101pub(crate) struct StorefrontPayload {
102    #[serde(rename = "countryCode")]
103    country_code: String,
104    id: String,
105    #[serde(rename = "currencyCode")]
106    currency_code: Option<String>,
107}
108
109impl StorefrontPayload {
110    pub(crate) fn into_storefront(self) -> Storefront {
111        Storefront {
112            country_code: self.country_code,
113            id: self.id,
114            currency_code: self.currency_code,
115        }
116    }
117}