oparl-types 0.8.0

Type definitions for the OParl protocol
Documentation
// SPDX-FileCopyrightText: Politik im Blick developers
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
//
// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2

use crate::{
    BodyListUrl, DateTime, EmailAddress, Name, OParlVersion, SystemUrl, Url,
    namespace::SystemNamespaceUrl,
};

#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct System {
    pub id: SystemUrl,

    #[serde(rename = "type")]
    pub namespace: SystemNamespaceUrl,

    pub oparl_version: OParlVersion,

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub other_oparl_versions: Vec<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub license: Option<Url>,

    pub body: BodyListUrl,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<Name>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub contact_email: Option<EmailAddress>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub contact_name: Option<Name>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub website: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub vendor: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub product: Option<Url>,

    pub created: DateTime,

    pub modified: DateTime,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub web: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deleted: Option<bool>,

    #[serde(default, flatten)]
    pub extensions: serde_json::Map<String, serde_json::Value>,
}

#[cfg(test)]
mod serde_tests {
    use pretty_assertions::assert_eq;
    use serde_json::json;
    use time::macros::datetime;

    use super::System;
    use crate::{OParlVersion, namespace::SystemNamespaceUrl};

    fn example_system() -> System {
        System {
            id: "https://oparl.example.org/"
                .parse()
                .expect("value must be parseable as id"),
            namespace: SystemNamespaceUrl::Identifier,
            oparl_version: OParlVersion::Version1_1,
            other_oparl_versions: vec![
                "https://oparl2.example.org/"
                    .parse()
                    .expect("value must be parseable as url"),
            ],
            license: None,
            body: "https://oparl.example.org/bodies"
                .parse()
                .expect("value must be parseable as url"),
            name: Some(
                "Beispiel-System"
                    .parse()
                    .expect("value must be parseable as name"),
            ),
            contact_email: Some(
                "info@example.org"
                    .parse()
                    .expect("value must be parseable as email"),
            ),
            contact_name: Some(
                "Allgemeiner OParl Kontakt"
                    .parse()
                    .expect("value must be parseable as name"),
            ),
            website: Some(
                "http://www.example.org/"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            vendor: Some(
                "http://example-software.com/"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            product: Some(
                "http://example-software.com/oparl-server/"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            created: datetime!(2011-11-11 11:11:00 +01:00).into(),
            modified: datetime!(2012-11-11 11:11:00 +01:00).into(),
            web: None,
            deleted: None,
            extensions: serde_json::Map::new(),
        }
    }

    fn example_system_json() -> serde_json::Value {
        json!({
            "id": "https://oparl.example.org/",
            "type": "https://schema.oparl.org/1.1/System",
            "oparlVersion": "https://schema.oparl.org/1.1/",
            "body": "https://oparl.example.org/bodies",
            "name": "Beispiel-System",
            "contactEmail": "info@example.org",
            "contactName": "Allgemeiner OParl Kontakt",
            "website": "http://www.example.org/",
            "vendor": "http://example-software.com/",
            "product": "http://example-software.com/oparl-server/",
            "otherOparlVersions": [
                "https://oparl2.example.org/"
            ],
            "created": "2011-11-11T11:11:00+01:00",
            "modified": "2012-11-11T11:11:00+01:00"
        })
    }

    #[test]
    fn serialize() {
        assert_eq!(json!(example_system()), example_system_json());
    }

    #[test]
    fn serialize_with_extensions() {
        let extensions = json!({"pib:hello":"world", "pib:status":"new"})
            .as_object()
            .cloned()
            .unwrap();

        let mut example_system = example_system();
        example_system.extensions = extensions;

        let mut example_system_json = example_system_json();
        {
            let extensions = example_system_json.as_object_mut().unwrap();
            extensions.insert("pib:hello".into(), "world".into());
            extensions.insert("pib:status".into(), "new".into());
        }

        assert_eq!(json!(example_system), example_system_json);
    }

    #[test]
    fn deserialize_good() {
        let deserialized: System = serde_json::from_value(example_system_json())
            .expect("value must be deserializable as System");
        assert_eq!(deserialized, example_system());
    }

    #[test]
    fn deserialize_with_extensions() {
        let extensions = json!({"pib:hello":"world", "pib:status":"new"})
            .as_object()
            .cloned()
            .unwrap();

        let mut example_system = example_system();
        example_system.extensions = extensions;
        let mut example_system_json = example_system_json();
        {
            let extensions = example_system_json.as_object_mut().unwrap();
            extensions.insert("pib:hello".into(), "world".into());
            extensions.insert("pib:status".into(), "new".into());
        }

        let deserialized: System = serde_json::from_value(example_system_json)
            .expect("value must be deserializable as System");

        assert_eq!(deserialized, example_system);
        assert_eq!(deserialized.extensions.len(), 2);
        assert_eq!(deserialized.extensions["pib:hello"], "world");
        assert_eq!(deserialized.extensions["pib:status"], "new");
    }

    #[test]
    fn deserialize_bad() {
        assert!(serde_json::from_value::<System>(json!("xyzabcd")).is_err());
        assert!(serde_json::from_value::<System>(json!(true)).is_err());
        assert!(serde_json::from_value::<System>(json!(123)).is_err());
    }
}