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 #[cfg(feature = "async")]
335 pub(crate) const fn is_owned(self) -> bool {
336 matches!(self.kind, ServiceKind::Owned)
337 }
338
339 pub fn attribution(&self) -> Result<WeatherAttribution, WeatherKitError> {
341 let ptr = self.fetch_service_handle(
342 ffi::service::wk_weather_service_attribution,
343 "WeatherService.attribution",
344 )?;
345 WeatherAttribution::from_owned_ptr(ptr)
346 }
347
348 pub fn weather(&self, location: &CLLocation) -> Result<Weather, WeatherKitError> {
350 let ptr = self.fetch_location_handle(
351 location,
352 ffi::service::wk_weather_service_weather,
353 "WeatherService.weather(for:)",
354 )?;
355 parse_json_from_handle(
356 ptr,
357 ffi::service::wk_weather_release,
358 ffi::service::wk_weather_copy_json,
359 "weather",
360 )
361 }
362
363 pub fn current_weather(
365 &self,
366 location: &CLLocation,
367 ) -> Result<CurrentWeather, WeatherKitError> {
368 let ptr = self.fetch_location_handle(
369 location,
370 ffi::service::wk_weather_service_current_weather,
371 "WeatherService.weather(for: including: .current)",
372 )?;
373 CurrentWeather::from_owned_ptr(ptr)
374 }
375
376 pub fn hourly_forecast(
378 &self,
379 location: &CLLocation,
380 ) -> Result<HourlyForecast, WeatherKitError> {
381 let ptr = self.fetch_interval_handle(
382 location,
383 None,
384 ffi::service::wk_weather_service_hourly_forecast,
385 "WeatherService.weather(for: including: .hourly)",
386 )?;
387 HourlyForecast::from_owned_ptr(ptr)
388 }
389
390 pub fn hourly_forecast_in(
392 &self,
393 location: &CLLocation,
394 interval: DateInterval,
395 ) -> Result<HourlyForecast, WeatherKitError> {
396 let ptr = self.fetch_interval_handle(
397 location,
398 Some(&interval),
399 ffi::service::wk_weather_service_hourly_forecast,
400 "WeatherService.weather(for: including: .hourly(startDate:endDate))",
401 )?;
402 HourlyForecast::from_owned_ptr(ptr)
403 }
404
405 pub fn daily_forecast(&self, location: &CLLocation) -> Result<DailyForecast, WeatherKitError> {
407 let ptr = self.fetch_interval_handle(
408 location,
409 None,
410 ffi::service::wk_weather_service_daily_forecast,
411 "WeatherService.weather(for: including: .daily)",
412 )?;
413 DailyForecast::from_owned_ptr(ptr)
414 }
415
416 pub fn daily_forecast_in(
418 &self,
419 location: &CLLocation,
420 interval: DateInterval,
421 ) -> Result<DailyForecast, WeatherKitError> {
422 let ptr = self.fetch_interval_handle(
423 location,
424 Some(&interval),
425 ffi::service::wk_weather_service_daily_forecast,
426 "WeatherService.weather(for: including: .daily(startDate:endDate))",
427 )?;
428 DailyForecast::from_owned_ptr(ptr)
429 }
430
431 pub fn minute_forecast(
433 &self,
434 location: &CLLocation,
435 ) -> Result<Option<MinuteForecastCollection>, WeatherKitError> {
436 let ptr = self.fetch_location_handle(
437 location,
438 ffi::service::wk_weather_service_minute_forecast,
439 "WeatherService.weather(for: including: .minute)",
440 )?;
441 MinuteForecastCollection::option_from_owned_ptr(ptr)
442 }
443
444 pub fn weather_alerts(
446 &self,
447 location: &CLLocation,
448 ) -> Result<Vec<WeatherAlert>, WeatherKitError> {
449 let ptr = self.fetch_location_handle(
450 location,
451 ffi::service::wk_weather_service_weather_alerts,
452 "WeatherService.weather(for: including: .alerts)",
453 )?;
454 alerts_from_owned_ptr(ptr)
455 }
456
457 pub fn availability(
459 &self,
460 location: &CLLocation,
461 ) -> Result<WeatherAvailability, WeatherKitError> {
462 let ptr = self.fetch_location_handle(
463 location,
464 ffi::service::wk_weather_service_availability,
465 "WeatherService.weather(for: including: .availability)",
466 )?;
467 WeatherAvailability::from_owned_ptr(ptr)
468 }
469
470 pub fn weather_including(
472 &self,
473 location: &CLLocation,
474 query: WeatherQuery,
475 ) -> Result<WeatherQueryResult, WeatherKitError> {
476 query.fetch(*self, location)
477 }
478
479 pub fn weather_including2(
481 &self,
482 location: &CLLocation,
483 query1: WeatherQuery,
484 query2: WeatherQuery,
485 ) -> Result<(WeatherQueryResult, WeatherQueryResult), WeatherKitError> {
486 Ok((
487 query1.fetch(*self, location)?,
488 query2.fetch(*self, location)?,
489 ))
490 }
491
492 pub fn weather_including3(
494 &self,
495 location: &CLLocation,
496 query1: WeatherQuery,
497 query2: WeatherQuery,
498 query3: WeatherQuery,
499 ) -> Result<(WeatherQueryResult, WeatherQueryResult, WeatherQueryResult), WeatherKitError> {
500 Ok((
501 query1.fetch(*self, location)?,
502 query2.fetch(*self, location)?,
503 query3.fetch(*self, location)?,
504 ))
505 }
506
507 pub fn weather_including4(
509 &self,
510 location: &CLLocation,
511 query1: WeatherQuery,
512 query2: WeatherQuery,
513 query3: WeatherQuery,
514 query4: WeatherQuery,
515 ) -> Result<
516 (
517 WeatherQueryResult,
518 WeatherQueryResult,
519 WeatherQueryResult,
520 WeatherQueryResult,
521 ),
522 WeatherKitError,
523 > {
524 Ok((
525 query1.fetch(*self, location)?,
526 query2.fetch(*self, location)?,
527 query3.fetch(*self, location)?,
528 query4.fetch(*self, location)?,
529 ))
530 }
531
532 pub fn weather_including5(
534 &self,
535 location: &CLLocation,
536 query1: WeatherQuery,
537 query2: WeatherQuery,
538 query3: WeatherQuery,
539 query4: WeatherQuery,
540 query5: WeatherQuery,
541 ) -> Result<
542 (
543 WeatherQueryResult,
544 WeatherQueryResult,
545 WeatherQueryResult,
546 WeatherQueryResult,
547 WeatherQueryResult,
548 ),
549 WeatherKitError,
550 > {
551 Ok((
552 query1.fetch(*self, location)?,
553 query2.fetch(*self, location)?,
554 query3.fetch(*self, location)?,
555 query4.fetch(*self, location)?,
556 query5.fetch(*self, location)?,
557 ))
558 }
559
560 #[allow(clippy::too_many_arguments)]
562 pub fn weather_including6(
563 &self,
564 location: &CLLocation,
565 query1: WeatherQuery,
566 query2: WeatherQuery,
567 query3: WeatherQuery,
568 query4: WeatherQuery,
569 query5: WeatherQuery,
570 query6: WeatherQuery,
571 ) -> Result<
572 (
573 WeatherQueryResult,
574 WeatherQueryResult,
575 WeatherQueryResult,
576 WeatherQueryResult,
577 WeatherQueryResult,
578 WeatherQueryResult,
579 ),
580 WeatherKitError,
581 > {
582 Ok((
583 query1.fetch(*self, location)?,
584 query2.fetch(*self, location)?,
585 query3.fetch(*self, location)?,
586 query4.fetch(*self, location)?,
587 query5.fetch(*self, location)?,
588 query6.fetch(*self, location)?,
589 ))
590 }
591
592 pub fn weather_including_many<I>(
594 &self,
595 location: &CLLocation,
596 queries: I,
597 ) -> Result<Vec<WeatherQueryResult>, WeatherKitError>
598 where
599 I: IntoIterator<Item = WeatherQuery>,
600 {
601 queries
602 .into_iter()
603 .map(|query| query.fetch(*self, location))
604 .collect()
605 }
606
607 pub fn weather_changes(
609 &self,
610 location: &CLLocation,
611 ) -> Result<Option<WeatherChanges>, WeatherKitError> {
612 let ptr = self.fetch_location_handle(
613 location,
614 ffi::changes::wk_weather_service_weather_changes,
615 "WeatherService.weather(for: including: .changes)",
616 )?;
617 WeatherChanges::option_from_owned_ptr(ptr)
618 }
619
620 pub fn historical_comparisons(
622 &self,
623 location: &CLLocation,
624 ) -> Result<Option<HistoricalComparisons>, WeatherKitError> {
625 let ptr = self.fetch_location_handle(
626 location,
627 ffi::changes::wk_weather_service_historical_comparisons,
628 "WeatherService.weather(for: including: .historicalComparisons)",
629 )?;
630 HistoricalComparisons::option_from_owned_ptr(ptr)
631 }
632
633 pub fn daily_statistics(
635 &self,
636 location: &CLLocation,
637 query: DailyWeatherStatisticsQuery,
638 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
639 self.daily_statistics_with_scope(location, query, QueryScope::None)
640 }
641
642 pub fn daily_statistics_in(
644 &self,
645 location: &CLLocation,
646 interval: DateInterval,
647 query: DailyWeatherStatisticsQuery,
648 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
649 self.daily_statistics_with_scope(location, query, QueryScope::Interval(&interval))
650 }
651
652 pub fn daily_statistics_between_days(
654 &self,
655 location: &CLLocation,
656 start_day: i64,
657 end_day: i64,
658 query: DailyWeatherStatisticsQuery,
659 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
660 self.daily_statistics_with_scope(
661 location,
662 query,
663 QueryScope::Index {
664 start: start_day,
665 end: end_day,
666 },
667 )
668 }
669
670 pub fn daily_summary(
672 &self,
673 location: &CLLocation,
674 query: DailyWeatherSummaryQuery,
675 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
676 self.daily_summary_with_scope(location, query, QueryScope::None)
677 }
678
679 pub fn daily_summary_in(
681 &self,
682 location: &CLLocation,
683 interval: DateInterval,
684 query: DailyWeatherSummaryQuery,
685 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
686 self.daily_summary_with_scope(location, query, QueryScope::Interval(&interval))
687 }
688
689 pub fn hourly_statistics(
691 &self,
692 location: &CLLocation,
693 query: HourlyWeatherStatisticsQuery,
694 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
695 self.hourly_statistics_with_scope(location, query, QueryScope::None)
696 }
697
698 pub fn hourly_statistics_in(
700 &self,
701 location: &CLLocation,
702 interval: DateInterval,
703 query: HourlyWeatherStatisticsQuery,
704 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
705 self.hourly_statistics_with_scope(location, query, QueryScope::Interval(&interval))
706 }
707
708 pub fn hourly_statistics_between_hours(
710 &self,
711 location: &CLLocation,
712 start_hour: i64,
713 end_hour: i64,
714 query: HourlyWeatherStatisticsQuery,
715 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
716 self.hourly_statistics_with_scope(
717 location,
718 query,
719 QueryScope::Index {
720 start: start_hour,
721 end: end_hour,
722 },
723 )
724 }
725
726 pub fn monthly_statistics(
728 &self,
729 location: &CLLocation,
730 query: MonthlyWeatherStatisticsQuery,
731 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
732 self.monthly_statistics_with_scope(location, query, QueryScope::None)
733 }
734
735 pub fn monthly_statistics_in(
737 &self,
738 location: &CLLocation,
739 interval: DateInterval,
740 query: MonthlyWeatherStatisticsQuery,
741 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
742 self.monthly_statistics_with_scope(location, query, QueryScope::Interval(&interval))
743 }
744
745 pub fn monthly_statistics_between_months(
747 &self,
748 location: &CLLocation,
749 start_month: i64,
750 end_month: i64,
751 query: MonthlyWeatherStatisticsQuery,
752 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
753 self.monthly_statistics_with_scope(
754 location,
755 query,
756 QueryScope::Index {
757 start: start_month,
758 end: end_month,
759 },
760 )
761 }
762
763 pub fn sun_events(&self, location: &CLLocation) -> Result<SunEvents, WeatherKitError> {
765 let forecast = self.daily_forecast(location)?;
766 forecast
767 .forecast
768 .first()
769 .map(|day| day.sun.clone())
770 .ok_or_else(|| WeatherKitError::bridge(-1, "daily forecast returned no days"))
771 }
772
773 pub fn moon_events(&self, location: &CLLocation) -> Result<MoonEvents, WeatherKitError> {
775 let forecast = self.daily_forecast(location)?;
776 forecast
777 .forecast
778 .first()
779 .map(|day| day.moon.clone())
780 .ok_or_else(|| WeatherKitError::bridge(-1, "daily forecast returned no days"))
781 }
782
783 pub fn pressure(&self, location: &CLLocation) -> Result<Pressure, WeatherKitError> {
785 Ok(self.current_weather(location)?.pressure_reading())
786 }
787
788 fn daily_statistics_with_scope(
789 &self,
790 location: &CLLocation,
791 query: DailyWeatherStatisticsQuery,
792 scope: QueryScope<'_>,
793 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
794 let ptr = self.fetch_scoped_query_handle(
795 location,
796 query.query_kind(),
797 scope,
798 ffi::statistics::wk_weather_service_daily_statistics,
799 "WeatherService.dailyStatistics",
800 )?;
801 match query {
802 DailyWeatherStatisticsQuery::Temperature => {
803 Ok(DailyWeatherStatisticsResult::Temperature(
804 DailyWeatherStatistics::<DayTemperatureStatistics>::from_owned_ptr(ptr)?,
805 ))
806 }
807 DailyWeatherStatisticsQuery::Precipitation => {
808 Ok(DailyWeatherStatisticsResult::Precipitation(
809 DailyWeatherStatistics::<DayPrecipitationStatistics>::from_owned_ptr(ptr)?,
810 ))
811 }
812 }
813 }
814
815 fn daily_summary_with_scope(
816 &self,
817 location: &CLLocation,
818 query: DailyWeatherSummaryQuery,
819 scope: QueryScope<'_>,
820 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
821 let ptr = self.fetch_scoped_query_handle(
822 location,
823 query.query_kind(),
824 scope,
825 ffi::statistics::wk_weather_service_daily_summary,
826 "WeatherService.dailySummary",
827 )?;
828 match query {
829 DailyWeatherSummaryQuery::Temperature => Ok(DailyWeatherSummaryResult::Temperature(
830 DailyWeatherSummary::<DayTemperatureSummary>::from_owned_ptr(ptr)?,
831 )),
832 DailyWeatherSummaryQuery::Precipitation => {
833 Ok(DailyWeatherSummaryResult::Precipitation(
834 DailyWeatherSummary::<DayPrecipitationSummary>::from_owned_ptr(ptr)?,
835 ))
836 }
837 }
838 }
839
840 fn hourly_statistics_with_scope(
841 &self,
842 location: &CLLocation,
843 query: HourlyWeatherStatisticsQuery,
844 scope: QueryScope<'_>,
845 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
846 let ptr = self.fetch_scoped_query_handle(
847 location,
848 query.query_kind(),
849 scope,
850 ffi::statistics::wk_weather_service_hourly_statistics,
851 "WeatherService.hourlyStatistics",
852 )?;
853 HourlyWeatherStatistics::<HourTemperatureStatistics>::from_owned_ptr(ptr)
854 }
855
856 fn monthly_statistics_with_scope(
857 &self,
858 location: &CLLocation,
859 query: MonthlyWeatherStatisticsQuery,
860 scope: QueryScope<'_>,
861 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
862 let ptr = self.fetch_scoped_query_handle(
863 location,
864 query.query_kind(),
865 scope,
866 ffi::statistics::wk_weather_service_monthly_statistics,
867 "WeatherService.monthlyStatistics",
868 )?;
869 match query {
870 MonthlyWeatherStatisticsQuery::Temperature => {
871 Ok(MonthlyWeatherStatisticsResult::Temperature(
872 MonthlyWeatherStatistics::<MonthTemperatureStatistics>::from_owned_ptr(ptr)?,
873 ))
874 }
875 MonthlyWeatherStatisticsQuery::Precipitation => {
876 Ok(MonthlyWeatherStatisticsResult::Precipitation(
877 MonthlyWeatherStatistics::<MonthPrecipitationStatistics>::from_owned_ptr(ptr)?,
878 ))
879 }
880 }
881 }
882
883 fn fetch_scoped_query_handle(
884 &self,
885 location: &CLLocation,
886 query_kind: i32,
887 scope: QueryScope<'_>,
888 call: ScopedQueryFetchFn,
889 context: &str,
890 ) -> Result<*mut c_void, WeatherKitError> {
891 location.validate()?;
892 let service = ServiceHandle::acquire(self.kind)?;
893 let mut out_handle = core::ptr::null_mut();
894 let mut out_error = core::ptr::null_mut();
895 let (scope_kind, start_seconds, end_seconds, start_index, end_index) = match scope {
896 QueryScope::None => (0, 0.0, 0.0, 0, 0),
897 QueryScope::Interval(interval) => {
898 (1, interval.start_seconds()?, interval.end_seconds()?, 0, 0)
899 }
900 QueryScope::Index { start, end } => {
901 validate_index_range(start, end, context)?;
902 (2, 0.0, 0.0, start, end)
903 }
904 };
905 let status = unsafe {
906 call(
910 service.as_ptr(),
911 location.latitude,
912 location.longitude,
913 query_kind,
914 scope_kind,
915 start_seconds,
916 end_seconds,
917 start_index,
918 end_index,
919 &mut out_handle,
920 &mut out_error,
921 )
922 };
923 if status != ffi::status::OK {
924 return Err(unsafe { error_from_status(status, out_error) });
927 }
928 if out_handle.is_null() {
929 return Err(WeatherKitError::bridge(
930 -1,
931 format!("missing handle for {context}"),
932 ));
933 }
934 Ok(out_handle)
935 }
936
937 fn fetch_service_handle(
938 &self,
939 call: ServiceFetchFn,
940 context: &str,
941 ) -> Result<*mut c_void, WeatherKitError> {
942 let service = ServiceHandle::acquire(self.kind)?;
943 let mut out_handle = core::ptr::null_mut();
944 let mut out_error = core::ptr::null_mut();
945 let status = unsafe { call(service.as_ptr(), &mut out_handle, &mut out_error) };
948 if status != ffi::status::OK {
949 return Err(unsafe { error_from_status(status, out_error) });
951 }
952 if out_handle.is_null() {
953 return Err(WeatherKitError::bridge(
954 -1,
955 format!("missing handle for {context}"),
956 ));
957 }
958 Ok(out_handle)
959 }
960
961 fn fetch_location_handle(
962 &self,
963 location: &CLLocation,
964 call: LocationFetchFn,
965 context: &str,
966 ) -> Result<*mut c_void, WeatherKitError> {
967 location.validate()?;
968 let service = ServiceHandle::acquire(self.kind)?;
969 let mut out_handle = core::ptr::null_mut();
970 let mut out_error = core::ptr::null_mut();
971 let status = unsafe {
974 call(
975 service.as_ptr(),
976 location.latitude,
977 location.longitude,
978 &mut out_handle,
979 &mut out_error,
980 )
981 };
982 if status != ffi::status::OK {
983 return Err(unsafe { error_from_status(status, out_error) });
985 }
986 if out_handle.is_null() {
987 return Err(WeatherKitError::bridge(
988 -1,
989 format!("missing handle for {context}"),
990 ));
991 }
992 Ok(out_handle)
993 }
994
995 fn fetch_interval_handle(
996 &self,
997 location: &CLLocation,
998 interval: Option<&DateInterval>,
999 call: IntervalFetchFn,
1000 context: &str,
1001 ) -> Result<*mut c_void, WeatherKitError> {
1002 location.validate()?;
1003 let service = ServiceHandle::acquire(self.kind)?;
1004 let mut out_handle = core::ptr::null_mut();
1005 let mut out_error = core::ptr::null_mut();
1006 let (has_range, start_seconds, end_seconds) = if let Some(interval) = interval {
1007 (1, interval.start_seconds()?, interval.end_seconds()?)
1008 } else {
1009 (0, 0.0, 0.0)
1010 };
1011 let status = unsafe {
1014 call(
1015 service.as_ptr(),
1016 location.latitude,
1017 location.longitude,
1018 has_range,
1019 start_seconds,
1020 end_seconds,
1021 &mut out_handle,
1022 &mut out_error,
1023 )
1024 };
1025 if status != ffi::status::OK {
1026 return Err(unsafe { error_from_status(status, out_error) });
1028 }
1029 if out_handle.is_null() {
1030 return Err(WeatherKitError::bridge(
1031 -1,
1032 format!("missing handle for {context}"),
1033 ));
1034 }
1035 Ok(out_handle)
1036 }
1037}
1038
1039fn validate_index_range(start: i64, end: i64, context: &str) -> Result<(), WeatherKitError> {
1040 if start > end {
1041 return Err(WeatherKitError::bridge(
1042 -1,
1043 format!("{context} start index must not be after end index"),
1044 ));
1045 }
1046 Ok(())
1047}
1048
1049fn unix_seconds(time: SystemTime) -> Result<f64, WeatherKitError> {
1050 let duration = time.duration_since(UNIX_EPOCH).map_err(|error| {
1051 WeatherKitError::bridge(-1, format!("time {time:?} is before UNIX_EPOCH: {error}"))
1052 })?;
1053 Ok(duration.as_secs_f64())
1054}