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 {
244 match kind {
245 ServiceKind::Shared => ffi::service::wk_weather_service_shared(),
246 ServiceKind::Owned => ffi::service::wk_weather_service_new(),
247 }
248 };
249 if ptr.is_null() {
250 Err(WeatherKitError::bridge(
251 -1,
252 "failed to acquire WeatherService handle",
253 ))
254 } else {
255 Ok(Self { ptr })
256 }
257 }
258
259 fn as_ptr(&self) -> *mut c_void {
260 self.ptr
261 }
262}
263
264impl Drop for ServiceHandle {
265 fn drop(&mut self) {
266 if !self.ptr.is_null() {
267 unsafe {
270 ffi::service::wk_weather_service_release(self.ptr);
271 }
272 self.ptr = core::ptr::null_mut();
273 }
274 }
275}
276
277impl WeatherService {
278 pub const fn shared() -> Self {
279 Self {
280 kind: ServiceKind::Shared,
281 }
282 }
283
284 pub const fn new() -> Self {
285 Self {
286 kind: ServiceKind::Owned,
287 }
288 }
289
290 pub(crate) const fn is_owned(self) -> bool {
291 matches!(self.kind, ServiceKind::Owned)
292 }
293
294 pub fn attribution(&self) -> Result<WeatherAttribution, WeatherKitError> {
295 let ptr = self.fetch_service_handle(
296 ffi::service::wk_weather_service_attribution,
297 "WeatherService.attribution",
298 )?;
299 WeatherAttribution::from_owned_ptr(ptr)
300 }
301
302 pub fn weather(&self, location: &CLLocation) -> Result<Weather, WeatherKitError> {
303 let ptr = self.fetch_location_handle(
304 location,
305 ffi::service::wk_weather_service_weather,
306 "WeatherService.weather(for:)",
307 )?;
308 parse_json_from_handle(
309 ptr,
310 ffi::service::wk_weather_release,
311 ffi::service::wk_weather_copy_json,
312 "weather",
313 )
314 }
315
316 pub fn current_weather(
317 &self,
318 location: &CLLocation,
319 ) -> Result<CurrentWeather, WeatherKitError> {
320 let ptr = self.fetch_location_handle(
321 location,
322 ffi::service::wk_weather_service_current_weather,
323 "WeatherService.weather(for: including: .current)",
324 )?;
325 CurrentWeather::from_owned_ptr(ptr)
326 }
327
328 pub fn hourly_forecast(
329 &self,
330 location: &CLLocation,
331 ) -> Result<HourlyForecast, WeatherKitError> {
332 let ptr = self.fetch_interval_handle(
333 location,
334 None,
335 ffi::service::wk_weather_service_hourly_forecast,
336 "WeatherService.weather(for: including: .hourly)",
337 )?;
338 HourlyForecast::from_owned_ptr(ptr)
339 }
340
341 pub fn hourly_forecast_in(
342 &self,
343 location: &CLLocation,
344 interval: DateInterval,
345 ) -> Result<HourlyForecast, WeatherKitError> {
346 let ptr = self.fetch_interval_handle(
347 location,
348 Some(&interval),
349 ffi::service::wk_weather_service_hourly_forecast,
350 "WeatherService.weather(for: including: .hourly(startDate:endDate))",
351 )?;
352 HourlyForecast::from_owned_ptr(ptr)
353 }
354
355 pub fn daily_forecast(&self, location: &CLLocation) -> Result<DailyForecast, WeatherKitError> {
356 let ptr = self.fetch_interval_handle(
357 location,
358 None,
359 ffi::service::wk_weather_service_daily_forecast,
360 "WeatherService.weather(for: including: .daily)",
361 )?;
362 DailyForecast::from_owned_ptr(ptr)
363 }
364
365 pub fn daily_forecast_in(
366 &self,
367 location: &CLLocation,
368 interval: DateInterval,
369 ) -> Result<DailyForecast, WeatherKitError> {
370 let ptr = self.fetch_interval_handle(
371 location,
372 Some(&interval),
373 ffi::service::wk_weather_service_daily_forecast,
374 "WeatherService.weather(for: including: .daily(startDate:endDate))",
375 )?;
376 DailyForecast::from_owned_ptr(ptr)
377 }
378
379 pub fn minute_forecast(
380 &self,
381 location: &CLLocation,
382 ) -> Result<Option<MinuteForecastCollection>, WeatherKitError> {
383 let ptr = self.fetch_location_handle(
384 location,
385 ffi::service::wk_weather_service_minute_forecast,
386 "WeatherService.weather(for: including: .minute)",
387 )?;
388 MinuteForecastCollection::option_from_owned_ptr(ptr)
389 }
390
391 pub fn weather_alerts(
392 &self,
393 location: &CLLocation,
394 ) -> Result<Vec<WeatherAlert>, WeatherKitError> {
395 let ptr = self.fetch_location_handle(
396 location,
397 ffi::service::wk_weather_service_weather_alerts,
398 "WeatherService.weather(for: including: .alerts)",
399 )?;
400 alerts_from_owned_ptr(ptr)
401 }
402
403 pub fn availability(
404 &self,
405 location: &CLLocation,
406 ) -> Result<WeatherAvailability, WeatherKitError> {
407 let ptr = self.fetch_location_handle(
408 location,
409 ffi::service::wk_weather_service_availability,
410 "WeatherService.weather(for: including: .availability)",
411 )?;
412 WeatherAvailability::from_owned_ptr(ptr)
413 }
414
415 pub fn weather_including(
416 &self,
417 location: &CLLocation,
418 query: WeatherQuery,
419 ) -> Result<WeatherQueryResult, WeatherKitError> {
420 query.fetch(*self, location)
421 }
422
423 pub fn weather_including2(
424 &self,
425 location: &CLLocation,
426 query1: WeatherQuery,
427 query2: WeatherQuery,
428 ) -> Result<(WeatherQueryResult, WeatherQueryResult), WeatherKitError> {
429 Ok((query1.fetch(*self, location)?, query2.fetch(*self, location)?))
430 }
431
432 pub fn weather_including3(
433 &self,
434 location: &CLLocation,
435 query1: WeatherQuery,
436 query2: WeatherQuery,
437 query3: WeatherQuery,
438 ) -> Result<(WeatherQueryResult, WeatherQueryResult, WeatherQueryResult), WeatherKitError> {
439 Ok((
440 query1.fetch(*self, location)?,
441 query2.fetch(*self, location)?,
442 query3.fetch(*self, location)?,
443 ))
444 }
445
446 pub fn weather_including4(
447 &self,
448 location: &CLLocation,
449 query1: WeatherQuery,
450 query2: WeatherQuery,
451 query3: WeatherQuery,
452 query4: WeatherQuery,
453 ) -> Result<
454 (
455 WeatherQueryResult,
456 WeatherQueryResult,
457 WeatherQueryResult,
458 WeatherQueryResult,
459 ),
460 WeatherKitError,
461 > {
462 Ok((
463 query1.fetch(*self, location)?,
464 query2.fetch(*self, location)?,
465 query3.fetch(*self, location)?,
466 query4.fetch(*self, location)?,
467 ))
468 }
469
470 pub fn weather_including5(
471 &self,
472 location: &CLLocation,
473 query1: WeatherQuery,
474 query2: WeatherQuery,
475 query3: WeatherQuery,
476 query4: WeatherQuery,
477 query5: WeatherQuery,
478 ) -> Result<
479 (
480 WeatherQueryResult,
481 WeatherQueryResult,
482 WeatherQueryResult,
483 WeatherQueryResult,
484 WeatherQueryResult,
485 ),
486 WeatherKitError,
487 > {
488 Ok((
489 query1.fetch(*self, location)?,
490 query2.fetch(*self, location)?,
491 query3.fetch(*self, location)?,
492 query4.fetch(*self, location)?,
493 query5.fetch(*self, location)?,
494 ))
495 }
496
497 #[allow(clippy::too_many_arguments)]
498 pub fn weather_including6(
499 &self,
500 location: &CLLocation,
501 query1: WeatherQuery,
502 query2: WeatherQuery,
503 query3: WeatherQuery,
504 query4: WeatherQuery,
505 query5: WeatherQuery,
506 query6: WeatherQuery,
507 ) -> Result<
508 (
509 WeatherQueryResult,
510 WeatherQueryResult,
511 WeatherQueryResult,
512 WeatherQueryResult,
513 WeatherQueryResult,
514 WeatherQueryResult,
515 ),
516 WeatherKitError,
517 > {
518 Ok((
519 query1.fetch(*self, location)?,
520 query2.fetch(*self, location)?,
521 query3.fetch(*self, location)?,
522 query4.fetch(*self, location)?,
523 query5.fetch(*self, location)?,
524 query6.fetch(*self, location)?,
525 ))
526 }
527
528 pub fn weather_including_many<I>(
529 &self,
530 location: &CLLocation,
531 queries: I,
532 ) -> Result<Vec<WeatherQueryResult>, WeatherKitError>
533 where
534 I: IntoIterator<Item = WeatherQuery>,
535 {
536 queries
537 .into_iter()
538 .map(|query| query.fetch(*self, location))
539 .collect()
540 }
541
542 pub fn weather_changes(
543 &self,
544 location: &CLLocation,
545 ) -> Result<Option<WeatherChanges>, WeatherKitError> {
546 let ptr = self.fetch_location_handle(
547 location,
548 ffi::changes::wk_weather_service_weather_changes,
549 "WeatherService.weather(for: including: .changes)",
550 )?;
551 WeatherChanges::option_from_owned_ptr(ptr)
552 }
553
554 pub fn historical_comparisons(
555 &self,
556 location: &CLLocation,
557 ) -> Result<Option<HistoricalComparisons>, WeatherKitError> {
558 let ptr = self.fetch_location_handle(
559 location,
560 ffi::changes::wk_weather_service_historical_comparisons,
561 "WeatherService.weather(for: including: .historicalComparisons)",
562 )?;
563 HistoricalComparisons::option_from_owned_ptr(ptr)
564 }
565
566 pub fn daily_statistics(
567 &self,
568 location: &CLLocation,
569 query: DailyWeatherStatisticsQuery,
570 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
571 self.daily_statistics_with_scope(location, query, QueryScope::None)
572 }
573
574 pub fn daily_statistics_in(
575 &self,
576 location: &CLLocation,
577 interval: DateInterval,
578 query: DailyWeatherStatisticsQuery,
579 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
580 self.daily_statistics_with_scope(location, query, QueryScope::Interval(&interval))
581 }
582
583 pub fn daily_statistics_between_days(
584 &self,
585 location: &CLLocation,
586 start_day: i64,
587 end_day: i64,
588 query: DailyWeatherStatisticsQuery,
589 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
590 self.daily_statistics_with_scope(
591 location,
592 query,
593 QueryScope::Index {
594 start: start_day,
595 end: end_day,
596 },
597 )
598 }
599
600 pub fn daily_summary(
601 &self,
602 location: &CLLocation,
603 query: DailyWeatherSummaryQuery,
604 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
605 self.daily_summary_with_scope(location, query, QueryScope::None)
606 }
607
608 pub fn daily_summary_in(
609 &self,
610 location: &CLLocation,
611 interval: DateInterval,
612 query: DailyWeatherSummaryQuery,
613 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
614 self.daily_summary_with_scope(location, query, QueryScope::Interval(&interval))
615 }
616
617 pub fn hourly_statistics(
618 &self,
619 location: &CLLocation,
620 query: HourlyWeatherStatisticsQuery,
621 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
622 self.hourly_statistics_with_scope(location, query, QueryScope::None)
623 }
624
625 pub fn hourly_statistics_in(
626 &self,
627 location: &CLLocation,
628 interval: DateInterval,
629 query: HourlyWeatherStatisticsQuery,
630 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
631 self.hourly_statistics_with_scope(location, query, QueryScope::Interval(&interval))
632 }
633
634 pub fn hourly_statistics_between_hours(
635 &self,
636 location: &CLLocation,
637 start_hour: i64,
638 end_hour: i64,
639 query: HourlyWeatherStatisticsQuery,
640 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
641 self.hourly_statistics_with_scope(
642 location,
643 query,
644 QueryScope::Index {
645 start: start_hour,
646 end: end_hour,
647 },
648 )
649 }
650
651 pub fn monthly_statistics(
652 &self,
653 location: &CLLocation,
654 query: MonthlyWeatherStatisticsQuery,
655 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
656 self.monthly_statistics_with_scope(location, query, QueryScope::None)
657 }
658
659 pub fn monthly_statistics_in(
660 &self,
661 location: &CLLocation,
662 interval: DateInterval,
663 query: MonthlyWeatherStatisticsQuery,
664 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
665 self.monthly_statistics_with_scope(location, query, QueryScope::Interval(&interval))
666 }
667
668 pub fn monthly_statistics_between_months(
669 &self,
670 location: &CLLocation,
671 start_month: i64,
672 end_month: i64,
673 query: MonthlyWeatherStatisticsQuery,
674 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
675 self.monthly_statistics_with_scope(
676 location,
677 query,
678 QueryScope::Index {
679 start: start_month,
680 end: end_month,
681 },
682 )
683 }
684
685 pub fn sun_events(&self, location: &CLLocation) -> Result<SunEvents, WeatherKitError> {
686 let forecast = self.daily_forecast(location)?;
687 forecast
688 .forecast
689 .first()
690 .map(|day| day.sun.clone())
691 .ok_or_else(|| WeatherKitError::bridge(-1, "daily forecast returned no days"))
692 }
693
694 pub fn moon_events(&self, location: &CLLocation) -> Result<MoonEvents, WeatherKitError> {
695 let forecast = self.daily_forecast(location)?;
696 forecast
697 .forecast
698 .first()
699 .map(|day| day.moon.clone())
700 .ok_or_else(|| WeatherKitError::bridge(-1, "daily forecast returned no days"))
701 }
702
703 pub fn pressure(&self, location: &CLLocation) -> Result<Pressure, WeatherKitError> {
704 Ok(self.current_weather(location)?.pressure_reading())
705 }
706
707 fn daily_statistics_with_scope(
708 &self,
709 location: &CLLocation,
710 query: DailyWeatherStatisticsQuery,
711 scope: QueryScope<'_>,
712 ) -> Result<DailyWeatherStatisticsResult, WeatherKitError> {
713 let ptr = self.fetch_scoped_query_handle(
714 location,
715 query.query_kind(),
716 scope,
717 ffi::statistics::wk_weather_service_daily_statistics,
718 "WeatherService.dailyStatistics",
719 )?;
720 match query {
721 DailyWeatherStatisticsQuery::Temperature => Ok(DailyWeatherStatisticsResult::Temperature(
722 DailyWeatherStatistics::<DayTemperatureStatistics>::from_owned_ptr(ptr)?,
723 )),
724 DailyWeatherStatisticsQuery::Precipitation => Ok(
725 DailyWeatherStatisticsResult::Precipitation(
726 DailyWeatherStatistics::<DayPrecipitationStatistics>::from_owned_ptr(ptr)?,
727 ),
728 ),
729 }
730 }
731
732 fn daily_summary_with_scope(
733 &self,
734 location: &CLLocation,
735 query: DailyWeatherSummaryQuery,
736 scope: QueryScope<'_>,
737 ) -> Result<DailyWeatherSummaryResult, WeatherKitError> {
738 let ptr = self.fetch_scoped_query_handle(
739 location,
740 query.query_kind(),
741 scope,
742 ffi::statistics::wk_weather_service_daily_summary,
743 "WeatherService.dailySummary",
744 )?;
745 match query {
746 DailyWeatherSummaryQuery::Temperature => Ok(DailyWeatherSummaryResult::Temperature(
747 DailyWeatherSummary::<DayTemperatureSummary>::from_owned_ptr(ptr)?,
748 )),
749 DailyWeatherSummaryQuery::Precipitation => Ok(
750 DailyWeatherSummaryResult::Precipitation(
751 DailyWeatherSummary::<DayPrecipitationSummary>::from_owned_ptr(ptr)?,
752 ),
753 ),
754 }
755 }
756
757 fn hourly_statistics_with_scope(
758 &self,
759 location: &CLLocation,
760 query: HourlyWeatherStatisticsQuery,
761 scope: QueryScope<'_>,
762 ) -> Result<HourlyWeatherStatistics<HourTemperatureStatistics>, WeatherKitError> {
763 let ptr = self.fetch_scoped_query_handle(
764 location,
765 query.query_kind(),
766 scope,
767 ffi::statistics::wk_weather_service_hourly_statistics,
768 "WeatherService.hourlyStatistics",
769 )?;
770 HourlyWeatherStatistics::<HourTemperatureStatistics>::from_owned_ptr(ptr)
771 }
772
773 fn monthly_statistics_with_scope(
774 &self,
775 location: &CLLocation,
776 query: MonthlyWeatherStatisticsQuery,
777 scope: QueryScope<'_>,
778 ) -> Result<MonthlyWeatherStatisticsResult, WeatherKitError> {
779 let ptr = self.fetch_scoped_query_handle(
780 location,
781 query.query_kind(),
782 scope,
783 ffi::statistics::wk_weather_service_monthly_statistics,
784 "WeatherService.monthlyStatistics",
785 )?;
786 match query {
787 MonthlyWeatherStatisticsQuery::Temperature => Ok(
788 MonthlyWeatherStatisticsResult::Temperature(
789 MonthlyWeatherStatistics::<MonthTemperatureStatistics>::from_owned_ptr(ptr)?,
790 ),
791 ),
792 MonthlyWeatherStatisticsQuery::Precipitation => Ok(
793 MonthlyWeatherStatisticsResult::Precipitation(
794 MonthlyWeatherStatistics::<MonthPrecipitationStatistics>::from_owned_ptr(ptr)?,
795 ),
796 ),
797 }
798 }
799
800 fn fetch_scoped_query_handle(
801 &self,
802 location: &CLLocation,
803 query_kind: i32,
804 scope: QueryScope<'_>,
805 call: ScopedQueryFetchFn,
806 context: &str,
807 ) -> Result<*mut c_void, WeatherKitError> {
808 location.validate()?;
809 let service = ServiceHandle::acquire(self.kind)?;
810 let mut out_handle = core::ptr::null_mut();
811 let mut out_error = core::ptr::null_mut();
812 let (scope_kind, start_seconds, end_seconds, start_index, end_index) = match scope {
813 QueryScope::None => (0, 0.0, 0.0, 0, 0),
814 QueryScope::Interval(interval) => (1, interval.start_seconds()?, interval.end_seconds()?, 0, 0),
815 QueryScope::Index { start, end } => {
816 validate_index_range(start, end, context)?;
817 (2, 0.0, 0.0, start, end)
818 }
819 };
820 let status = unsafe {
821 call(
825 service.as_ptr(),
826 location.latitude,
827 location.longitude,
828 query_kind,
829 scope_kind,
830 start_seconds,
831 end_seconds,
832 start_index,
833 end_index,
834 &mut out_handle,
835 &mut out_error,
836 )
837 };
838 if status != ffi::status::OK {
839 return Err(unsafe { error_from_status(status, out_error) });
842 }
843 if out_handle.is_null() {
844 return Err(WeatherKitError::bridge(
845 -1,
846 format!("missing handle for {context}"),
847 ));
848 }
849 Ok(out_handle)
850 }
851
852 fn fetch_service_handle(
853 &self,
854 call: ServiceFetchFn,
855 context: &str,
856 ) -> Result<*mut c_void, WeatherKitError> {
857 let service = ServiceHandle::acquire(self.kind)?;
858 let mut out_handle = core::ptr::null_mut();
859 let mut out_error = core::ptr::null_mut();
860 let status = unsafe { call(service.as_ptr(), &mut out_handle, &mut out_error) };
863 if status != ffi::status::OK {
864 return Err(unsafe { error_from_status(status, out_error) });
866 }
867 if out_handle.is_null() {
868 return Err(WeatherKitError::bridge(
869 -1,
870 format!("missing handle for {context}"),
871 ));
872 }
873 Ok(out_handle)
874 }
875
876 fn fetch_location_handle(
877 &self,
878 location: &CLLocation,
879 call: LocationFetchFn,
880 context: &str,
881 ) -> Result<*mut c_void, WeatherKitError> {
882 location.validate()?;
883 let service = ServiceHandle::acquire(self.kind)?;
884 let mut out_handle = core::ptr::null_mut();
885 let mut out_error = core::ptr::null_mut();
886 let status = unsafe {
889 call(
890 service.as_ptr(),
891 location.latitude,
892 location.longitude,
893 &mut out_handle,
894 &mut out_error,
895 )
896 };
897 if status != ffi::status::OK {
898 return Err(unsafe { error_from_status(status, out_error) });
900 }
901 if out_handle.is_null() {
902 return Err(WeatherKitError::bridge(
903 -1,
904 format!("missing handle for {context}"),
905 ));
906 }
907 Ok(out_handle)
908 }
909
910 fn fetch_interval_handle(
911 &self,
912 location: &CLLocation,
913 interval: Option<&DateInterval>,
914 call: IntervalFetchFn,
915 context: &str,
916 ) -> Result<*mut c_void, WeatherKitError> {
917 location.validate()?;
918 let service = ServiceHandle::acquire(self.kind)?;
919 let mut out_handle = core::ptr::null_mut();
920 let mut out_error = core::ptr::null_mut();
921 let (has_range, start_seconds, end_seconds) = if let Some(interval) = interval {
922 (1, interval.start_seconds()?, interval.end_seconds()?)
923 } else {
924 (0, 0.0, 0.0)
925 };
926 let status = unsafe {
929 call(
930 service.as_ptr(),
931 location.latitude,
932 location.longitude,
933 has_range,
934 start_seconds,
935 end_seconds,
936 &mut out_handle,
937 &mut out_error,
938 )
939 };
940 if status != ffi::status::OK {
941 return Err(unsafe { error_from_status(status, out_error) });
943 }
944 if out_handle.is_null() {
945 return Err(WeatherKitError::bridge(
946 -1,
947 format!("missing handle for {context}"),
948 ));
949 }
950 Ok(out_handle)
951 }
952}
953
954fn validate_index_range(start: i64, end: i64, context: &str) -> Result<(), WeatherKitError> {
955 if start > end {
956 return Err(WeatherKitError::bridge(
957 -1,
958 format!("{context} start index must not be after end index"),
959 ));
960 }
961 Ok(())
962}
963
964fn unix_seconds(time: SystemTime) -> Result<f64, WeatherKitError> {
965 let duration = time.duration_since(UNIX_EPOCH).map_err(|error| {
966 WeatherKitError::bridge(-1, format!("time {time:?} is before UNIX_EPOCH: {error}"))
967 })?;
968 Ok(duration.as_secs_f64())
969}