openmeteo-rs 1.0.0

Rust client for the Open-Meteo weather API.
Documentation
//! WMO weather-code helpers.

/// Open-Meteo subset of WMO present weather codes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
#[repr(u8)]
#[non_exhaustive]
pub enum WmoCode {
    /// Clear sky.
    ClearSky = 0,
    /// Mainly clear.
    MainlyClear = 1,
    /// Partly cloudy.
    PartlyCloudy = 2,
    /// Overcast.
    Overcast = 3,
    /// Fog.
    Fog = 45,
    /// Depositing rime fog.
    DepositingRimeFog = 48,
    /// Light drizzle.
    DrizzleLight = 51,
    /// Moderate drizzle.
    DrizzleModerate = 53,
    /// Dense drizzle.
    DrizzleDense = 55,
    /// Light freezing drizzle.
    FreezingDrizzleLight = 56,
    /// Dense freezing drizzle.
    FreezingDrizzleDense = 57,
    /// Slight rain.
    RainSlight = 61,
    /// Moderate rain.
    RainModerate = 63,
    /// Heavy rain.
    RainHeavy = 65,
    /// Light freezing rain.
    FreezingRainLight = 66,
    /// Heavy freezing rain.
    FreezingRainHeavy = 67,
    /// Slight snow fall.
    SnowFallSlight = 71,
    /// Moderate snow fall.
    SnowFallModerate = 73,
    /// Heavy snow fall.
    SnowFallHeavy = 75,
    /// Snow grains.
    SnowGrains = 77,
    /// Slight rain showers.
    RainShowersSlight = 80,
    /// Moderate rain showers.
    RainShowersModerate = 81,
    /// Violent rain showers.
    RainShowersViolent = 82,
    /// Slight snow showers.
    SnowShowersSlight = 85,
    /// Heavy snow showers.
    SnowShowersHeavy = 86,
    /// Slight or moderate thunderstorm.
    ThunderstormSlightOrModerate = 95,
    /// Thunderstorm with slight hail.
    ThunderstormWithSlightHail = 96,
    /// Thunderstorm with heavy hail.
    ThunderstormWithHeavyHail = 99,
}

impl WmoCode {
    /// Converts the numeric API code into a known enum value.
    pub fn from_code(code: u8) -> Option<Self> {
        Some(match code {
            0 => Self::ClearSky,
            1 => Self::MainlyClear,
            2 => Self::PartlyCloudy,
            3 => Self::Overcast,
            45 => Self::Fog,
            48 => Self::DepositingRimeFog,
            51 => Self::DrizzleLight,
            53 => Self::DrizzleModerate,
            55 => Self::DrizzleDense,
            56 => Self::FreezingDrizzleLight,
            57 => Self::FreezingDrizzleDense,
            61 => Self::RainSlight,
            63 => Self::RainModerate,
            65 => Self::RainHeavy,
            66 => Self::FreezingRainLight,
            67 => Self::FreezingRainHeavy,
            71 => Self::SnowFallSlight,
            73 => Self::SnowFallModerate,
            75 => Self::SnowFallHeavy,
            77 => Self::SnowGrains,
            80 => Self::RainShowersSlight,
            81 => Self::RainShowersModerate,
            82 => Self::RainShowersViolent,
            85 => Self::SnowShowersSlight,
            86 => Self::SnowShowersHeavy,
            95 => Self::ThunderstormSlightOrModerate,
            96 => Self::ThunderstormWithSlightHail,
            99 => Self::ThunderstormWithHeavyHail,
            _ => return None,
        })
    }

