Skip to main content

storekit/
subscription_status_stream.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::{duration_to_timeout_ms, error_from_status, parse_json_ptr};
11use crate::subscription_info::{SubscriptionStatus, SubscriptionStatusPayload};
12
13#[derive(Debug, Clone)]
14/// Carries the `StoreKit` statuses for a subscription group.
15pub struct SubscriptionGroupStatuses {
16    /// Subscription group identifier reported by `StoreKit`.
17    pub group_id: String,
18    /// `StoreKit`-provided `statuses` value.
19    pub statuses: Vec<SubscriptionStatus>,
20}
21
22impl SubscriptionStatus {
23    /// Creates a stream backed by `StoreKit` subscription status updates.
24    pub fn updates() -> Result<SubscriptionStatusStream, StoreKitError> {
25        SubscriptionStatusStream::new()
26    }
27
28    /// Creates a stream backed by `StoreKit` subscription-group statuses.
29    pub fn all() -> Result<SubscriptionGroupStatusStream, StoreKitError> {
30        SubscriptionGroupStatusStream::new()
31    }
32}
33
34#[derive(Debug)]
35/// Wraps the `StoreKit` subscription status update stream.
36pub struct SubscriptionStatusStream {
37    handle: NonNull<c_void>,
38    finished: bool,
39}
40
41impl Drop for SubscriptionStatusStream {
42    fn drop(&mut self) {
43        unsafe { ffi::sk_subscription_status_stream_release(self.handle.as_ptr()) };
44    }
45}
46
47impl SubscriptionStatusStream {
48    fn new() -> Result<Self, StoreKitError> {
49        let mut error_message = ptr::null_mut();
50        let handle = unsafe { ffi::sk_subscription_status_stream_create(&mut error_message) };
51        let handle = NonNull::new(handle)
52            .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
53        Ok(Self {
54            handle,
55            finished: false,
56        })
57    }
58
59    /// Returns whether this `StoreKit` stream has reached the end of the sequence.
60    pub const fn is_finished(&self) -> bool {
61        self.finished
62    }
63
64    #[allow(clippy::should_implement_trait)]
65    /// Waits for the next value from the `StoreKit` stream using the default timeout.
66    pub fn next(&mut self) -> Result<Option<SubscriptionStatus>, StoreKitError> {
67        self.next_timeout(Duration::from_secs(30))
68    }
69
70    /// Waits for the next value from the `StoreKit` stream up to the supplied timeout.
71    pub fn next_timeout(
72        &mut self,
73        timeout: Duration,
74    ) -> Result<Option<SubscriptionStatus>, StoreKitError> {
75        let mut status_json = ptr::null_mut();
76        let mut error_message = ptr::null_mut();
77        let status = unsafe {
78            ffi::sk_subscription_status_stream_next(
79                self.handle.as_ptr(),
80                duration_to_timeout_ms(timeout),
81                &mut status_json,
82                &mut error_message,
83            )
84        };
85
86        match status {
87            ffi::status::OK => {
88                let payload = unsafe {
89                    parse_json_ptr::<SubscriptionStatusPayload>(
90                        status_json,
91                        "subscription status update",
92                    )
93                }?;
94                payload.into_subscription_status().map(Some)
95            }
96            ffi::status::END_OF_STREAM => {
97                self.finished = true;
98                Ok(None)
99            }
100            ffi::status::TIMED_OUT => Ok(None),
101            _ => Err(unsafe { error_from_status(status, error_message) }),
102        }
103    }
104}
105
106#[derive(Debug)]
107/// Wraps the `StoreKit` subscription-group status stream.
108pub struct SubscriptionGroupStatusStream {
109    handle: NonNull<c_void>,
110    finished: bool,
111}
112
113impl Drop for SubscriptionGroupStatusStream {
114    fn drop(&mut self) {
115        unsafe { ffi::sk_subscription_group_status_stream_release(self.handle.as_ptr()) };
116    }
117}
118
119impl SubscriptionGroupStatusStream {
120    fn new() -> Result<Self, StoreKitError> {
121        let mut error_message = ptr::null_mut();
122        let handle = unsafe { ffi::sk_subscription_group_status_stream_create(&mut error_message) };
123        let handle = NonNull::new(handle)
124            .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
125        Ok(Self {
126            handle,
127            finished: false,
128        })
129    }
130
131    /// Returns whether this `StoreKit` stream has reached the end of the sequence.
132    pub const fn is_finished(&self) -> bool {
133        self.finished
134    }
135
136    #[allow(clippy::should_implement_trait)]
137    /// Waits for the next value from the `StoreKit` stream using the default timeout.
138    pub fn next(&mut self) -> Result<Option<SubscriptionGroupStatuses>, StoreKitError> {
139        self.next_timeout(Duration::from_secs(30))
140    }
141
142    /// Waits for the next value from the `StoreKit` stream up to the supplied timeout.
143    pub fn next_timeout(
144        &mut self,
145        timeout: Duration,
146    ) -> Result<Option<SubscriptionGroupStatuses>, StoreKitError> {
147        let mut payload_json = ptr::null_mut();
148        let mut error_message = ptr::null_mut();
149        let status = unsafe {
150            ffi::sk_subscription_group_status_stream_next(
151                self.handle.as_ptr(),
152                duration_to_timeout_ms(timeout),
153                &mut payload_json,
154                &mut error_message,
155            )
156        };
157
158        match status {
159            ffi::status::OK => {
160                let payload = unsafe {
161                    parse_json_ptr::<SubscriptionGroupStatusesPayload>(
162                        payload_json,
163                        "subscription group statuses",
164                    )
165                }?;
166                payload.into_group_statuses().map(Some)
167            }
168            ffi::status::END_OF_STREAM => {
169                self.finished = true;
170                Ok(None)
171            }
172            ffi::status::TIMED_OUT => Ok(None),
173            _ => Err(unsafe { error_from_status(status, error_message) }),
174        }
175    }
176}
177
178#[derive(Debug, Deserialize)]
179struct SubscriptionGroupStatusesPayload {
180    #[serde(rename = "groupID")]
181    group_id: String,
182    statuses: Vec<SubscriptionStatusPayload>,
183}
184
185impl SubscriptionGroupStatusesPayload {
186    fn into_group_statuses(self) -> Result<SubscriptionGroupStatuses, StoreKitError> {
187        Ok(SubscriptionGroupStatuses {
188            group_id: self.group_id,
189            statuses: self
190                .statuses
191                .into_iter()
192                .map(SubscriptionStatusPayload::into_subscription_status)
193                .collect::<Result<Vec<_>, _>>()?,
194        })
195    }
196}