Skip to main content

weatherkit/
changes.rs

1//! WeatherKit change-tracking and historical comparison types.
2
3use core::{ffi::c_void, marker::PhantomData};
4
5use serde::{Deserialize, Deserializer};
6
7use crate::error::WeatherKitError;
8use crate::ffi;
9use crate::private::parse_json_from_handle;
10use crate::service::WeatherMetadata;
11
12/// Marks WeatherKit temperature-based trend values.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum TemperatureUnit {}
15
16/// Marks WeatherKit precipitation- and snowfall-based length values.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum LengthUnit {}
19
20/// Represents the WeatherKit `Deviation` value.
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum Deviation {
24    /// Matches the WeatherKit `MuchHigher` case.
25    MuchHigher,
26    /// Matches the WeatherKit `Higher` case.
27    Higher,
28    /// Matches the WeatherKit `Normal` case.
29    Normal,
30    /// Matches the WeatherKit `Lower` case.
31    Lower,
32    /// Matches the WeatherKit `MuchLower` case.
33    MuchLower,
34    /// Stores an unrecognized WeatherKit case name.
35    Unknown(String),
36}
37
38impl Deviation {
39    pub(crate) fn from_raw(value: String) -> Self {
40        match value.as_str() {
41            "muchHigher" => Self::MuchHigher,
42            "higher" => Self::Higher,
43            "normal" => Self::Normal,
44            "lower" => Self::Lower,
45            "muchLower" => Self::MuchLower,
46            other => Self::Unknown(other.to_owned()),
47        }
48    }
49}
50
51/// Represents the WeatherKit `TrendBaselineKind` value.
52#[derive(Debug, Clone, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum TrendBaselineKind {
55    /// Matches the WeatherKit `Mean` case.
56    Mean,
57    /// Stores an unrecognized WeatherKit case name.
58    Unknown(String),
59}
60
61impl TrendBaselineKind {
62    pub(crate) fn from_raw(value: String) -> Self {
63        match value.as_str() {
64            "mean" => Self::Mean,
65            other => Self::Unknown(other.to_owned()),
66        }
67    }
68}
69
70/// Represents a WeatherKit trend baseline.
71#[derive(Debug, Clone, PartialEq, Deserialize)]
72#[serde(rename_all = "camelCase", bound(deserialize = ""))]
73pub struct TrendBaseline<Unit> {
74    /// Matches the WeatherKit kind value.
75    #[serde(deserialize_with = "deserialize_trend_baseline_kind")]
76    pub kind: TrendBaselineKind,
77    /// Matches the WeatherKit value value.
78    pub value: f64,
79    /// Matches the WeatherKit start date value.
80    pub start_date: String,
81    #[serde(skip)]
82    marker: PhantomData<Unit>,
83}
84
85/// Represents a WeatherKit trend payload.
86#[derive(Debug, Clone, PartialEq, Deserialize)]
87#[serde(rename_all = "camelCase", bound(deserialize = ""))]
88pub struct Trend<Unit> {
89    /// Matches the WeatherKit baseline value.
90    pub baseline: TrendBaseline<Unit>,
91    /// Matches the WeatherKit current value value.
92    pub current_value: f64,
93    /// Matches the WeatherKit deviation value.
94    #[serde(deserialize_with = "deserialize_deviation")]
95    pub deviation: Deviation,
96    #[serde(skip)]
97    marker: PhantomData<Unit>,
98}
99
100/// Represents WeatherKit percentile values.
101#[derive(Debug, Clone, PartialEq, Deserialize)]
102#[serde(rename_all = "camelCase", bound(deserialize = ""))]
103pub struct Percentiles<Unit> {
104    /// Matches the WeatherKit p10 value.
105    pub p10: f64,
106    /// Matches the WeatherKit p50 value.
107    pub p50: f64,
108    /// Matches the WeatherKit p90 value.
109    pub p90: f64,
110    #[serde(skip)]
111    marker: PhantomData<Unit>,
112}
113
114/// Represents a WeatherKit historical comparison result.
115#[derive(Debug, Clone, PartialEq, Deserialize)]
116#[serde(tag = "kind", content = "trend", rename_all = "camelCase")]
117pub enum HistoricalComparison {
118    /// Carries the WeatherKit high-temperature trend.
119    HighTemperature(Trend<TemperatureUnit>),
120    /// Carries the WeatherKit low-temperature trend.
121    LowTemperature(Trend<TemperatureUnit>),
122    /// Carries the WeatherKit precipitation-amount trend.
123    PrecipitationAmount(Trend<LengthUnit>),
124    /// Carries the WeatherKit snowfall-amount trend.
125    SnowfallAmount(Trend<LengthUnit>),
126}
127
128/// Wraps the WeatherKit historical comparisons payload.
129#[derive(Debug, Clone, PartialEq, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct HistoricalComparisons {
132    /// Matches the WeatherKit comparisons value.
133    pub comparisons: Vec<HistoricalComparison>,
134    /// Matches the WeatherKit metadata value.
135    pub metadata: WeatherMetadata,
136}
137
138impl HistoricalComparisons {
139    pub(crate) fn option_from_owned_ptr(ptr: *mut c_void) -> Result<Option<Self>, WeatherKitError> {
140        parse_json_from_handle(
141            ptr,
142            ffi::json_handle::wk_json_handle_release,
143            ffi::json_handle::wk_json_handle_copy_json,
144            "historical comparisons",
145        )
146    }
147
148    /// Returns the number of WeatherKit comparisons in this collection.
149    pub fn len(&self) -> usize {
150        self.comparisons.len()
151    }
152
153    /// Returns whether this WeatherKit comparison collection is empty.
154    pub fn is_empty(&self) -> bool {
155        self.comparisons.is_empty()
156    }
157
158    /// Iterates over the WeatherKit comparisons in this collection.
159    pub fn iter(&self) -> std::slice::Iter<'_, HistoricalComparison> {
160        self.comparisons.iter()
161    }
162}
163
164impl<'a> IntoIterator for &'a HistoricalComparisons {
165    type Item = &'a HistoricalComparison;
166    type IntoIter = std::slice::Iter<'a, HistoricalComparison>;
167
168    fn into_iter(self) -> Self::IntoIter {
169        self.iter()
170    }
171}
172
173/// Represents the WeatherKit `WeatherChangeDirection` value.
174#[derive(Debug, Clone, PartialEq, Eq)]
175#[non_exhaustive]
176pub enum WeatherChangeDirection {
177    /// Matches the WeatherKit `Increase` case.
178    Increase,
179    /// Matches the WeatherKit `Decrease` case.
180    Decrease,
181    /// Matches the WeatherKit `Steady` case.
182    Steady,
183    /// Stores an unrecognized WeatherKit case name.
184    Unknown(String),
185}
186
187impl WeatherChangeDirection {
188    pub(crate) fn from_raw(value: String) -> Self {
189        match value.as_str() {
190            "increase" => Self::Increase,
191            "decrease" => Self::Decrease,
192            "steady" => Self::Steady,
193            other => Self::Unknown(other.to_owned()),
194        }
195    }
196}
197
198/// Represents a WeatherKit weather change entry.
199#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
200#[serde(rename_all = "camelCase")]
201pub struct WeatherChange {
202    /// Matches the WeatherKit date value.
203    pub date: String,
204    /// Matches the WeatherKit day precipitation amount value.
205    #[serde(deserialize_with = "deserialize_weather_change_direction")]
206    pub day_precipitation_amount: WeatherChangeDirection,
207    /// Matches the WeatherKit high temperature value.
208    #[serde(deserialize_with = "deserialize_weather_change_direction")]
209    pub high_temperature: WeatherChangeDirection,
210    /// Matches the WeatherKit low temperature value.
211    #[serde(deserialize_with = "deserialize_weather_change_direction")]
212    pub low_temperature: WeatherChangeDirection,
213    /// Matches the WeatherKit night precipitation amount value.
214    #[serde(deserialize_with = "deserialize_weather_change_direction")]
215    pub night_precipitation_amount: WeatherChangeDirection,
216}
217
218/// Wraps the WeatherKit weather changes payload.
219#[derive(Debug, Clone, PartialEq, Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct WeatherChanges {
222    /// Matches the WeatherKit changes value.
223    pub changes: Vec<WeatherChange>,
224    /// Matches the WeatherKit metadata value.
225    pub metadata: WeatherMetadata,
226}
227
228impl WeatherChanges {
229    pub(crate) fn option_from_owned_ptr(ptr: *mut c_void) -> Result<Option<Self>, WeatherKitError> {
230        parse_json_from_handle(
231            ptr,
232            ffi::json_handle::wk_json_handle_release,
233            ffi::json_handle::wk_json_handle_copy_json,
234            "weather changes",
235        )
236    }
237
238    /// Returns the number of WeatherKit changes in this collection.
239    pub fn len(&self) -> usize {
240        self.changes.len()
241    }
242
243    /// Returns whether this WeatherKit change collection is empty.
244    pub fn is_empty(&self) -> bool {
245        self.changes.is_empty()
246    }
247
248    /// Iterates over the WeatherKit changes in this collection.
249    pub fn iter(&self) -> std::slice::Iter<'_, WeatherChange> {
250        self.changes.iter()
251    }
252}
253
254impl<'a> IntoIterator for &'a WeatherChanges {
255    type Item = &'a WeatherChange;
256    type IntoIter = std::slice::Iter<'a, WeatherChange>;
257
258    fn into_iter(self) -> Self::IntoIter {
259        self.iter()
260    }
261}
262
263pub(crate) fn deserialize_deviation<'de, D>(deserializer: D) -> Result<Deviation, D::Error>
264where
265    D: Deserializer<'de>,
266{
267    let raw = String::deserialize(deserializer)?;
268    Ok(Deviation::from_raw(raw))
269}
270
271pub(crate) fn deserialize_trend_baseline_kind<'de, D>(
272    deserializer: D,
273) -> Result<TrendBaselineKind, D::Error>
274where
275    D: Deserializer<'de>,
276{
277    let raw = String::deserialize(deserializer)?;
278    Ok(TrendBaselineKind::from_raw(raw))
279}
280
281pub(crate) fn deserialize_weather_change_direction<'de, D>(
282    deserializer: D,
283) -> Result<WeatherChangeDirection, D::Error>
284where
285    D: Deserializer<'de>,
286{
287    let raw = String::deserialize(deserializer)?;
288    Ok(WeatherChangeDirection::from_raw(raw))
289}