weatherkit-doomfish 0.3.1

Safe Rust bindings for Apple's WeatherKit framework — weather data on macOS
Documentation
use core::ffi::c_void;

use serde::Deserialize;

use crate::error::WeatherKitError;
use crate::ffi;
use crate::pressure::{deserialize_pressure_trend, Pressure, PressureTrend};
use crate::private::{parse_json_from_handle, parse_json_from_static};
use crate::service::WeatherMetadata;
use crate::weather_condition::{deserialize_weather_condition, WeatherCondition};

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CloudCoverByAltitude {
    pub low: f64,
    pub medium: f64,
    pub high: f64,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum WindCompassDirection {
    North,
    NorthNortheast,
    Northeast,
    EastNortheast,
    East,
    EastSoutheast,
    Southeast,
    SouthSoutheast,
    South,
    SouthSouthwest,
    Southwest,
    WestSouthwest,
    West,
    WestNorthwest,
    Northwest,
    NorthNorthwest,
    Unknown(String),
}

impl WindCompassDirection {
    pub(crate) fn from_raw(value: String) -> Self {
        match value.as_str() {
            "north" => Self::North,
            "northNortheast" => Self::NorthNortheast,
            "northeast" => Self::Northeast,
            "eastNortheast" => Self::EastNortheast,
            "east" => Self::East,
            "eastSoutheast" => Self::EastSoutheast,
            "southeast" => Self::Southeast,
            "southSoutheast" => Self::SouthSoutheast,
            "south" => Self::South,
            "southSouthwest" => Self::SouthSouthwest,
            "southwest" => Self::Southwest,
            "westSouthwest" => Self::WestSouthwest,
            "west" => Self::West,
            "westNorthwest" => Self::WestNorthwest,
            "northwest" => Self::Northwest,
            "northNorthwest" => Self::NorthNorthwest,
            other => Self::Unknown(other.to_owned()),
        }
    }

    pub fn raw_value(&self) -> &str {
        match self {
            Self::North => "north",
            Self::NorthNortheast => "northNortheast",
            Self::Northeast => "northeast",
            Self::EastNortheast => "eastNortheast",
            Self::East => "east",
            Self::EastSoutheast => "eastSoutheast",
            Self::Southeast => "southeast",
            Self::SouthSoutheast => "southSoutheast",
            Self::South => "south",
            Self::SouthSouthwest => "southSouthwest",
            Self::Southwest => "southwest",
            Self::WestSouthwest => "westSouthwest",
            Self::West => "west",
            Self::WestNorthwest => "westNorthwest",
            Self::Northwest => "northwest",
            Self::NorthNorthwest => "northNorthwest",
            Self::Unknown(value) => value.as_str(),
        }
    }

    pub fn descriptors() -> Result<Vec<WindCompassDirectionDescriptor>, WeatherKitError> {
        parse_json_from_static(
            ffi::current_weather::wk_wind_compass_direction_copy_descriptors_json,
            "wind compass direction descriptors",
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WindCompassDirectionDescriptor {
    pub raw_value: String,
    pub abbreviation: String,
    pub description: String,
    pub accessibility_description: String,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Wind {
    pub speed: f64,
    pub direction: f64,
    pub compass_direction: String,
    pub gust: Option<f64>,
}

impl Wind {
    pub fn compass_direction_kind(&self) -> WindCompassDirection {
        WindCompassDirection::from_raw(self.compass_direction.clone())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum UVExposureCategory {
    Low,
    Moderate,
    High,
    VeryHigh,
    Extreme,
    Unknown(String),
}

impl UVExposureCategory {
    pub(crate) fn from_raw(value: String) -> Self {
        match value.as_str() {
            "low" => Self::Low,
            "moderate" => Self::Moderate,
            "high" => Self::High,
            "veryHigh" => Self::VeryHigh,
            "extreme" => Self::Extreme,
            other => Self::Unknown(other.to_owned()),
        }
    }

    pub fn raw_value(&self) -> &str {
        match self {
            Self::Low => "low",
            Self::Moderate => "moderate",
            Self::High => "high",
            Self::VeryHigh => "veryHigh",
            Self::Extreme => "extreme",
            Self::Unknown(value) => value.as_str(),
        }
    }

    pub fn descriptors() -> Result<Vec<UVExposureCategoryDescriptor>, WeatherKitError> {
        parse_json_from_static(
            ffi::current_weather::wk_uv_exposure_category_copy_descriptors_json,
            "UV exposure category descriptors",
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UVExposureCategoryDescriptor {
    pub raw_value: String,
    pub description: String,
    pub accessibility_description: String,
    pub range_start: i64,
    pub range_end: i64,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UVIndex {
    pub value: i64,
    pub category: String,
}

impl UVIndex {
    pub fn exposure_category(&self) -> UVExposureCategory {
        UVExposureCategory::from_raw(self.category.clone())
    }
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CurrentWeather {
    pub date: String,
    pub temperature: f64,
    pub feels_like: f64,
    pub humidity: f64,
    pub dew_point: f64,
    pub pressure: f64,
    #[serde(deserialize_with = "deserialize_pressure_trend")]
    pub pressure_trend: PressureTrend,
    #[serde(deserialize_with = "deserialize_weather_condition")]
    pub condition: WeatherCondition,
    pub symbol_name: String,
    pub wind: Wind,
    pub uv_index: UVIndex,
    pub visibility: f64,
    pub cloud_cover: f64,
    #[serde(default)]
    pub cloud_cover_by_altitude: Option<CloudCoverByAltitude>,
    pub is_daylight: bool,
    pub precipitation_intensity: f64,
    pub metadata: WeatherMetadata,
}

impl CurrentWeather {
    pub(crate) fn from_owned_ptr(ptr: *mut c_void) -> Result<Self, WeatherKitError> {
        parse_json_from_handle(
            ptr,
            ffi::current_weather::wk_current_weather_release,
            ffi::current_weather::wk_current_weather_copy_json,
            "current weather",
        )
    }

    pub fn pressure_reading(&self) -> Pressure {
        Pressure::new(self.pressure, self.pressure_trend.clone())
    }
}