Skip to main content

weatherkit/
async_api.rs

1//! Async API for `WeatherKit` — executor-agnostic `Future` wrappers.
2//!
3//! Enabled with the `async` Cargo feature.  Every Apple `async throws` surface
4//! on [`WeatherService`] gets a corresponding Future
5//! newtype here.  The pattern follows the doom-fish gold standard from
6//! `screencapturekit-rs`: a `@_cdecl` Swift thunk launches a Swift `Task`,
7//! fires a C callback on completion, and the Rust side wraps the call in an
8//! [`AsyncCompletionFuture`] via [`doom_fish_utils::completion::AsyncCompletion`].
9//!
10//! ## Available Future types
11//!
12//! | Future type | Swift API |
13//! |-------------|-----------|
14//! | [`WeatherFuture`] | `WeatherService.weather(for:)` |
15//! | [`CurrentWeatherFuture`] | `WeatherService.weather(for: including: .current)` |
16//! | [`HourlyForecastFuture`] | `WeatherService.weather(for: including: .hourly)` |
17//! | [`DailyForecastFuture`] | `WeatherService.weather(for: including: .daily)` |
18//! | [`MinuteForecastFuture`] | `WeatherService.weather(for: including: .minute)` |
19//! | [`WeatherAlertsFuture`] | `WeatherService.weather(for: including: .alerts)` |
20//! | [`AvailabilityFuture`] | `WeatherService.weather(for: including: .availability)` |
21//! | [`AttributionFuture`] | `WeatherService.attribution` |
22//! | [`WeatherChangesFuture`] | `WeatherService.weather(for: including: .changes)` (macOS 15+) |
23//! | [`HistoricalComparisonsFuture`] | `WeatherService.weather(for: including: .historicalComparisons)` (macOS 15+) |
24//!
25//! ## Tier-2 note (streams / KVO)
26//!
27//! [`WeatherChange`](crate::WeatherChange) / `WeatherUpdate` multi-fire
28//! observation and server-sent-event style APIs are deferred to **Tier 2**
29//! (async `Stream` pattern).
30//!
31//! ## Example
32//!
33//! ```rust,no_run
34//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
35//! use weatherkit::async_api::AsyncWeatherService;
36//! use weatherkit::service::CLLocation;
37//!
38//! pollster::block_on(async {
39//!     let svc = AsyncWeatherService::shared();
40//!     let loc = CLLocation::new(37.3382, -121.8863);
41//!     let weather = svc.weather(&loc).await
42//!         .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
43//!     println!("{:?}", weather.current_weather.condition);
44//!     Ok::<_, Box<dyn std::error::Error>>(())
45//! })
46//! # }
47//! ```
48
49use std::ffi::c_void;
50use std::future::Future;
51use std::pin::Pin;
52use std::task::{Context, Poll};
53
54use doom_fish_utils::completion::{error_from_cstr, AsyncCompletion, AsyncCompletionFuture};
55use doom_fish_utils::panic_safe::catch_user_panic;
56
57use crate::availability_kind::WeatherAvailability;
58use crate::changes::{HistoricalComparisons, WeatherChanges};
59use crate::current_weather::CurrentWeather;
60use crate::daily_forecast::DailyForecast;
61use crate::error::WeatherKitError;
62use crate::error::ErrorPayload;
63use crate::ffi;
64use crate::hourly_forecast::HourlyForecast;
65use crate::minute_forecast::MinuteForecastCollection;
66use crate::private::parse_json_from_handle;
67use crate::service::{CLLocation, DateInterval, Weather, WeatherService};
68use crate::weather_alert::{alerts_from_owned_ptr, WeatherAlert};
69use crate::weather_attribution::WeatherAttribution;
70
71// ============================================================================
72// Helpers
73// ============================================================================
74
75fn unix_seconds(t: std::time::SystemTime) -> f64 {
76    t.duration_since(std::time::UNIX_EPOCH)
77        .map_or(0.0, |d| d.as_secs_f64())
78}
79
80/// Parse an error string that may be a JSON `ErrorPayload` (as emitted by the
81/// async Swift thunks via `wkAsyncErrorCString`).  Falls back to a plain
82/// bridge error if the string is not valid JSON.
83fn parse_async_error(msg: String) -> WeatherKitError {
84    serde_json::from_str::<ErrorPayload>(&msg)
85        .map_or_else(|_| WeatherKitError::bridge(-1, msg), WeatherKitError::from_payload)
86}
87
88/// Acquire a raw service handle from a [`WeatherService`] value.
89///
90/// Returns `(ptr, is_owned)`.  When `is_owned = true` the caller must release
91/// the handle with [`ffi::service::wk_weather_service_release`] after use.
92fn acquire_service_ptr(svc: WeatherService) -> Result<*mut c_void, WeatherKitError> {
93    // SAFETY: both FFI functions return a freshly retained opaque pointer (or
94    // null on failure); no invariants are required on the Rust side beyond the
95    // null-check that follows immediately.
96    let ptr = unsafe {
97        if svc.is_owned() {
98            ffi::service::wk_weather_service_new()
99        } else {
100            ffi::service::wk_weather_service_shared()
101        }
102    };
103    if ptr.is_null() {
104        Err(WeatherKitError::bridge(
105            -1,
106            "failed to acquire WeatherService handle",
107        ))
108    } else {
109        Ok(ptr)
110    }
111}
112
113// ============================================================================
114// Macro: generate Future newtype + callback for a simple "location" query
115// ============================================================================
116
117/// Internal callback type matching the Swift side.
118type RawCb = unsafe extern "C" fn(*const c_void, *const i8, *mut c_void);
119
120// ============================================================================
121// WeatherFuture — WeatherService.weather(for:)
122// ============================================================================
123
124extern "C" fn weather_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
125    catch_user_panic("weather_cb", || {
126        if !error.is_null() {
127            let msg = unsafe { error_from_cstr(error) };
128            unsafe { AsyncCompletion::<Weather>::complete_err(ctx, msg) };
129        } else if !result.is_null() {
130            let ptr = result.cast_mut();
131            match parse_json_from_handle::<Weather>(
132                ptr,
133                ffi::service::wk_weather_release,
134                ffi::service::wk_weather_copy_json,
135                "weather",
136            ) {
137                Ok(w) => unsafe { AsyncCompletion::complete_ok(ctx, w) },
138                Err(e) => unsafe { AsyncCompletion::<Weather>::complete_err(ctx, e.to_string()) },
139            }
140        } else {
141            unsafe { AsyncCompletion::<Weather>::complete_err(ctx, "weather_cb: null result and null error".into()) };
142        }
143    });
144}
145
146/// Future returned by [`AsyncWeatherService::weather`].
147pub struct WeatherFuture {
148    inner: AsyncCompletionFuture<Weather>,
149    _service_ptr: ServicePtrGuard,
150}
151
152impl Future for WeatherFuture {
153    type Output = Result<Weather, WeatherKitError>;
154
155    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
156        Pin::new(&mut self.inner)
157            .poll(cx)
158            .map(|r| r.map_err(parse_async_error))
159    }
160}
161
162// ============================================================================
163// CurrentWeatherFuture
164// ============================================================================
165
166extern "C" fn current_weather_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
167    catch_user_panic("current_weather_cb", || {
168        if !error.is_null() {
169            let msg = unsafe { error_from_cstr(error) };
170            unsafe { AsyncCompletion::<CurrentWeather>::complete_err(ctx, msg) };
171        } else if !result.is_null() {
172            let ptr = result.cast_mut();
173            match CurrentWeather::from_owned_ptr(ptr) {
174                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
175                Err(e) => unsafe { AsyncCompletion::<CurrentWeather>::complete_err(ctx, e.to_string()) },
176            }
177        } else {
178            unsafe { AsyncCompletion::<CurrentWeather>::complete_err(ctx, "current_weather_cb: null result and null error".into()) };
179        }
180    });
181}
182
183/// Future returned by [`AsyncWeatherService::current_weather`].
184pub struct CurrentWeatherFuture {
185    inner: AsyncCompletionFuture<CurrentWeather>,
186    _service_ptr: ServicePtrGuard,
187}
188
189impl Future for CurrentWeatherFuture {
190    type Output = Result<CurrentWeather, WeatherKitError>;
191
192    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
193        Pin::new(&mut self.inner)
194            .poll(cx)
195            .map(|r| r.map_err(parse_async_error))
196    }
197}
198
199// ============================================================================
200// HourlyForecastFuture
201// ============================================================================
202
203extern "C" fn hourly_forecast_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
204    catch_user_panic("hourly_forecast_cb", || {
205        if !error.is_null() {
206            let msg = unsafe { error_from_cstr(error) };
207            unsafe { AsyncCompletion::<HourlyForecast>::complete_err(ctx, msg) };
208        } else if !result.is_null() {
209            let ptr = result.cast_mut();
210            match HourlyForecast::from_owned_ptr(ptr) {
211                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
212                Err(e) => unsafe { AsyncCompletion::<HourlyForecast>::complete_err(ctx, e.to_string()) },
213            }
214        } else {
215            unsafe { AsyncCompletion::<HourlyForecast>::complete_err(ctx, "hourly_forecast_cb: null result and null error".into()) };
216        }
217    });
218}
219
220/// Future returned by [`AsyncWeatherService::hourly_forecast`] and
221/// [`AsyncWeatherService::hourly_forecast_in`].
222pub struct HourlyForecastFuture {
223    inner: AsyncCompletionFuture<HourlyForecast>,
224    _service_ptr: ServicePtrGuard,
225}
226
227impl Future for HourlyForecastFuture {
228    type Output = Result<HourlyForecast, WeatherKitError>;
229
230    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
231        Pin::new(&mut self.inner)
232            .poll(cx)
233            .map(|r| r.map_err(parse_async_error))
234    }
235}
236
237// ============================================================================
238// DailyForecastFuture
239// ============================================================================
240
241extern "C" fn daily_forecast_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
242    catch_user_panic("daily_forecast_cb", || {
243        if !error.is_null() {
244            let msg = unsafe { error_from_cstr(error) };
245            unsafe { AsyncCompletion::<DailyForecast>::complete_err(ctx, msg) };
246        } else if !result.is_null() {
247            let ptr = result.cast_mut();
248            match DailyForecast::from_owned_ptr(ptr) {
249                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
250                Err(e) => unsafe { AsyncCompletion::<DailyForecast>::complete_err(ctx, e.to_string()) },
251            }
252        } else {
253            unsafe { AsyncCompletion::<DailyForecast>::complete_err(ctx, "daily_forecast_cb: null result and null error".into()) };
254        }
255    });
256}
257
258/// Future returned by [`AsyncWeatherService::daily_forecast`] and
259/// [`AsyncWeatherService::daily_forecast_in`].
260pub struct DailyForecastFuture {
261    inner: AsyncCompletionFuture<DailyForecast>,
262    _service_ptr: ServicePtrGuard,
263}
264
265impl Future for DailyForecastFuture {
266    type Output = Result<DailyForecast, WeatherKitError>;
267
268    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
269        Pin::new(&mut self.inner)
270            .poll(cx)
271            .map(|r| r.map_err(parse_async_error))
272    }
273}
274
275// ============================================================================
276// MinuteForecastFuture
277// ============================================================================
278
279extern "C" fn minute_forecast_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
280    catch_user_panic("minute_forecast_cb", || {
281        if !error.is_null() {
282            let msg = unsafe { error_from_cstr(error) };
283            unsafe { AsyncCompletion::<Option<MinuteForecastCollection>>::complete_err(ctx, msg) };
284        } else if !result.is_null() {
285            let ptr = result.cast_mut();
286            match MinuteForecastCollection::option_from_owned_ptr(ptr) {
287                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
288                Err(e) => unsafe { AsyncCompletion::<Option<MinuteForecastCollection>>::complete_err(ctx, e.to_string()) },
289            }
290        } else {
291            unsafe { AsyncCompletion::<Option<MinuteForecastCollection>>::complete_err(ctx, "minute_forecast_cb: null result and null error".into()) };
292        }
293    });
294}
295
296/// Future returned by [`AsyncWeatherService::minute_forecast`].
297pub struct MinuteForecastFuture {
298    inner: AsyncCompletionFuture<Option<MinuteForecastCollection>>,
299    _service_ptr: ServicePtrGuard,
300}
301
302impl Future for MinuteForecastFuture {
303    type Output = Result<Option<MinuteForecastCollection>, WeatherKitError>;
304
305    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
306        Pin::new(&mut self.inner)
307            .poll(cx)
308            .map(|r| r.map_err(parse_async_error))
309    }
310}
311
312// ============================================================================
313// WeatherAlertsFuture
314// ============================================================================
315
316extern "C" fn weather_alerts_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
317    catch_user_panic("weather_alerts_cb", || {
318        if !error.is_null() {
319            let msg = unsafe { error_from_cstr(error) };
320            unsafe { AsyncCompletion::<Vec<WeatherAlert>>::complete_err(ctx, msg) };
321        } else if !result.is_null() {
322            let ptr = result.cast_mut();
323            match alerts_from_owned_ptr(ptr) {
324                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
325                Err(e) => unsafe { AsyncCompletion::<Vec<WeatherAlert>>::complete_err(ctx, e.to_string()) },
326            }
327        } else {
328            unsafe { AsyncCompletion::<Vec<WeatherAlert>>::complete_err(ctx, "weather_alerts_cb: null result and null error".into()) };
329        }
330    });
331}
332
333/// Future returned by [`AsyncWeatherService::weather_alerts`].
334pub struct WeatherAlertsFuture {
335    inner: AsyncCompletionFuture<Vec<WeatherAlert>>,
336    _service_ptr: ServicePtrGuard,
337}
338
339impl Future for WeatherAlertsFuture {
340    type Output = Result<Vec<WeatherAlert>, WeatherKitError>;
341
342    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
343        Pin::new(&mut self.inner)
344            .poll(cx)
345            .map(|r| r.map_err(parse_async_error))
346    }
347}
348
349// ============================================================================
350// AvailabilityFuture
351// ============================================================================
352
353extern "C" fn availability_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
354    catch_user_panic("availability_cb", || {
355        if !error.is_null() {
356            let msg = unsafe { error_from_cstr(error) };
357            unsafe { AsyncCompletion::<WeatherAvailability>::complete_err(ctx, msg) };
358        } else if !result.is_null() {
359            let ptr = result.cast_mut();
360            match WeatherAvailability::from_owned_ptr(ptr) {
361                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
362                Err(e) => unsafe { AsyncCompletion::<WeatherAvailability>::complete_err(ctx, e.to_string()) },
363            }
364        } else {
365            unsafe { AsyncCompletion::<WeatherAvailability>::complete_err(ctx, "availability_cb: null result and null error".into()) };
366        }
367    });
368}
369
370/// Future returned by [`AsyncWeatherService::availability`].
371pub struct AvailabilityFuture {
372    inner: AsyncCompletionFuture<WeatherAvailability>,
373    _service_ptr: ServicePtrGuard,
374}
375
376impl Future for AvailabilityFuture {
377    type Output = Result<WeatherAvailability, WeatherKitError>;
378
379    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
380        Pin::new(&mut self.inner)
381            .poll(cx)
382            .map(|r| r.map_err(parse_async_error))
383    }
384}
385
386// ============================================================================
387// AttributionFuture
388// ============================================================================
389
390extern "C" fn attribution_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
391    catch_user_panic("attribution_cb", || {
392        if !error.is_null() {
393            let msg = unsafe { error_from_cstr(error) };
394            unsafe { AsyncCompletion::<WeatherAttribution>::complete_err(ctx, msg) };
395        } else if !result.is_null() {
396            let ptr = result.cast_mut();
397            match WeatherAttribution::from_owned_ptr(ptr) {
398                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
399                Err(e) => unsafe { AsyncCompletion::<WeatherAttribution>::complete_err(ctx, e.to_string()) },
400            }
401        } else {
402            unsafe { AsyncCompletion::<WeatherAttribution>::complete_err(ctx, "attribution_cb: null result and null error".into()) };
403        }
404    });
405}
406
407/// Future returned by [`AsyncWeatherService::attribution`].
408pub struct AttributionFuture {
409    inner: AsyncCompletionFuture<WeatherAttribution>,
410    _service_ptr: ServicePtrGuard,
411}
412
413impl Future for AttributionFuture {
414    type Output = Result<WeatherAttribution, WeatherKitError>;
415
416    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
417        Pin::new(&mut self.inner)
418            .poll(cx)
419            .map(|r| r.map_err(parse_async_error))
420    }
421}
422
423// ============================================================================
424// WeatherChangesFuture — macOS 15+, returns WKBox<String> (JSON)
425// ============================================================================
426
427extern "C" fn weather_changes_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
428    catch_user_panic("weather_changes_cb", || {
429        if !error.is_null() {
430            let msg = unsafe { error_from_cstr(error) };
431            unsafe { AsyncCompletion::<Option<WeatherChanges>>::complete_err(ctx, msg) };
432        } else if !result.is_null() {
433            let ptr = result.cast_mut();
434            match WeatherChanges::option_from_owned_ptr(ptr) {
435                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
436                Err(e) => unsafe { AsyncCompletion::<Option<WeatherChanges>>::complete_err(ctx, e.to_string()) },
437            }
438        } else {
439            unsafe { AsyncCompletion::<Option<WeatherChanges>>::complete_err(ctx, "weather_changes_cb: null result and null error".into()) };
440        }
441    });
442}
443
444/// Future returned by [`AsyncWeatherService::weather_changes`].
445///
446/// Requires macOS 15.0+.  Returns an error on older OS versions.
447pub struct WeatherChangesFuture {
448    inner: AsyncCompletionFuture<Option<WeatherChanges>>,
449    _service_ptr: ServicePtrGuard,
450}
451
452impl Future for WeatherChangesFuture {
453    type Output = Result<Option<WeatherChanges>, WeatherKitError>;
454
455    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
456        Pin::new(&mut self.inner)
457            .poll(cx)
458            .map(|r| r.map_err(parse_async_error))
459    }
460}
461
462// ============================================================================
463// HistoricalComparisonsFuture — macOS 15+
464// ============================================================================
465
466extern "C" fn historical_comparisons_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
467    catch_user_panic("historical_comparisons_cb", || {
468        if !error.is_null() {
469            let msg = unsafe { error_from_cstr(error) };
470            unsafe { AsyncCompletion::<Option<HistoricalComparisons>>::complete_err(ctx, msg) };
471        } else if !result.is_null() {
472            let ptr = result.cast_mut();
473            match HistoricalComparisons::option_from_owned_ptr(ptr) {
474                Ok(v) => unsafe { AsyncCompletion::complete_ok(ctx, v) },
475                Err(e) => unsafe { AsyncCompletion::<Option<HistoricalComparisons>>::complete_err(ctx, e.to_string()) },
476            }
477        } else {
478            unsafe { AsyncCompletion::<Option<HistoricalComparisons>>::complete_err(ctx, "historical_comparisons_cb: null result and null error".into()) };
479        }
480    });
481}
482
483/// Future returned by [`AsyncWeatherService::historical_comparisons`].
484///
485/// Requires macOS 15.0+.  Returns an error on older OS versions.
486pub struct HistoricalComparisonsFuture {
487    inner: AsyncCompletionFuture<Option<HistoricalComparisons>>,
488    _service_ptr: ServicePtrGuard,
489}
490
491impl Future for HistoricalComparisonsFuture {
492    type Output = Result<Option<HistoricalComparisons>, WeatherKitError>;
493
494    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
495        Pin::new(&mut self.inner)
496            .poll(cx)
497            .map(|r| r.map_err(parse_async_error))
498    }
499}
500
501// ============================================================================
502// ServicePtrGuard — RAII wrapper that releases the service handle on drop
503// ============================================================================
504
505struct ServicePtrGuard(*mut c_void);
506
507// SAFETY: the handle is an opaque pointer to a Swift ARC object; sending it
508// across threads is safe because Swift's ARC is thread-safe and the pointer
509// is valid until explicitly released.
510unsafe impl Send for ServicePtrGuard {}
511unsafe impl Sync for ServicePtrGuard {}
512
513impl Drop for ServicePtrGuard {
514    fn drop(&mut self) {
515        if !self.0.is_null() {
516            unsafe { ffi::service::wk_weather_service_release(self.0) };
517            self.0 = std::ptr::null_mut();
518        }
519    }
520}
521
522// ============================================================================
523// AsyncWeatherService — the public entry point
524// ============================================================================
525
526/// Async wrapper around [`WeatherService`].
527///
528/// Create one with [`AsyncWeatherService::shared()`] or
529/// [`AsyncWeatherService::new()`], then call any of its methods to get a
530/// `Future`.  The futures are **executor-agnostic** — use `pollster`, Tokio,
531/// async-std, or any other runtime.
532///
533/// # Example
534///
535/// ```rust,no_run
536/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
537/// use weatherkit::async_api::AsyncWeatherService;
538/// use weatherkit::service::CLLocation;
539///
540/// pollster::block_on(async {
541///     let svc = AsyncWeatherService::shared();
542///     let sf = CLLocation::new(37.7749, -122.4194);
543///     let weather = svc.weather(&sf).await
544///         .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
545///     println!("Condition: {:?}", weather.current_weather.condition);
546///     Ok::<_, Box<dyn std::error::Error>>(())
547/// })
548/// # }
549/// ```
550#[derive(Debug, Clone, Copy)]
551pub struct AsyncWeatherService {
552    inner: WeatherService,
553}
554
555impl AsyncWeatherService {
556    /// Use the shared `WeatherService` singleton.
557    pub const fn shared() -> Self {
558        Self { inner: WeatherService::shared() }
559    }
560
561    /// Create a new `WeatherService` instance.
562    pub const fn new() -> Self {
563        Self { inner: WeatherService::new() }
564    }
565
566    // -----------------------------------------------------------------------
567    // Private helper: acquire a retained service handle to pass to Swift.
568    // The handle is wrapped in ServicePtrGuard and released when the Future
569    // (and its associated guard) is dropped.
570    // -----------------------------------------------------------------------
571
572    fn service_ptr(self) -> Result<*mut c_void, WeatherKitError> {
573        acquire_service_ptr(self.inner)
574    }
575
576    // -----------------------------------------------------------------------
577    // Public async API
578    // -----------------------------------------------------------------------
579
580    /// Fetch the full [`Weather`] bundle.
581    ///
582    /// Wraps `WeatherService.weather(for:) async throws`.
583    ///
584    /// # Errors
585    /// Returns a [`WeatherKitError`] if the app lacks a valid WeatherKit
586    /// entitlement or if the network request fails.
587    pub fn weather(&self, location: &CLLocation) -> WeatherFuture {
588        let ptr = match self.service_ptr() {
589            Ok(p) => p,
590            Err(e) => {
591                let (future, ctx) = AsyncCompletion::create();
592                unsafe { AsyncCompletion::<Weather>::complete_err(ctx, e.to_string()) };
593                return WeatherFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
594            }
595        };
596        let (future, ctx) = AsyncCompletion::create();
597        unsafe {
598            ffi::async_ffi::wk_weather_service_weather_async(
599                ptr,
600                location.latitude,
601                location.longitude,
602                weather_cb as RawCb,
603                ctx,
604            );
605        }
606        WeatherFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
607    }
608
609    /// Fetch only the current conditions.
610    ///
611    /// Wraps `WeatherService.weather(for: including: .current) async throws`.
612    pub fn current_weather(&self, location: &CLLocation) -> CurrentWeatherFuture {
613        let ptr = match self.service_ptr() {
614            Ok(p) => p,
615            Err(e) => {
616                let (future, ctx) = AsyncCompletion::create();
617                unsafe { AsyncCompletion::<CurrentWeather>::complete_err(ctx, e.to_string()) };
618                return CurrentWeatherFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
619            }
620        };
621        let (future, ctx) = AsyncCompletion::create();
622        unsafe {
623            ffi::async_ffi::wk_weather_service_current_weather_async(
624                ptr,
625                location.latitude,
626                location.longitude,
627                current_weather_cb as RawCb,
628                ctx,
629            );
630        }
631        CurrentWeatherFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
632    }
633
634    /// Fetch the hourly forecast.
635    ///
636    /// Wraps `WeatherService.weather(for: including: .hourly) async throws`.
637    pub fn hourly_forecast(&self, location: &CLLocation) -> HourlyForecastFuture {
638        self.hourly_forecast_impl(location, None)
639    }
640
641    /// Fetch the hourly forecast for a specific date interval.
642    ///
643    /// Wraps `WeatherService.weather(for: including: .hourly(startDate:endDate:)) async throws`.
644    pub fn hourly_forecast_in(
645        &self,
646        location: &CLLocation,
647        interval: DateInterval,
648    ) -> HourlyForecastFuture {
649        self.hourly_forecast_impl(location, Some(interval))
650    }
651
652    fn hourly_forecast_impl(
653        &self,
654        location: &CLLocation,
655        interval: Option<DateInterval>,
656    ) -> HourlyForecastFuture {
657        let ptr = match self.service_ptr() {
658            Ok(p) => p,
659            Err(e) => {
660                let (future, ctx) = AsyncCompletion::create();
661                unsafe { AsyncCompletion::<HourlyForecast>::complete_err(ctx, e.to_string()) };
662                return HourlyForecastFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
663            }
664        };
665        let (has_range, start_s, end_s) = interval
666            .as_ref()
667            .map_or((0, 0.0, 0.0), |iv| (1_i32, unix_seconds(iv.start), unix_seconds(iv.end)));
668        let (future, ctx) = AsyncCompletion::create();
669        unsafe {
670            ffi::async_ffi::wk_weather_service_hourly_forecast_async(
671                ptr,
672                location.latitude,
673                location.longitude,
674                has_range,
675                start_s,
676                end_s,
677                hourly_forecast_cb as RawCb,
678                ctx,
679            );
680        }
681        HourlyForecastFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
682    }
683
684    /// Fetch the daily forecast.
685    ///
686    /// Wraps `WeatherService.weather(for: including: .daily) async throws`.
687    pub fn daily_forecast(&self, location: &CLLocation) -> DailyForecastFuture {
688        self.daily_forecast_impl(location, None)
689    }
690
691    /// Fetch the daily forecast for a specific date interval.
692    ///
693    /// Wraps `WeatherService.weather(for: including: .daily(startDate:endDate:)) async throws`.
694    pub fn daily_forecast_in(
695        &self,
696        location: &CLLocation,
697        interval: DateInterval,
698    ) -> DailyForecastFuture {
699        self.daily_forecast_impl(location, Some(interval))
700    }
701
702    fn daily_forecast_impl(
703        &self,
704        location: &CLLocation,
705        interval: Option<DateInterval>,
706    ) -> DailyForecastFuture {
707        let ptr = match self.service_ptr() {
708            Ok(p) => p,
709            Err(e) => {
710                let (future, ctx) = AsyncCompletion::create();
711                unsafe { AsyncCompletion::<DailyForecast>::complete_err(ctx, e.to_string()) };
712                return DailyForecastFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
713            }
714        };
715        let (has_range, start_s, end_s) = interval
716            .as_ref()
717            .map_or((0, 0.0, 0.0), |iv| (1_i32, unix_seconds(iv.start), unix_seconds(iv.end)));
718        let (future, ctx) = AsyncCompletion::create();
719        unsafe {
720            ffi::async_ffi::wk_weather_service_daily_forecast_async(
721                ptr,
722                location.latitude,
723                location.longitude,
724                has_range,
725                start_s,
726                end_s,
727                daily_forecast_cb as RawCb,
728                ctx,
729            );
730        }
731        DailyForecastFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
732    }
733
734    /// Fetch the minute-by-minute precipitation forecast.
735    ///
736    /// Wraps `WeatherService.weather(for: including: .minute) async throws`.
737    ///
738    /// Returns `None` if minute forecast is not available for the location.
739    pub fn minute_forecast(&self, location: &CLLocation) -> MinuteForecastFuture {
740        let ptr = match self.service_ptr() {
741            Ok(p) => p,
742            Err(e) => {
743                let (future, ctx) = AsyncCompletion::create();
744                unsafe { AsyncCompletion::<Option<MinuteForecastCollection>>::complete_err(ctx, e.to_string()) };
745                return MinuteForecastFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
746            }
747        };
748        let (future, ctx) = AsyncCompletion::create();
749        unsafe {
750            ffi::async_ffi::wk_weather_service_minute_forecast_async(
751                ptr,
752                location.latitude,
753                location.longitude,
754                minute_forecast_cb as RawCb,
755                ctx,
756            );
757        }
758        MinuteForecastFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
759    }
760
761    /// Fetch weather alerts for the location.
762    ///
763    /// Wraps `WeatherService.weather(for: including: .alerts) async throws`.
764    pub fn weather_alerts(&self, location: &CLLocation) -> WeatherAlertsFuture {
765        let ptr = match self.service_ptr() {
766            Ok(p) => p,
767            Err(e) => {
768                let (future, ctx) = AsyncCompletion::create();
769                unsafe { AsyncCompletion::<Vec<WeatherAlert>>::complete_err(ctx, e.to_string()) };
770                return WeatherAlertsFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
771            }
772        };
773        let (future, ctx) = AsyncCompletion::create();
774        unsafe {
775            ffi::async_ffi::wk_weather_service_weather_alerts_async(
776                ptr,
777                location.latitude,
778                location.longitude,
779                weather_alerts_cb as RawCb,
780                ctx,
781            );
782        }
783        WeatherAlertsFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
784    }
785
786    /// Fetch data-availability information for the location.
787    ///
788    /// Wraps `WeatherService.weather(for: including: .availability) async throws`.
789    pub fn availability(&self, location: &CLLocation) -> AvailabilityFuture {
790        let ptr = match self.service_ptr() {
791            Ok(p) => p,
792            Err(e) => {
793                let (future, ctx) = AsyncCompletion::create();
794                unsafe { AsyncCompletion::<WeatherAvailability>::complete_err(ctx, e.to_string()) };
795                return AvailabilityFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
796            }
797        };
798        let (future, ctx) = AsyncCompletion::create();
799        unsafe {
800            ffi::async_ffi::wk_weather_service_availability_async(
801                ptr,
802                location.latitude,
803                location.longitude,
804                availability_cb as RawCb,
805                ctx,
806            );
807        }
808        AvailabilityFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
809    }
810
811    /// Fetch the WeatherKit attribution metadata required to be displayed
812    /// in your UI.
813    ///
814    /// Wraps `WeatherService.attribution async throws`.
815    pub fn attribution(&self) -> AttributionFuture {
816        let ptr = match self.service_ptr() {
817            Ok(p) => p,
818            Err(e) => {
819                let (future, ctx) = AsyncCompletion::create();
820                unsafe { AsyncCompletion::<WeatherAttribution>::complete_err(ctx, e.to_string()) };
821                return AttributionFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
822            }
823        };
824        let (future, ctx) = AsyncCompletion::create();
825        unsafe {
826            ffi::async_ffi::wk_weather_service_attribution_async(
827                ptr,
828                attribution_cb as RawCb,
829                ctx,
830            );
831        }
832        AttributionFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
833    }
834
835    /// Fetch weather-change forecasts (macOS 15.0+).
836    ///
837    /// Wraps `WeatherService.weather(for: including: .changes) async throws`.
838    ///
839    /// Returns an error on macOS < 15.0.
840    pub fn weather_changes(&self, location: &CLLocation) -> WeatherChangesFuture {
841        let ptr = match self.service_ptr() {
842            Ok(p) => p,
843            Err(e) => {
844                let (future, ctx) = AsyncCompletion::create();
845                unsafe { AsyncCompletion::<Option<WeatherChanges>>::complete_err(ctx, e.to_string()) };
846                return WeatherChangesFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
847            }
848        };
849        let (future, ctx) = AsyncCompletion::create();
850        unsafe {
851            ffi::async_ffi::wk_weather_service_weather_changes_async(
852                ptr,
853                location.latitude,
854                location.longitude,
855                weather_changes_cb as RawCb,
856                ctx,
857            );
858        }
859        WeatherChangesFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
860    }
861
862    /// Fetch historical weather comparisons (macOS 15.0+).
863    ///
864    /// Wraps `WeatherService.weather(for: including: .historicalComparisons) async throws`.
865    ///
866    /// Returns an error on macOS < 15.0.
867    pub fn historical_comparisons(&self, location: &CLLocation) -> HistoricalComparisonsFuture {
868        let ptr = match self.service_ptr() {
869            Ok(p) => p,
870            Err(e) => {
871                let (future, ctx) = AsyncCompletion::create();
872                unsafe { AsyncCompletion::<Option<HistoricalComparisons>>::complete_err(ctx, e.to_string()) };
873                return HistoricalComparisonsFuture { inner: future, _service_ptr: ServicePtrGuard(std::ptr::null_mut()) };
874            }
875        };
876        let (future, ctx) = AsyncCompletion::create();
877        unsafe {
878            ffi::async_ffi::wk_weather_service_historical_comparisons_async(
879                ptr,
880                location.latitude,
881                location.longitude,
882                historical_comparisons_cb as RawCb,
883                ctx,
884            );
885        }
886        HistoricalComparisonsFuture { inner: future, _service_ptr: ServicePtrGuard(ptr) }
887    }
888}
889
890impl Default for AsyncWeatherService {
891    fn default() -> Self {
892        Self::shared()
893    }
894}