oparl-types 0.7.1

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, DateTime, FileUrl, Keyword, MeetingUrl, Name, PaperUrl, Sha1Sum, Sha512Sum, Url,
    date::Date, namespace::FileNamespaceUrl,
};

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

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

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

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

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

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

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

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

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

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

    pub access_url: Url,

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

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

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

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

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

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

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

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

    #[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::{date, datetime};

    use super::File;
    use crate::{Sha1Sum, namespace::FileNamespaceUrl};

    fn example_file() -> File {
        File {
            id: "https://oparl.example.org/files/57737"
                .parse()
                .expect("value must be parseable as id"),
            namespace: FileNamespaceUrl::Identifier,
            file_name: Some("anlage_1_zur_anfrage.pdf".into()),
            name: Some("Anlage 1 zur Anfrage".into()),
            mime_type: Some("application/pdf".into()),
            date: Some(date!(2013 - 01 - 04).into()),
            size: Some(82930),
            sha1_checksum: Some(Sha1Sum::from([
                0xd7, 0x49, 0x75, 0x1a, 0xf4, 0x4a, 0x32, 0xc8, 0x18, 0xb9, 0xb1, 0xe1, 0x51, 0x52,
                0x51, 0xc6, 0x77, 0x34, 0xf5, 0xd2,
            ])),
            sha512_checksum: None,
            text: None,
            access_url: "https://oparl.example.org/files/57737.pdf"
                .parse()
                .expect("value must be parseable as url"),
            download_url: Some(
                "https://oparl.example.org/files/download/57737.pdf"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            external_service_url: None,
            master_file: None,
            derivative_file: vec![
                "https://oparl.example.org/files/57739"
                    .parse()
                    .expect("value must be parseable as url"),
            ],
            file_license: Some(
                "http://www.opendefinition.org/licenses/cc-by"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            meeting: vec![],
            agenda_item: vec![],
            paper: vec![],
            license: None,
            keyword: vec![],
            created: datetime!(2013-01-04 07:54:13 +01:00).into(),
            modified: datetime!(2013-01-04 07:54:13 +01:00).into(),
            web: None,
            deleted: None,
            extensions: serde_json::Map::new(),
        }
    }

    fn example_file_json() -> serde_json::Value {
        json!({
            "id": "https://oparl.example.org/files/57737",
            "type": "https://schema.oparl.org/1.1/File",
            "name": "Anlage 1 zur Anfrage",
            "fileName": "anlage_1_zur_anfrage.pdf",
            "mimeType": "application/pdf",
            "date": "2013-01-04",
            "size": 82930,
            "sha1Checksum": "d749751af44a32c818b9b1e1515251c67734f5d2",
            "accessUrl": "https://oparl.example.org/files/57737.pdf",
            "downloadUrl": "https://oparl.example.org/files/download/57737.pdf",
            "derivativeFile": [
                "https://oparl.example.org/files/57739"
            ],
            "fileLicense": "http://www.opendefinition.org/licenses/cc-by",
            "created": "2013-01-04T07:54:13+01:00",
            "modified": "2013-01-04T07:54:13+01:00"
        })
    }

    #[test]
    fn serialize() {
        assert_eq!(json!(example_file()), example_file_json());
    }

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

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