| 1 | //! `GET /v1/appPriceSchedules/{id}`. |
| 2 | //! |
| 3 | //! A price schedule is the container for everything an app costs. There is |
| 4 | //! no `POST` here: the schedule comes into existence with the app, and its |
| 5 | //! id **is** the app's id — `/v1/appPriceSchedules/6751143237` and |
| 6 | //! `/v1/apps/6751143237` describe the same product. Callers therefore pass |
| 7 | //! an app id, not a separately-looked-up schedule id. |
| 8 | //! |
| 9 | //! The resource carries no attributes at all. Its only payload is |
| 10 | //! relationships: the base territory, the manual prices, and the automatic |
| 11 | //! ones. This module reads the base territory — the storefront whose price |
| 12 | //! the developer set by hand, and from which Apple converts every other |
| 13 | //! storefront. Reading the prices themselves is [`crate::app_price`]. |
| 14 | |
| 15 | use async_trait::async_trait; |
| 16 | use reqwest::Method; |
| 17 | use serde::{Deserialize, Serialize}; |
| 18 | use smbcloud_ascapi_core::Client; |
| 19 | use smbcloud_ascapi_core::Result; |
| 20 | |
| 21 | pub const RESOURCE_TYPE: &str = "appPriceSchedules"; |
| 22 | |
| 23 | /// An app's price schedule, flattened to the one fact it carries. |
| 24 | /// |
| 25 | /// Not [`Resource`](smbcloud_ascapi_core::jsonapi::Resource), because that |
| 26 | /// type requires an `attributes` object and `appPriceSchedules` returns |
| 27 | /// none — the resource is relationships and links only. |
| 28 | #[derive(Debug, Clone, Serialize, Deserialize)] |
| 29 | #[serde(rename_all = "camelCase")] |
| 30 | pub struct AppPriceSchedule { |
| 31 | /// The schedule id, which is also the app's App Store Connect id. |
| 32 | pub id: String, |
| 33 | /// Territory code whose price was set manually and from which Apple |
| 34 | /// converts the rest, e.g. `USA`. `None` if Apple did not return the |
| 35 | /// relationship. |
| 36 | pub base_territory: Option<String>, |
| 37 | } |
| 38 | |
| 39 | #[derive(Debug, Clone, Deserialize)] |
| 40 | struct ScheduleDocument { |
| 41 | data: ScheduleResource, |
| 42 | } |
| 43 | |
| 44 | #[derive(Debug, Clone, Deserialize)] |
| 45 | struct ScheduleResource { |
| 46 | id: String, |
| 47 | #[serde(default)] |
| 48 | relationships: ScheduleRelationships, |
| 49 | } |
| 50 | |
| 51 | #[derive(Debug, Clone, Default, Deserialize)] |
| 52 | #[serde(rename_all = "camelCase")] |
| 53 | struct ScheduleRelationships { |
| 54 | #[serde(default)] |
| 55 | base_territory: Option<crate::app_price::ToOne>, |
| 56 | } |
| 57 | |
| 58 | /// App price schedules: the container an app's prices hang off. |
| 59 | /// |
| 60 | /// An extension trait rather than inherent methods, because `Client` |
| 61 | /// lives in the core crate and Rust only allows inherent impls in the |
| 62 | /// crate that defines the type. Import it, or the crate's `prelude`, |
| 63 | /// to call these on a `Client`. |
| 64 | #[async_trait] |
| 65 | pub trait AppPriceSchedulesApi { |
| 66 | /// `GET /v1/appPriceSchedules/{app_id}` — the app's base territory. |
| 67 | /// |
| 68 | /// Takes the **app** id; schedule and app share one id. |
| 69 | async fn get_app_price_schedule(&self, app_id: &str) -> Result<AppPriceSchedule>; |
| 70 | } |
| 71 | |
| 72 | #[async_trait] |
| 73 | impl AppPriceSchedulesApi for Client { |
| 74 | async fn get_app_price_schedule(&self, app_id: &str) -> Result<AppPriceSchedule> { |
| 75 | let path = format!("/v1/appPriceSchedules/{app_id}"); |
| 76 | let doc: ScheduleDocument = self |
| 77 | .request( |
| 78 | Method::GET, |
| 79 | &path, |
| 80 | &[("include", "baseTerritory")], |
| 81 | None::<&()>, |
| 82 | ) |
| 83 | .await?; |
| 84 | Ok(AppPriceSchedule { |
| 85 | id: doc.data.id, |
| 86 | base_territory: doc |
| 87 | .data |
| 88 | .relationships |
| 89 | .base_territory |
| 90 | .and_then(|rel| rel.data) |
| 91 | .map(|reference| reference.id), |
| 92 | }) |
| 93 | } |
| 94 | } |