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