oparl_types/
paper.rs

1// SPDX-FileCopyrightText: Politik im Blick developers
2// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2
5
6use url::Url;
7
8use crate::{
9    namespace::PaperNamespaceUrl, BodyId, Consultation, Date, DateTime, File, Keyword, Location,
10    Name, OrganizationId, PaperId, PersonId,
11};
12
13#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct Paper {
16    pub id: PaperId,
17
18    #[serde(rename = "type")]
19    pub namespace: PaperNamespaceUrl,
20
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub body: Option<BodyId>,
23
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub name: Option<Name>,
26
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub reference: Option<String>,
29
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub date: Option<Date>,
32
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub paper_type: Option<String>,
35
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    pub related_paper: Vec<PaperId>,
38
39    #[serde(default, skip_serializing_if = "Vec::is_empty")]
40    pub superordinated_paper: Vec<PaperId>,
41
42    #[serde(default, skip_serializing_if = "Vec::is_empty")]
43    pub subordinated_paper: Vec<PaperId>,
44
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub main_file: Option<File>,
47
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub auxiliary_file: Vec<File>,
50
51    #[serde(default, skip_serializing_if = "Vec::is_empty")]
52    pub location: Vec<Location>,
53
54    #[serde(default, skip_serializing_if = "Vec::is_empty")]
55    pub originator_person: Vec<PersonId>,
56
57    #[serde(default, skip_serializing_if = "Vec::is_empty")]
58    pub under_direction_of: Vec<OrganizationId>,
59
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub originator_organization: Vec<OrganizationId>,
62
63    #[serde(default, skip_serializing_if = "Vec::is_empty")]
64    pub consultation: Vec<Consultation>,
65
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub license: Option<String>,
68
69    #[serde(default, skip_serializing_if = "Vec::is_empty")]
70    pub keyword: Vec<Keyword>,
71
72    pub created: DateTime,
73
74    pub modified: DateTime,
75
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub web: Option<Url>,
78
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub deleted: Option<bool>,
81}
82
83#[cfg(test)]
84mod serde_tests {
85    use super::Paper;
86    use crate::{
87        namespace::{
88            ConsultationNamespaceUrl, FileNamespaceUrl, LocationNamespaceUrl, PaperNamespaceUrl,
89        },
90        Consultation, File, Location, Sha1Sum,
91    };
92
93    use pretty_assertions::assert_eq;
94    use serde_json::json;
95    use time::macros::{date, datetime};
96
97    fn example_paper() -> Paper {
98        let geojson_feature = geojson::Value::Point(vec![7.03291, 50.98249]);
99
100        Paper {
101            id: "https://oparl.example.org/paper/749"
102                .parse()
103                .expect("value must be parseable as id"),
104            namespace: PaperNamespaceUrl::Identifier,
105            body: Some(
106                "https://oparl.example.org/bodies/1"
107                    .parse()
108                    .expect("value must be parseable as url"),
109            ),
110            name: Some("Antwort auf Anfrage 1200/2014".into()),
111            reference: Some("1234/2014".into()),
112            date: Some(date!(2014 - 04 - 04).into()),
113            paper_type: Some("Beantwortung einer Anfrage".into()),
114            related_paper: vec!["https://oparl.example.org/paper/699"
115                .parse()
116                .expect("value must be parseable as url")],
117            superordinated_paper: vec![],
118            subordinated_paper: vec![],
119            main_file: Some(File {
120                id: "https://oparl.example.org/files/57737"
121                    .parse()
122                    .expect("value must be parseable as id"),
123                namespace: FileNamespaceUrl::Identifier,
124                file_name: Some("anlage_1_zur_anfrage.pdf".into()),
125                name: Some("Anlage 1 zur Anfrage".into()),
126                mime_type: Some("application/pdf".into()),
127                date: Some(date!(2013 - 01 - 04).into()),
128                size: Some(82930),
129                sha1_checksum: Some(Sha1Sum::from([
130                    0xd7, 0x49, 0x75, 0x1a, 0xf4, 0x4a, 0x32, 0xc8, 0x18, 0xb9, 0xb1, 0xe1, 0x51,
131                    0x52, 0x51, 0xc6, 0x77, 0x34, 0xf5, 0xd2,
132                ])),
133                sha512_checksum: None,
134                text: None,
135                access_url: "https://oparl.example.org/files/57737.pdf"
136                    .parse()
137                    .expect("value must be parseable as url"),
138                download_url: Some(
139                    "https://oparl.example.org/files/download/57737.pdf"
140                        .parse()
141                        .expect("value must be parseable as url"),
142                ),
143                external_service_url: None,
144                master_file: None,
145                derivative_file: vec![],
146                file_license: None,
147                meeting: vec![],
148                agenda_item: vec![],
149                paper: vec![],
150                license: Some(
151                    "http://www.opendefinition.org/licenses/cc-by"
152                        .parse()
153                        .expect("value must be parseable as url"),
154                ),
155                keyword: vec![],
156                created: datetime!(2013-01-04 07:54:13 +01:00).into(),
157                modified: datetime!(2013-01-04 07:54:13 +01:00).into(),
158                web: None,
159                deleted: None,
160            }),
161            auxiliary_file: vec![File {
162                id: "https://oparl.example.org/files/57739"
163                    .parse()
164                    .expect("value must be parseable as id"),
165                namespace: FileNamespaceUrl::Identifier,
166                file_name: Some("anlage.pdf".into()),
167                name: Some("Anlage 1 zur Anfrage".into()),
168                mime_type: Some("application/pdf".into()),
169                date: Some(date!(2013 - 01 - 04).into()),
170                size: Some(82930),
171                sha1_checksum: Some(Sha1Sum::from([
172                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
173                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
174                ])),
175                sha512_checksum: None,
176                text: Some("Der Übersichtsplan zeigt alle Ebenen des ...".into()),
177                access_url: "https://oparl.example.org/files/57739.pdf"
178                    .parse()
179                    .expect("value must be parseable as url"),
180                download_url: Some(
181                    "https://oparl.example.org/files/download/57739.pdf"
182                        .parse()
183                        .expect("value must be parseable as url"),
184                ),
185                external_service_url: None,
186                master_file: Some(
187                    "https://oparl.example.org/files/57738"
188                        .parse()
189                        .expect("value must be parseable as id"),
190                ),
191                derivative_file: vec![],
192                file_license: None,
193                meeting: vec![],
194                agenda_item: vec![],
195                paper: vec![],
196                license: Some(
197                    "http://www.opendefinition.org/licenses/cc-by"
198                        .parse()
199                        .expect("value must be parseable as url"),
200                ),
201                keyword: vec![],
202                created: datetime!(2013-01-04 07:54:13 +01:00).into(),
203                modified: datetime!(2013-01-04 07:54:13 +01:00).into(),
204                web: None,
205                deleted: None,
206            }],
207            location: vec![Location {
208                id: "https://oparl.example.org/locations/29856"
209                    .parse()
210                    .expect("value must be parseable as id"),
211                namespace: LocationNamespaceUrl::Identifier,
212                description: Some("Honschaftsstraße 312, Köln".to_string()),
213                geojson: Some(geojson_feature.into()),
214                street_address: None,
215                room: None,
216                postal_code: None,
217                sub_locality: None,
218                locality: None,
219                bodies: vec![],
220                organizations: vec![],
221                persons: vec![],
222                meetings: vec![],
223                papers: vec![],
224                license: None,
225                keyword: vec![],
226                created: datetime!(2012-01-08 14:05:27 +01:00).into(),
227                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
228                web: None,
229                deleted: None,
230            }],
231            originator_person: vec![
232                "https://oparl.example.org/person/2000"
233                    .parse()
234                    .expect("value must be parseable as url"),
235                "https://oparl.example.org/person/1000"
236                    .parse()
237                    .expect("value must be parseable as url"),
238            ],
239            under_direction_of: vec!["https://oparl.example.org/organization/2000"
240                .parse()
241                .expect("value must be parseable as url")],
242            originator_organization: vec![
243                "https://oparl.example.org/organization/2000"
244                    .parse()
245                    .expect("value must be parseable as url"),
246                "https://oparl.example.org/organization/1000"
247                    .parse()
248                    .expect("value must be parseable as url"),
249            ],
250            consultation: vec![Consultation {
251                id: "https://oparl.example.org/consultation/47594"
252                    .parse()
253                    .expect("value must be parseable as id"),
254                namespace: ConsultationNamespaceUrl::Identifier,
255                paper: None,
256                agenda_item: Some(
257                    "https://oparl.example.org/agendaitem/15569"
258                        .parse()
259                        .expect("value must be parseable as url"),
260                ),
261                meeting: Some(
262                    "https://oparl.example.org/meeting/243"
263                        .parse()
264                        .expect("value must be parseable as url"),
265                ),
266                organization: vec!["https://oparl.example.org/organization/96"
267                    .parse()
268                    .expect("value must be parseable as url")],
269                authoritative: Some(false),
270                role: Some("Beschlussfassung".into()),
271                license: None,
272                keyword: vec![],
273                created: datetime!(2012-01-08 14:05:27 +01:00).into(),
274                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
275                web: None,
276                deleted: None,
277            }],
278            license: None,
279            keyword: vec![],
280            created: datetime!(2013-01-08 12:05:27 +01:00).into(),
281            modified: datetime!(2013-01-08 12:05:27 +01:00).into(),
282            web: None,
283            deleted: None,
284        }
285    }
286
287    fn example_paper_json() -> serde_json::Value {
288        json!({
289    "id": "https://oparl.example.org/paper/749",
290    "type": "https://schema.oparl.org/1.1/Paper",
291    "body": "https://oparl.example.org/bodies/1",
292    "name": "Antwort auf Anfrage 1200/2014",
293    "reference": "1234/2014",
294    "date": "2014-04-04",
295    "paperType": "Beantwortung einer Anfrage",
296    "relatedPaper": [
297        "https://oparl.example.org/paper/699"
298    ],
299    "mainFile": {
300        "id": "https://oparl.example.org/files/57737",
301        "type": "https://schema.oparl.org/1.1/File",
302        "name": "Anlage 1 zur Anfrage",
303        "fileName": "anlage_1_zur_anfrage.pdf",
304        "mimeType": "application/pdf",
305        "date": "2013-01-04",
306        "sha1Checksum": "d749751af44a32c818b9b1e1515251c67734f5d2",
307        "size": 82930,
308        "accessUrl": "https://oparl.example.org/files/57737.pdf",
309        "downloadUrl": "https://oparl.example.org/files/download/57737.pdf",
310        "license": "http://www.opendefinition.org/licenses/cc-by",
311        "created": "2013-01-04T07:54:13+01:00",
312        "modified": "2013-01-04T07:54:13+01:00"
313    },
314    "auxiliaryFile": [
315        {
316            "id": "https://oparl.example.org/files/57739",
317            "type": "https://schema.oparl.org/1.1/File",
318            "name": "Anlage 1 zur Anfrage",
319            "fileName": "anlage.pdf",
320            "mimeType": "application/pdf",
321            "date": "2013-01-04",
322            "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
323            "size": 82930,
324            "accessUrl": "https://oparl.example.org/files/57739.pdf",
325            "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
326            "text": "Der Übersichtsplan zeigt alle Ebenen des ...",
327            "masterFile": "https://oparl.example.org/files/57738",
328            "license": "http://www.opendefinition.org/licenses/cc-by",
329            "created": "2013-01-04T07:54:13+01:00",
330            "modified": "2013-01-04T07:54:13+01:00"
331        }
332    ],
333    "location": [
334        {
335            "id": "https://oparl.example.org/locations/29856",
336            "type": "https://schema.oparl.org/1.1/Location",
337            "description": "Honschaftsstraße 312, Köln",
338            "created": "2012-01-08T14:05:27+01:00",
339            "modified": "2012-01-08T14:05:27+01:00",
340            "geojson": {
341                "type": "Point",
342                "coordinates": [
343                    7.03291,
344                    50.98249
345                ]
346            }
347        }
348    ],
349    "originatorPerson": [
350        "https://oparl.example.org/person/2000",
351        "https://oparl.example.org/person/1000"
352    ],
353    "originatorOrganization": [
354        "https://oparl.example.org/organization/2000",
355        "https://oparl.example.org/organization/1000"
356    ],
357    "consultation": [
358        {
359            "id": "https://oparl.example.org/consultation/47594",
360            "type": "https://schema.oparl.org/1.1/Consultation",
361            "agendaItem": "https://oparl.example.org/agendaitem/15569",
362            "meeting": "https://oparl.example.org/meeting/243",
363            "organization": [
364                "https://oparl.example.org/organization/96"
365            ],
366            "authoritative": false,
367            "role": "Beschlussfassung",
368            "created": "2012-01-08T14:05:27+01:00",
369            "modified": "2012-01-08T14:05:27+01:00"
370        }
371    ],
372    "underDirectionOf": [
373        "https://oparl.example.org/organization/2000"
374    ],
375    "created": "2013-01-08T12:05:27+01:00",
376    "modified": "2013-01-08T12:05:27+01:00"})
377    }
378
379    #[test]
380    fn serialize() {
381        assert_eq!(json!(example_paper()), example_paper_json());
382    }
383
384    #[test]
385    fn deserialize_good() {
386        let deserialized: Paper = serde_json::from_value(example_paper_json())
387            .expect("value must be deserializable as Paper");
388        assert_eq!(deserialized, example_paper());
389    }
390
391    #[test]
392    fn deserialize_bad() {
393        assert!(serde_json::from_value::<Paper>(json!("xyzabcd")).is_err());
394        assert!(serde_json::from_value::<Paper>(json!(true)).is_err());
395        assert!(serde_json::from_value::<Paper>(json!(123)).is_err());
396    }
397}