oparl-types 0.8.5

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::{
    AgendaItemUrl, ConsultationUrl, DateTime, File, Keyword, MeetingUrl, Name, Url,
    namespace::AgendaItemNamespaceUrl,
};

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgendaItem {
    pub id: AgendaItemUrl,

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

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

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

    pub order: u32,

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

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

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

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

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

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

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

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

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

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

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

    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::AgendaItem;
    use crate::namespace::AgendaItemNamespaceUrl;

    fn example_agenda_item() -> AgendaItem {
        AgendaItem {
            id: "https://oparl.example.org/agendaitem/3271"
                .parse()
                .expect("value must be parseable as id"),
            namespace: AgendaItemNamespaceUrl::Identifier,
            meeting: Some(
                "https://oparl.example.org/meeting/281"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            number: Some("10.1".into()),
            order: 0,
            name: Some("Satzungsänderung für Ausschreibungen".into()),
            public: Some(true),
            consultation: Some(
                "https://oparl.example.org/consultation/1034"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            result: Some("Geändert beschlossen".into()),
            resolution_text: Some("Der Beschluss weicht wie folgt vom Antrag ab: ...".into()),
            resolution_file: None,
            auxiliary_file: vec![],
            start: None,
            end: None,
            license: None,
            keyword: vec![],
            created: datetime!(2012-01-06 12:01:00 +01:00).into(),
            modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
            web: None,
            deleted: None,
            extensions: serde_json::Map::new(),
        }
    }

    fn example_agenda_item_json() -> serde_json::Value {
        json!(        {
            "id": "https://oparl.example.org/agendaitem/3271",
            "type": "https://schema.oparl.org/1.1/AgendaItem",
            "meeting": "https://oparl.example.org/meeting/281",
            "number": "10.1",
            "order": 0,
            "name": "Satzungsänderung für Ausschreibungen",
            "public": true,
            "consultation": "https://oparl.example.org/consultation/1034",
            "result": "Geändert beschlossen",
            "resolutionText": "Der Beschluss weicht wie folgt vom Antrag ab: ...",
            "created": "2012-01-06T12:01:00+01:00",
            "modified": "2012-01-08T14:05:27+01:00"
        })
    }

    #[test]
    fn serialize() {
        assert_eq!(json!(example_agenda_item()), example_agenda_item_json());
    }

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

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