1use core::ffi::{c_char, c_void};
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use serde::Deserialize;
5
6use crate::availability_kind::WeatherAvailability;
7use crate::changes::{HistoricalComparisons, WeatherChanges};
8use crate::current_weather::CurrentWeather;
9use crate::daily_forecast::{DailyForecast, DayForecast};
10use crate::error::WeatherKitError;
11use crate::ffi;
12use crate::hourly_forecast::HourlyForecast;
13use crate::minute_forecast::MinuteForecastCollection;
14use crate::moon_events::MoonEvents;
15use crate::pressure::Pressure;
16use crate::private::{error_from_status, parse_json_from_handle};
17use crate::statistics::{
18 DailyWeatherStatistics, DailyWeatherStatisticsQuery, DailyWeatherStatisticsResult,
19 DailyWeatherSummary, DailyWeatherSummaryQuery, DailyWeatherSummaryResult,
20 DayPrecipitationStatistics, DayPrecipitationSummary, DayTemperatureStatistics,
21 DayTemperatureSummary, HourTemperatureStatistics, HourlyWeatherStatistics,
22 HourlyWeatherStatisticsQuery, MonthPrecipitationStatistics, MonthTemperatureStatistics,
23 MonthlyWeatherStatistics, MonthlyWeatherStatisticsQuery, MonthlyWeatherStatisticsResult,
24};
25use crate::sun_events::SunEvents;
26use crate::weather_alert::{alerts_from_owned_ptr, WeatherAlert};
27use crate::weather_attribution::WeatherAttribution;
28
29#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct CLLocation {
32 pub latitude: f64,
33 pub longitude: f64,
34}
35
36impl CLLocation {
37 pub const fn new(latitude: f64, longitude: f64) -> Self {
38 Self {
39 latitude,
40 longitude,
41 }
42 }
43
44 fn validate(&self) -> Result<(), WeatherKitError> {
45 if !self.latitude.is_finite() || !self.longitude.is_finite() {
46 return Err(WeatherKitError::bridge(
47 -1,
48 "latitude and longitude must be finite numbers",
49 ));
50 }
51 if !(-90.0..=90.0).contains(&self.latitude) {
52 return Err(WeatherKitError::bridge(
53 -1,
54 format!("latitude {} is outside -90..=90", self.latitude),
55 ));
56 }
57 if !(-180.0..=180.0).contains(&self.longitude) {
58 return Err(WeatherKitError::bridge(
59 -1,
60 format!("longitude {} is outside -180..=180", self.longitude),
61 ));
62 }
63 Ok(())
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct DateInterval {
69 pub start: SystemTime,
70 pub end: SystemTime,
71}
72
73impl DateInterval {
74 pub fn new(start: SystemTime, end: SystemTime) -> Result<Self, WeatherKitError> {
75 if start > end {
76 return Err(WeatherKitError::bridge(
77 -1,
78 "date interval start must not be after end",
79 ));
80 }
81 Ok(Self { start, end })
82 }
83
84 fn start_seconds(&self) -> Result<f64, WeatherKitError> {
85 unix_seconds(self.start)
86 }
87
88 fn end_seconds(&self) -> Result<f64, WeatherKitError> {
89 unix_seconds(self.end)
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct WeatherMetadata {
96 pub date: String,
97 pub expiration_date: String,
98 pub location: CLLocation,
99}
100
101#[derive(Debug, Clone, PartialEq, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct Weather {
104 pub current_weather: CurrentWeather,
105 pub hourly_forecast: Vec<crate::hourly_forecast::HourForecast>,
106 pub daily_forecast: Vec<DayForecast>,
107 pub minute_forecast: Option<Vec<crate::minute_forecast::MinuteForecast>>,
108 #[serde(default)]
109 pub weather_alerts: Vec<WeatherAlert>,
110 pub availability: WeatherAvailability,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub enum WeatherQuery {
115 Current,
116 Minute,
117 Hourly,
118 HourlyIn(DateInterval),
119 Daily,
120 DailyIn(DateInterval),
121 Alerts,
122 Availability,
123 Changes,
124 HistoricalComparisons,
125}
126
127#[derive(Debug, Clone, PartialEq)]
128pub enum WeatherQueryResult {
129 CurrentWeather(Box<CurrentWeather>),
130 MinuteForecast(Option<Box<MinuteForecastCollection>>),
131 HourlyForecast(Box<HourlyForecast>),
132 DailyForecast(Box<DailyForecast>),
133 WeatherAlerts(Vec<WeatherAlert>),
134 Availability(Box<WeatherAvailability>),
135 WeatherChanges(Option<Box<WeatherChanges>>),
136 HistoricalComparisons(Option<Box<HistoricalComparisons>>),
137}
138
139impl WeatherQuery {
140 fn fetch(
141 &self,
142 service: WeatherService,
143 location: &CLLocation,
144 ) -> Result<WeatherQueryResult, WeatherKitError> {
145 match self {
146 Self::Current => service
147 .current_weather(location)
148 .map(Box::new)
149 .map(WeatherQueryResult::CurrentWeather),
150 Self::Minute => service
151 .minute_forecast(location)
152 .map(|forecast| forecast.map(Box::new))
153 .map(WeatherQueryResult::MinuteForecast),
154 Self::Hourly => service
155 .hourly_forecast(location)
156 .map(Box::new)
157 .map(WeatherQueryResult::HourlyForecast),
158 Self::HourlyIn(interval) => service
159 .hourly_forecast_in(location, interval.clone())
160 .map(Box::new)
161 .map(WeatherQueryResult::HourlyForecast),
162 Self::Daily => service
163 .daily_forecast(location)
164 .map(Box::new)
165 .map(WeatherQueryResult::DailyForecast),
166 Self::DailyIn(interval) => service
167 .daily_forecast_in(location, interval.clone())
168 .map(Box::new)
169 .map(WeatherQueryResult::DailyForecast),
170 Self::Alerts => service
171 .weather_alerts(location)
172 .map(WeatherQueryResult::WeatherAlerts),
173 Self::Availability => service
174 .availability(location)
175 .map(Box::new)
176 .map(WeatherQueryResult::Availability),
177 Self::Changes => service
178 .weather_changes(location)
179 .map(|changes| changes.map(Box::new))
180 .map(WeatherQueryResult::WeatherChanges),
181 Self::HistoricalComparisons => service
182 .historical_comparisons(location)
183 .map(|comparisons| comparisons.map(Box::new))
184 .map(WeatherQueryResult::HistoricalComparisons),
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, Default)]
190pub struct WeatherService {
191 kind: ServiceKind,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
195enum ServiceKind {
196 #[default]
197 Shared,
198 Owned,
199}
200
201struct ServiceHandle {
202 ptr: *mut c_void,
203}
204
205type LocationFetchFn =
206 unsafe extern "C" fn(*mut c_void, f64, f64, *mut *mut c_void, *mut *mut c_char) -> i32;
207type IntervalFetchFn = unsafe extern "C" fn(
208 *mut c_void,
209 f64,
210 f64,
211 i32,
212 f64,
213 f64,
214 *mut *mut c_void,
215 *mut *mut c_char,
216) -> i32;
217type ScopedQueryFetchFn = unsafe extern "C" fn(
218 *mut c_void,
219 f64,
220 f64,
221 i32,
222 i32,
223 f64,
224 f64,
225 i64,
226 i64,
227 *mut *mut c_void,
228 *mut *mut c_char,
229) -> i32;
230type ServiceFetchFn = unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut c_char) -> i32;
231
232#[derive(Debug, Clone, Copy)]
233enum QueryScope<'a> {
234 None,
235 Interval(&'a DateInterval),
236 Index { start: i64, end: i64 },
237}
238
239impl ServiceHandle {
240 fn acquire(kind: ServiceKind) -> Result<Self, WeatherKitError> {
241 let ptr = unsafe {
242 match kind {
243 ServiceKind::Shared => ffi::service::wk_weather_service_shared(),
244 ServiceKind::Owned => ffi::service::wk_weather_service_new(),
245 }
246 };
247 if ptr.is_null() {
248 Err(WeatherKitError::bridge(
249 -1,
250 "failed to acquire WeatherService handle",
251 ))
252 } else {
253 Ok(Self { ptr })
254 }
255 }
256
257 fn as_ptr(&self) -> *mut c_void {
258 self.ptr
259 }
260}
261
262impl Drop for ServiceHandle {
263 fn drop(&mut self) {
264 if !self.ptr.is_null() {
265 unsafe {
266 ffi::service::wk_weather_service_release(self.ptr);
267 }
268 self.ptr = core::ptr::null_mut();
269 }
270 }
271}
272
273impl WeatherService {
274 pub const fn shared() -> Self {
275 Self {
276 kind: ServiceKind::Shared,
277 }
278 }
279
280 pub const fn new() -> Self {
281 Self {
282 kind: ServiceKind::Owned,
283 }
284 }
285
286 pub fn attribution(&self) -> Result<WeatherAttribution, WeatherKitError> {
287 let ptr = self.fetch_service_handle(
288 ffi::service::wk_weather_service_attribution,
289 "WeatherService.attribution",
290 )?;
291 WeatherAttribution::from_owned_ptr(ptr)
292 }
293
294 pub fn weather(&self, location: &CLLocation) -> Result<Weather, WeatherKitError> {
295 let ptr = self.fetch_location_handle(
296 location,
297 ffi::service::wk_weather_service_weather,
298 "WeatherService.weather(for:)",
299 )?;
300 parse_json_from_handle(
301 ptr,
302 ffi::service::wk_weather_release,
303 ffi::service::wk_weather_copy_json,
304 "weather",
305 )
306 }
307
308 pub fn current_weather(
309 &self,
310 location: &CLLocation,
311 ) -> Result<CurrentWeather, WeatherKitError> {
312 let ptr = self.fetch_location_handle(
313 location,
314 ffi::service::wk_weather_service_current_weather,
315 "WeatherService.weather(for: including: .current)",
316 )?;
317 CurrentWeather::from_owned_ptr(ptr)
318 }
319
320 pub fn hourly_forecast(
321 &self,
322 location: &CLLocation,
323 ) -> Result<HourlyForecast, WeatherKitError> {
324 let ptr = self.fetch_interval_handle(
325 location,
326 None,
327 ffi::service::wk_weather_service_hourly_forecast,
328 "WeatherService.weather(for: including: .hourly)",
329 )?;
330 HourlyForecast::from_owned_ptr(ptr)
331 }
332
333 pub fn hourly_forecast_in(
334 &self,
335 location: &CLLocation,
336 interval: DateInterval,
337 ) -> Result<HourlyForecast, WeatherKitError> {
338 let ptr = self.fetch_interval_handle(
339 location,
340 Some(&interval),
341 ffi::service::wk_weather_service_hourly_forecast,
342 "WeatherService.weather(for: including: .hourly(startDate:endDate))",
343 )?;
344 HourlyForecast::from_owned_ptr(ptr)
345 }
346
347 pub fn daily_forecast(&self, location: &CLLocation) -> Result<DailyForecast, WeatherKitError> {
348 let ptr = self.fetch_interval_handle(
349 location,
350 None,
351 ffi::service::wk_weather_service_daily_forecast,
352 "WeatherService.weather(for: including: .daily)",
353 )?;
354 DailyForecast::from_owned_ptr(ptr)
355 }
356
357 pub fn daily_forecast_in(
358 &self,
359 location: &CLLocation,
360 interval: DateInterval,
361 ) -> Result<DailyForecast, WeatherKitError> {
362 let ptr = self.fetch_interval_handle(
363 location,
364 Some(&interval),
365 ffi::service::wk_weather_service_daily_forecast,
366 "WeatherService.weather(for: including: .daily(startDate:endDate))",
367 )?;
368 DailyForecast::from_owned_ptr(ptr)
369 }
370
371 pub fn minute_forecast(
372 &self,
373 location: &CLLocation,
374 ) -> Result<Option<MinuteForecastCollection>, WeatherKitError> {
375 let ptr = self.fetch_location_handle(
376 location,
377 ffi::service::wk_weather_service_minute_forecast,
378 "WeatherService.weather(for: including: .minute)",
379 )?;
380 MinuteForecastCollection::option_from_owned_ptr(ptr)
381 }
382
383 pub fn weather_alerts(
384 &self,
385 location: &CLLocation,
386 ) -> Result<Vec<WeatherAlert>, WeatherKitError> {
387 let ptr = self.fetch_location_handle(
388 location,
389 ffi::service::wk_weather_service_weather_alerts,
390 "WeatherService.weather(for: including: .alerts)",
391 )?;
392 alerts_from_owned_ptr(ptr)
393 }
394
395 pub fn availability(
396 &self,
397 location: &CLLocation,
398 ) -> Result<WeatherAvailability, WeatherKitError> {
399 let ptr = self.fetch_location_handle(
400 location,
401 ffi::service::wk_weather_service_availability,
402 "WeatherService.weather(for: including: .availability)",
403 )?;
404 WeatherAvailability::from_owned_ptr(ptr)
405 }
406
407 pub fn weather_including(
408 &self,
409 location: &CLLocation,
410 query: WeatherQuery,
411 ) -> Result<WeatherQueryResult, WeatherKitError> {
412 query.fetch(*self, location)
413 }
414
415 pub fn weather_including2(
416 &self,
417 location: &CLLocation,
418 query1: WeatherQuery,
419 query2: WeatherQuery,
420 ) -> Result<(WeatherQueryResult, WeatherQueryResult), WeatherKitError> {
421 Ok((query1.fetch(*self, location)?, query2.fetch(*self, location)?))
422 }
423
424 pub fn weather_including3(
425 &self,
426 location: &CLLocation,
427 query1: WeatherQuery,
428 query2: WeatherQuery,
429 query3: WeatherQuery,
430 ) -> Result<(WeatherQueryResult, WeatherQueryResult, WeatherQueryResult), WeatherKitError> {
431 Ok((
432 query1.fetch(*self, location)?,
433 query2.fetch(*self, location)?,
434 query3.fetch(*self, location)?,
435 ))
436 }
437
438 pub fn weather_including4(
439 &self,
440 location: &CLLocation,
441 query1: WeatherQuery,
442 query2: WeatherQuery,
443 query3: WeatherQuery,
444 query4: WeatherQuery,
445 ) -> Result<
446 (
447 WeatherQueryResult,
448 WeatherQueryResult,
449 WeatherQueryResult,
450 WeatherQueryResult,
451 ),
452 WeatherKitError,
453 > {
454 Ok((
455 query1.fetch(*self, location)?,
456 query2.fetch(*self, location)?,
457 query3.fetch(*self, location)?,
458 query4.fetch(*self, location)?,
459 ))
460 }
461
462 pub fn weather_including5(
463 &self,
464 location: &CLLocation,
465 query1: WeatherQuery,
466 query2: WeatherQuery,
467 query3: WeatherQuery,
468 query4: WeatherQuery,
469 query5: WeatherQuery,
470 ) -> Result<
471 (
472 WeatherQueryResult,
473 WeatherQueryResult,
474 WeatherQueryResult,
475 WeatherQueryResult,
476 WeatherQueryResult,
477 ),
478 WeatherKitError,
479 > {
480 Ok((
481 query1.fetch(*self, location)?,
482 query2.fetch(*self, location)?,
483 query3.fetch(*self, location)?,
484 query4.fetch(*self, location)?,
485 query5.fetch(*self, location)?,
486 ))
487 }
488
489 #[allow(clippy::too_many_arguments)]
490 pub fn weather_including6(
491 &self,
492 location: &CLLocation,
493 query1: WeatherQuery,
494 query2: WeatherQuery,
495 query3: WeatherQuery,
496 query4: WeatherQuery,
497 query5: WeatherQuery,
498 query6: WeatherQuery,
499 ) -> Result<
500 (
501 WeatherQueryResult,
502 WeatherQueryResult,
503 WeatherQueryResult,
504 WeatherQueryResult,
505 WeatherQueryResult,
506 WeatherQueryResult,
507 ),
508 WeatherKitError,
509 > {
510 Ok((
511 query1.fetch(*self, location)?,
512 query2.fetch(*self, location)?,
513 query3.fetch(*self, location)?,
514 query4.fetch(*self, location)?,
515 query5.fetch(*self, location)?,
516 query6.fetch(*self, location)?,
517 ))
518 }
519
520 pub fn weather_including_many<I>(
521 &self,
522 location: &CLLocation,
523 queries: I,
524 ) -> Result<Vec<WeatherQueryResult>, WeatherKitError>
525 where
526 I: IntoIterator<Item = WeatherQuery>,
527 {
528 queries
529 .into_iter()
530 .map(|query| query.fetch(*self, location))
531 .collect()
532 }
533
534 pub fn weather_changes(
535 &self,
536 location: &CLLocation,
537 ) -> Result<Option<WeatherChanges>, WeatherKitError> {
538 let ptr = self.fetch_location_handle(
539 location,
540 ffi::changes::wk_weather_service_weather_changes,
541 "WeatherService.weather(for: including: .changes)",
542 )?;
543 WeatherChanges::option_from_owned_ptr(ptr)
544 }
545
546 pub fn historical_comparisons(
547 &self,
548 location: &CLLocation,
549 ) -> Result<Option<HistoricalComparisons>, WeatherKitError> {
550 let ptr = self.fetch_location_handle(
551 location,
552 ffi::changes::wk_weather_service_historical_comparisons,
553 "WeatherService.weather(for: including: .historicalComparisons)",
554 )?;
555 HistoricalComparisons::option_from_owned_ptr(ptr)
556 }
557
558 pub fn daily_statistics(
559 &self,
560 location: &CLLocation,
561 query: DailyWeatherStatisticsQuery,
562 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
563 self.daily_statistics_with_scope(location, query, QueryScope::None)
564 }
565
566 pub fn daily_statistics_in(
567 &self,
568 location: &CLLocation,
569 interval: DateInterval,
570 query: DailyWeatherStatisticsQuery,
571 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
572 self.daily_statistics_with_scope(location, query, QueryScope::Interval(&interval))
573 }
574
575 pub fn daily_statistics_between_days(
576 &self,
577 location: &CLLocation,
578 start_day: i64,
579 end_day: i64,
580 query: DailyWeatherStatisticsQuery,
581 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
582 self.daily_statistics_with_scope(
583 location,
584 query,
585 QueryScope::Index {
586 start: start_day,
587 end: end_day,
588 },
589 )
590 }
591
592 pub fn daily_summary(
593 &self,
594 location: &CLLocation,
595 query: DailyWeatherSummaryQuery,
596 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
597 self.daily_summary_with_scope(location, query, QueryScope::None)
598 }
599
600 pub fn daily_summary_in(
601 &self,
602 location: &CLLocation,
603 interval: DateInterval,
604 query: DailyWeatherSummaryQuery,
605 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
606 self.daily_summary_with_scope(location, query, QueryScope::Interval(&interval))
607 }
608
609 pub fn hourly_statistics(
610 &self,
611 location: &CLLocation,
612 query: HourlyWeatherStatisticsQuery,
613 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
614 self.hourly_statistics_with_scope(location, query, QueryScope::None)
615 }
616
617 pub fn hourly_statistics_in(
618 &self,
619 location: &CLLocation,
620 interval: DateInterval,
621 query: HourlyWeatherStatisticsQuery,
622 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
623 self.hourly_statistics_with_scope(location, query, QueryScope::Interval(&interval))
624 }
625
626 pub fn hourly_statistics_between_hours(
627 &self,
628 location: &CLLocation,
629 start_hour: i64,
630 end_hour: i64,
631 query: HourlyWeatherStatisticsQuery,
632 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
633 self.hourly_statistics_with_scope(
634 location,
635 query,
636 QueryScope::Index {
637 start: start_hour,
638 end: end_hour,
639 },
640 )
641 }
642
643 pub fn monthly_statistics(
644 &self,
645 location: &CLLocation,
646 query: MonthlyWeatherStatisticsQuery,
647 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
648 self.monthly_statistics_with_scope(location, query, QueryScope::None)
649 }
650
651 pub fn monthly_statistics_in(
652 &self,
653 location: &CLLocation,
654 interval: DateInterval,
655 query: MonthlyWeatherStatisticsQuery,
656 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
657 self.monthly_statistics_with_scope(location, query, QueryScope::Interval(&interval))
658 }
659
660 pub fn monthly_statistics_between_months(
661 &self,
662 location: &CLLocation,
663 start_month: i64,
664 end_month: i64,
665 query: MonthlyWeatherStatisticsQuery,
666 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
667 self.monthly_statistics_with_scope(
668 location,
669 query,
670 QueryScope::Index {
671 start: start_month,
672 end: end_month,
673 },
674 )
675 }
676
677 pub fn sun_events(&self, location: &CLLocation) -> Result<SunEvents, WeatherKitError> {
678 let forecast = self.daily_forecast(location)?;
679 forecast
680 .forecast
681 .first()
682 .map(|day| day.sun.clone())
683 .ok_or_else(|| WeatherKitError::bridge(-1, "daily forecast returned no days"))
684 }
685
686 pub fn moon_events(&self, location: &CLLocation) -> Result<MoonEvents, WeatherKitError> {
687 let forecast = self.daily_forecast(location)?;
688 forecast
689 .forecast
690 .first()
691 .map(|day| day.moon.clone())
692 .ok_or_else(|| WeatherKitError::bridge(-1, "daily forecast returned no days"))
693 }
694
695 pub fn pressure(&self, location: &CLLocation) -> Result<Pressure, WeatherKitError> {
696 Ok(self.current_weather(location)?.pressure_reading())
697 }
698
699 fn daily_statistics_with_scope(
700 &self,
701 location: &CLLocation,
702 query: DailyWeatherStatisticsQuery,
703 scope: QueryScope<'_>,
704 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
705 let ptr = self.fetch_scoped_query_handle(
706 location,
707 query.query_kind(),
708 scope,
709 ffi::statistics::wk_weather_service_daily_statistics,
710 "WeatherService.dailyStatistics",
711 )?;
712 match query {
713 DailyWeatherStatisticsQuery::Temperature => Ok(DailyWeatherStatisticsResult::Temperature(
714 DailyWeatherStatistics::<DayTemperatureStatistics>::from_owned_ptr(ptr)?,
715 )),
716 DailyWeatherStatisticsQuery::Precipitation => Ok(
717 DailyWeatherStatisticsResult::Precipitation(
718 DailyWeatherStatistics::<DayPrecipitationStatistics>::from_owned_ptr(ptr)?,
719 ),
720 ),
721 }
722 }
723
724 fn daily_summary_with_scope(
725 &self,
726 location: &CLLocation,
727 query: DailyWeatherSummaryQuery,
728 scope: QueryScope<'_>,
729 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
730 let ptr = self.fetch_scoped_query_handle(
731 location,
732 query.query_kind(),
733 scope,
734 ffi::statistics::wk_weather_service_daily_summary,
735 "WeatherService.dailySummary",
736 )?;
737 match query {
738 DailyWeatherSummaryQuery::Temperature => Ok(DailyWeatherSummaryResult::Temperature(
739 DailyWeatherSummary::<DayTemperatureSummary>::from_owned_ptr(ptr)?,
740 )),
741 DailyWeatherSummaryQuery::Precipitation => Ok(
742 DailyWeatherSummaryResult::Precipitation(
743 DailyWeatherSummary::<DayPrecipitationSummary>::from_owned_ptr(ptr)?,
744 ),
745 ),
746 }
747 }
748
749 fn hourly_statistics_with_scope(
750 &self,
751 location: &CLLocation,
752 query: HourlyWeatherStatisticsQuery,
753 scope: QueryScope<'_>,
754 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
755 let ptr = self.fetch_scoped_query_handle(
756 location,
757 query.query_kind(),
758 scope,
759 ffi::statistics::wk_weather_service_hourly_statistics,
760 "WeatherService.hourlyStatistics",
761 )?;
762 HourlyWeatherStatistics::<HourTemperatureStatistics>::from_owned_ptr(ptr)
763 }
764
765 fn monthly_statistics_with_scope(
766 &self,
767 location: &CLLocation,
768 query: MonthlyWeatherStatisticsQuery,
769 scope: QueryScope<'_>,
770 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
771 let ptr = self.fetch_scoped_query_handle(
772 location,
773 query.query_kind(),
774 scope,
775 ffi::statistics::wk_weather_service_monthly_statistics,
776 "WeatherService.monthlyStatistics",
777 )?;
778 match query {
779 MonthlyWeatherStatisticsQuery::Temperature => Ok(
780 MonthlyWeatherStatisticsResult::Temperature(
781 MonthlyWeatherStatistics::<MonthTemperatureStatistics>::from_owned_ptr(ptr)?,
782 ),
783 ),
784 MonthlyWeatherStatisticsQuery::Precipitation => Ok(
785 MonthlyWeatherStatisticsResult::Precipitation(
786 MonthlyWeatherStatistics::<MonthPrecipitationStatistics>::from_owned_ptr(ptr)?,
787 ),
788 ),
789 }
790 }
791
792 fn fetch_scoped_query_handle(
793 &self,
794 location: &CLLocation,
795 query_kind: i32,
796 scope: QueryScope<'_>,
797 call: ScopedQueryFetchFn,
798 context: &str,
799 ) -> Result<*mut c_void, WeatherKitError> {
800 location.validate()?;
801 let service = ServiceHandle::acquire(self.kind)?;
802 let mut out_handle = core::ptr::null_mut();
803 let mut out_error = core::ptr::null_mut();
804 let (scope_kind, start_seconds, end_seconds, start_index, end_index) = match scope {
805 QueryScope::None => (0, 0.0, 0.0, 0, 0),
806 QueryScope::Interval(interval) => (1, interval.start_seconds()?, interval.end_seconds()?, 0, 0),
807 QueryScope::Index { start, end } => {
808 validate_index_range(start, end, context)?;
809 (2, 0.0, 0.0, start, end)
810 }
811 };
812 let status = unsafe {
813 call(
814 service.as_ptr(),
815 location.latitude,
816 location.longitude,
817 query_kind,
818 scope_kind,
819 start_seconds,
820 end_seconds,
821 start_index,
822 end_index,
823 &mut out_handle,
824 &mut out_error,
825 )
826 };
827 if status != ffi::status::OK {
828 return Err(unsafe { error_from_status(status, out_error) });
829 }
830 if out_handle.is_null() {
831 return Err(WeatherKitError::bridge(
832 -1,
833 format!("missing handle for {context}"),
834 ));
835 }
836 Ok(out_handle)
837 }
838
839 fn fetch_service_handle(
840 &self,
841 call: ServiceFetchFn,
842 context: &str,
843 ) -> Result<*mut c_void, WeatherKitError> {
844 let service = ServiceHandle::acquire(self.kind)?;
845 let mut out_handle = core::ptr::null_mut();
846 let mut out_error = core::ptr::null_mut();
847 let status = unsafe { call(service.as_ptr(), &mut out_handle, &mut out_error) };
848 if status != ffi::status::OK {
849 return Err(unsafe { error_from_status(status, out_error) });
850 }
851 if out_handle.is_null() {
852 return Err(WeatherKitError::bridge(
853 -1,
854 format!("missing handle for {context}"),
855 ));
856 }
857 Ok(out_handle)
858 }
859
860 fn fetch_location_handle(
861 &self,
862 location: &CLLocation,
863 call: LocationFetchFn,
864 context: &str,
865 ) -> Result<*mut c_void, WeatherKitError> {
866 location.validate()?;
867 let service = ServiceHandle::acquire(self.kind)?;
868 let mut out_handle = core::ptr::null_mut();
869 let mut out_error = core::ptr::null_mut();
870 let status = unsafe {
871 call(
872 service.as_ptr(),
873 location.latitude,
874 location.longitude,
875 &mut out_handle,
876 &mut out_error,
877 )
878 };
879 if status != ffi::status::OK {
880 return Err(unsafe { error_from_status(status, out_error) });
881 }
882 if out_handle.is_null() {
883 return Err(WeatherKitError::bridge(
884 -1,
885 format!("missing handle for {context}"),
886 ));
887 }
888 Ok(out_handle)
889 }
890
891 fn fetch_interval_handle(
892 &self,
893 location: &CLLocation,
894 interval: Option<&DateInterval>,
895 call: IntervalFetchFn,
896 context: &str,
897 ) -> Result<*mut c_void, WeatherKitError> {
898 location.validate()?;
899 let service = ServiceHandle::acquire(self.kind)?;
900 let mut out_handle = core::ptr::null_mut();
901 let mut out_error = core::ptr::null_mut();
902 let (has_range, start_seconds, end_seconds) = if let Some(interval) = interval {
903 (1, interval.start_seconds()?, interval.end_seconds()?)
904 } else {
905 (0, 0.0, 0.0)
906 };
907 let status = unsafe {
908 call(
909 service.as_ptr(),
910 location.latitude,
911 location.longitude,
912 has_range,
913 start_seconds,
914 end_seconds,
915 &mut out_handle,
916 &mut out_error,
917 )
918 };
919 if status != ffi::status::OK {
920 return Err(unsafe { error_from_status(status, out_error) });
921 }
922 if out_handle.is_null() {
923 return Err(WeatherKitError::bridge(
924 -1,
925 format!("missing handle for {context}"),
926 ));
927 }
928 Ok(out_handle)
929 }
930}
931
932fn validate_index_range(start: i64, end: i64, context: &str) -> Result<(), WeatherKitError> {
933 if start > end {
934 return Err(WeatherKitError::bridge(
935 -1,
936 format!("{context} start index must not be after end index"),
937 ));
938 }
939 Ok(())
940}
941
942fn unix_seconds(time: SystemTime) -> Result<f64, WeatherKitError> {
943 let duration = time.duration_since(UNIX_EPOCH).map_err(|error| {
944 WeatherKitError::bridge(-1, format!("time {time:?} is before UNIX_EPOCH: {error}"))
945 })?;
946 Ok(duration.as_secs_f64())
947}