    /// Human-readable Open-Meteo weather-code description.
    pub fn description(self) -> &'static str {
        match self {
            Self::ClearSky => "clear sky",
            Self::MainlyClear => "mainly clear",
            Self::PartlyCloudy => "partly cloudy",
            Self::Overcast => "overcast",
            Self::Fog => "fog",
            Self::DepositingRimeFog => "depositing rime fog",
            Self::DrizzleLight => "light drizzle",
            Self::DrizzleModerate => "moderate drizzle",
            Self::DrizzleDense => "dense drizzle",
            Self::FreezingDrizzleLight => "light freezing drizzle",
            Self::FreezingDrizzleDense => "dense freezing drizzle",
            Self::RainSlight => "slight rain",
            Self::RainModerate => "moderate rain",
            Self::RainHeavy => "heavy rain",
            Self::FreezingRainLight => "light freezing rain",
            Self::FreezingRainHeavy => "heavy freezing rain",
            Self::SnowFallSlight => "slight snow fall",
            Self::SnowFallModerate => "moderate snow fall",
            Self::SnowFallHeavy => "heavy snow fall",
            Self::SnowGrains => "snow grains",
            Self::RainShowersSlight => "slight rain showers",
            Self::RainShowersModerate => "moderate rain showers",
            Self::RainShowersViolent => "violent rain showers",
            Self::SnowShowersSlight => "slight snow showers",
            Self::SnowShowersHeavy => "heavy snow showers",
            Self::ThunderstormSlightOrModerate => "slight or moderate thunderstorm",
            Self::ThunderstormWithSlightHail => "thunderstorm with slight hail",
            Self::ThunderstormWithHeavyHail => "thunderstorm with heavy hail",
        }
    }

    /// Returns whether this weather code implies precipitation.
    pub fn is_precipitating(self) -> bool {
        matches!(
            self,
            Self::DrizzleLight
                | Self::DrizzleModerate
                | Self::DrizzleDense
                | Self::FreezingDrizzleLight
                | Self::FreezingDrizzleDense
                | Self::RainSlight
                | Self::RainModerate
                | Self::RainHeavy
                | Self::FreezingRainLight
                | Self::FreezingRainHeavy
                | Self::SnowFallSlight
                | Self::SnowFallModerate
                | Self::SnowFallHeavy
                | Self::SnowGrains
                | Self::RainShowersSlight
                | Self::RainShowersModerate
                | Self::RainShowersViolent
                | Self::SnowShowersSlight
                | Self::SnowShowersHeavy
                | Self::ThunderstormSlightOrModerate
                | Self::ThunderstormWithSlightHail
                | Self::ThunderstormWithHeavyHail
        )
    }

    /// Returns whether this weather code is a thunderstorm.
    pub fn is_thunderstorm(self) -> bool {
        matches!(
            self,
            Self::ThunderstormSlightOrModerate
                | Self::ThunderstormWithSlightHail
                | Self::ThunderstormWithHeavyHail
        )
    }

    /// Coarse severity classification.
    pub fn severity(self) -> Severity {
        match self {
            Self::ClearSky | Self::MainlyClear | Self::PartlyCloudy => Severity::Clear,
            Self::DrizzleLight
            | Self::FreezingDrizzleLight
            | Self::RainSlight
            | Self::FreezingRainLight
            | Self::SnowFallSlight
            | Self::RainShowersSlight
            | Self::SnowShowersSlight => Severity::Light,
            Self::Overcast
            | Self::Fog
            | Self::DepositingRimeFog
            | Self::DrizzleModerate
            | Self::RainModerate
            | Self::SnowFallModerate
            | Self::SnowGrains
            | Self::RainShowersModerate
            | Self::ThunderstormSlightOrModerate => Severity::Moderate,
            Self::DrizzleDense
            | Self::FreezingDrizzleDense
            | Self::RainHeavy
            | Self::FreezingRainHeavy
            | Self::SnowFallHeavy
            | Self::RainShowersViolent
            | Self::SnowShowersHeavy => Severity::Heavy,
            Self::ThunderstormWithSlightHail | Self::ThunderstormWithHeavyHail => Severity::Severe,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Severity, WmoCode};

    #[test]
    fn all_documented_open_meteo_codes_are_supported() {
        for (code, expected) in [
            (0, WmoCode::ClearSky),
            (1, WmoCode::MainlyClear),
            (2, WmoCode::PartlyCloudy),
            (3, WmoCode::Overcast),
            (45, WmoCode::Fog),
            (48, WmoCode::DepositingRimeFog),
            (51, WmoCode::DrizzleLight),
            (53, WmoCode::DrizzleModerate),
            (55, WmoCode::DrizzleDense),
            (56, WmoCode::FreezingDrizzleLight),
            (57, WmoCode::FreezingDrizzleDense),
            (61, WmoCode::RainSlight),
            (63, WmoCode::RainModerate),
            (65, WmoCode::RainHeavy),
            (66, WmoCode::FreezingRainLight),
            (67, WmoCode::FreezingRainHeavy),
            (71, WmoCode::SnowFallSlight),
            (73, WmoCode::SnowFallModerate),
            (75, WmoCode::SnowFallHeavy),
            (77, WmoCode::SnowGrains),
            (80, WmoCode::RainShowersSlight),
            (81, WmoCode::RainShowersModerate),
            (82, WmoCode::RainShowersViolent),
            (85, WmoCode::SnowShowersSlight),
            (86, WmoCode::SnowShowersHeavy),
            (95, WmoCode::ThunderstormSlightOrModerate),
            (96, WmoCode::ThunderstormWithSlightHail),
            (99, WmoCode::ThunderstormWithHeavyHail),
        ] {
            assert_eq!(WmoCode::from_code(code), Some(expected));
            assert!(!expected.description().is_empty());
        }

        assert_eq!(WmoCode::from_code(4), None);
    }

    #[test]
    fn weather_code_helpers_classify_precipitation_and_storms() {
        assert!(!WmoCode::ClearSky.is_precipitating());
        assert!(WmoCode::FreezingRainLight.is_precipitating());
        assert!(WmoCode::RainShowersViolent.is_precipitating());
        assert!(WmoCode::ThunderstormWithSlightHail.is_thunderstorm());
        assert_eq!(
            WmoCode::ThunderstormWithHeavyHail.severity(),
            Severity::Severe
        );
    }
}

/// Coarse weather-code severity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
#[non_exhaustive]
pub enum Severity {
    /// Clear or fair conditions.
    Clear,
    /// Light weather impact.
    Light,
    /// Moderate weather impact.
    Moderate,
    /// Heavy weather impact.
    Heavy,
    /// Severe weather impact.
    Severe,
}