oasiscap 0.2.0

Types for the OASIS Common Alerting Protocol (CAP)
Documentation
use serde::{Deserialize, Serialize};
use std::ops::RangeInclusive;

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "XmlAltitude", into = "XmlAltitude")]
pub enum Altitude {
    Unspecified,
    Specific(i64),
    Range(RangeInclusive<i64>),
}

#[derive(Debug, Serialize, Deserialize)]
struct XmlAltitude {
    #[serde(
        rename = "{http://www.incident.com/cap/1.0}cap:altitude",
        skip_serializing_if = "Option::is_none"
    )]
    altitude: Option<i64>,
    #[serde(
        rename = "{http://www.incident.com/cap/1.0}cap:ceiling",
        skip_serializing_if = "Option::is_none"
    )]
    ceiling: Option<i64>,
}

impl TryFrom<XmlAltitude> for Altitude {
    type Error = &'static str;

    fn try_from(value: XmlAltitude) -> Result<Self, Self::Error> {
        match value {
            XmlAltitude {
                altitude: None,
                ceiling: None,
            } => Ok(Altitude::Unspecified),
            XmlAltitude {
                altitude: Some(a),
                ceiling: None,
            } => Ok(Altitude::Specific(a)),
            XmlAltitude {
                altitude: Some(a),
                ceiling: Some(b),
            } => Ok(Altitude::Range(a..=b)),
            _ => Err("ceiling must not be specified without altitude"),
        }
    }
}

impl From<Altitude> for XmlAltitude {
    fn from(a: Altitude) -> Self {
        match a {
            Altitude::Unspecified => XmlAltitude {
                altitude: None,
                ceiling: None,
            },
            Altitude::Specific(altitude) => XmlAltitude {
                altitude: Some(altitude),
                ceiling: None,
            },
            Altitude::Range(range) => XmlAltitude {
                altitude: Some(*range.start()),
                ceiling: Some(*range.end()),
            },
        }
    }
}

#[test]
fn from_xml() {
    #[derive(Deserialize)]
    struct Doc {
        #[serde(rename = "{http://www.incident.com/cap/1.0}:cap:container")]
        container: Container,
    }

    #[derive(Deserialize)]
    struct Container {
        #[serde(flatten)]
        altitude: Altitude,
    }

    assert_eq!(
        xml_serde::from_str::<Doc>(
            r#"
<cap:container xmlns:cap="http://www.incident.com/cap/1.0">
    <cap:altitude>100</cap:altitude>
</cap:container>
"#,
        )
        .unwrap()
        .container
        .altitude,
        Altitude::Specific(100)
    );
}