oparl_types/
meeting.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::MeetingNamespaceUrl, AgendaItem, DateTime, File, Keyword, Location, MeetingId, Name,
10    OrganizationId,
11};
12
13#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct Meeting {
16    pub id: MeetingId,
17
18    #[serde(rename = "type")]
19    pub namespace: MeetingNamespaceUrl,
20
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub name: Option<Name>,
23
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub meeting_state: Option<String>,
26
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub cancelled: Option<bool>,
29
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub start: Option<DateTime>,
32
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub end: Option<DateTime>,
35
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub location: Option<Location>,
38
39    #[serde(default, skip_serializing_if = "Vec::is_empty")]
40    pub organization: Vec<OrganizationId>,
41
42    #[serde(default, skip_serializing_if = "Vec::is_empty")]
43    pub participant: Vec<Url>,
44
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub invitation: Option<File>,
47
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub results_protocol: Option<File>,
50
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub verbatim_protocol: Option<File>,
53
54    #[serde(default, skip_serializing_if = "Vec::is_empty")]
55    pub auxiliary_file: Vec<File>,
56
57    #[serde(default, skip_serializing_if = "Vec::is_empty")]
58    pub agenda_item: Vec<AgendaItem>,
59
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub license: Option<String>,
62
63    #[serde(default, skip_serializing_if = "Vec::is_empty")]
64    pub keyword: Vec<Keyword>,
65
66    pub created: DateTime,
67
68    pub modified: DateTime,
69
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub web: Option<Url>,
72
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub deleted: Option<bool>,
75}
76
77#[cfg(test)]
78mod serde_tests {
79    use super::Meeting;
80    use crate::{
81        namespace::{
82            AgendaItemNamespaceUrl, FileNamespaceUrl, LocationNamespaceUrl, MeetingNamespaceUrl,
83        },
84        AgendaItem, File, Location, Sha1Sum,
85    };
86
87    use pretty_assertions::assert_eq;
88    use serde_json::json;
89    use time::macros::{date, datetime};
90
91    fn example_meeting() -> Meeting {
92        let geojson_feature = {
93            let mut f =
94                geojson::Feature::from(geojson::Geometry::new(geojson::Value::Point(vec![
95                    50.1234, 10.4321,
96                ])));
97            f.set_property("name", "Rathausplatz");
98            f
99        };
100
101        Meeting {
102            id: "https://oparl.example.org/meeting/281"
103                .parse()
104                .expect("value must be parseable as id"),
105            namespace: MeetingNamespaceUrl::Identifier,
106            name: Some("4. Sitzung des Finanzausschusses".into()),
107            cancelled: None,
108            meeting_state: None,
109            start: Some(datetime!(2013 - 01 - 04 08:00:00 +01:00).into()),
110            end: Some(datetime!(2013 - 01 - 04 12:00:00 +01:00).into()),
111            location: Some(Location {
112                id: "https://oparl.example.org/location/0"
113                    .parse()
114                    .expect("value must be parseable as id"),
115                namespace: LocationNamespaceUrl::Identifier,
116                description: Some(
117                    "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt".to_string(),
118                ),
119                geojson: Some(geojson_feature.into()),
120                street_address: None,
121                room: None,
122                postal_code: None,
123                sub_locality: None,
124                locality: None,
125                bodies: vec![],
126                organizations: vec![],
127                persons: vec![],
128                meetings: vec![],
129                papers: vec![],
130                license: None,
131                keyword: vec![],
132                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
133                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
134                web: None,
135                deleted: None,
136            }),
137            organization: vec!["https://oparl.example.org/organization/34"
138                .parse()
139                .expect("value must be parseable as url")],
140            participant: vec![],
141            invitation: Some(File {
142                id: "https://oparl.example.org/files/57739"
143                    .parse()
144                    .expect("value must be parseable as id"),
145                namespace: FileNamespaceUrl::Identifier,
146                file_name: Some("einladung.pdf".into()),
147                name: Some("Einladung".into()),
148                mime_type: Some("application/pdf".into()),
149                date: Some(date!(2012 - 01 - 08).into()),
150                size: Some(82930),
151                sha1_checksum: Some(Sha1Sum::from([
152                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
153                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
154                ])),
155                sha512_checksum: None,
156                text: None,
157                access_url: "https://oparl.example.org/files/57739.pdf"
158                    .parse()
159                    .expect("value must be parseable as url"),
160                download_url: Some(
161                    "https://oparl.example.org/files/download/57739.pdf"
162                        .parse()
163                        .expect("value must be parseable as url"),
164                ),
165                external_service_url: None,
166                master_file: None,
167                derivative_file: vec![],
168                file_license: None,
169                meeting: vec![],
170                agenda_item: vec![],
171                paper: vec![],
172                license: None,
173                keyword: vec![],
174                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
175                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
176                web: None,
177                deleted: None,
178            }),
179            results_protocol: Some(File {
180                id: "https://oparl.example.org/files/57739"
181                    .parse()
182                    .expect("value must be parseable as id"),
183                namespace: FileNamespaceUrl::Identifier,
184                file_name: Some("protokoll.pdf".into()),
185                name: Some("Protokoll".into()),
186                mime_type: Some("application/pdf".into()),
187                date: Some(date!(2012 - 01 - 08).into()),
188                size: Some(82930),
189                sha1_checksum: Some(Sha1Sum::from([
190                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
191                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
192                ])),
193                sha512_checksum: None,
194                text: None,
195                access_url: "https://oparl.example.org/files/57739.pdf"
196                    .parse()
197                    .expect("value must be parseable as url"),
198                download_url: Some(
199                    "https://oparl.example.org/files/download/57739.pdf"
200                        .parse()
201                        .expect("value must be parseable as url"),
202                ),
203                external_service_url: None,
204                master_file: None,
205                derivative_file: vec![],
206                file_license: None,
207                meeting: vec![],
208                agenda_item: vec![],
209                paper: vec![],
210                license: None,
211                keyword: vec![],
212                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
213                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
214                web: None,
215                deleted: None,
216            }),
217            verbatim_protocol: Some(File {
218                id: "https://oparl.example.org/files/57739"
219                    .parse()
220                    .expect("value must be parseable as id"),
221                namespace: FileNamespaceUrl::Identifier,
222                file_name: Some("wortprotokoll.pdf".into()),
223                name: Some("Wortprotokoll".into()),
224                mime_type: Some("application/pdf".into()),
225                date: Some(date!(2012 - 01 - 08).into()),
226                size: Some(82930),
227                sha1_checksum: Some(Sha1Sum::from([
228                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
229                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
230                ])),
231                sha512_checksum: None,
232                text: None,
233                access_url: "https://oparl.example.org/files/57739.pdf"
234                    .parse()
235                    .expect("value must be parseable as url"),
236                download_url: Some(
237                    "https://oparl.example.org/files/download/57739.pdf"
238                        .parse()
239                        .expect("value must be parseable as url"),
240                ),
241                external_service_url: None,
242                master_file: None,
243                derivative_file: vec![],
244                file_license: None,
245                meeting: vec![],
246                agenda_item: vec![],
247                paper: vec![],
248                license: None,
249                keyword: vec![],
250                created: datetime!(2012-01-08 14:05:27 +01:00).into(),
251                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
252                web: None,
253                deleted: None,
254            }),
255            auxiliary_file: vec![File {
256                id: "https://oparl.example.org/files/57739"
257                    .parse()
258                    .expect("value must be parseable as id"),
259                namespace: FileNamespaceUrl::Identifier,
260                file_name: Some("nachtrag-TO.pdf".into()),
261                name: Some("Nachtrags-Tagesordnung".into()),
262                mime_type: Some("application/pdf".into()),
263                date: Some(date!(2012 - 01 - 08).into()),
264                size: Some(82930),
265                sha1_checksum: Some(Sha1Sum::from([
266                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
267                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
268                ])),
269                sha512_checksum: None,
270                text: None,
271                access_url: "https://oparl.example.org/files/57739.pdf"
272                    .parse()
273                    .expect("value must be parseable as url"),
274                download_url: Some(
275                    "https://oparl.example.org/files/download/57739.pdf"
276                        .parse()
277                        .expect("value must be parseable as url"),
278                ),
279                external_service_url: None,
280                master_file: None,
281                derivative_file: vec![],
282                file_license: None,
283                meeting: vec![],
284                agenda_item: vec![],
285                paper: vec![],
286                license: None,
287                keyword: vec![],
288                created: datetime!(2012-01-08 14:05:27 +01:00).into(),
289                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
290                web: None,
291                deleted: None,
292            }],
293            agenda_item: vec![AgendaItem {
294                id: "https://oparl.example.org/agendaitem/3271"
295                    .parse()
296                    .expect("value must be parseable as id"),
297                namespace: AgendaItemNamespaceUrl::Identifier,
298                meeting: Some(
299                    "https://oparl.example.org/meeting/281"
300                        .parse()
301                        .expect("value must be parseable as url"),
302                ),
303                number: Some("10.1".into()),
304                order: 0,
305                name: Some("Satzungsänderung für Ausschreibungen".into()),
306                public: Some(true),
307                consultation: Some(
308                    "https://oparl.example.org/consultation/1034"
309                        .parse()
310                        .expect("value must be parseable as url"),
311                ),
312                result: Some("Geändert beschlossen".into()),
313                resolution_text: Some("Der Beschluss weicht wie folgt vom Antrag ab: ...".into()),
314                resolution_file: None,
315                auxiliary_file: vec![],
316                start: None,
317                end: None,
318                license: None,
319                keyword: vec![],
320                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
321                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
322                web: None,
323                deleted: None,
324            }],
325            license: None,
326            keyword: vec![],
327            created: datetime!(2012-01-06 12:01:00 +01:00).into(),
328            modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
329            web: None,
330            deleted: None,
331        }
332    }
333
334    fn example_meeting_json() -> serde_json::Value {
335        json!({
336            "id": "https://oparl.example.org/meeting/281",
337            "type": "https://schema.oparl.org/1.1/Meeting",
338            "name": "4. Sitzung des Finanzausschusses",
339            "start": "2013-01-04T08:00:00+01:00",
340            "end": "2013-01-04T12:00:00+01:00",
341            "location": {
342                "id": "https://oparl.example.org/location/0",
343                "type": "https://schema.oparl.org/1.1/Location",
344                "description": "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt",
345                "created": "2012-01-06T12:01:00+01:00",
346                "modified": "2012-01-08T14:05:27+01:00",
347                "geojson": {
348                    "type": "Feature",
349                    "geometry": {
350                        "type": "Point",
351                        "coordinates": [
352                            50.1234,
353                            10.4321
354                        ]
355                    },
356                    "properties": {
357                        "name": "Rathausplatz"
358                    }
359                }
360            },
361            "organization": [
362                "https://oparl.example.org/organization/34"
363            ],
364            "invitation": {
365                "id": "https://oparl.example.org/files/57739",
366                "type": "https://schema.oparl.org/1.1/File",
367                "name": "Einladung",
368                "fileName": "einladung.pdf",
369                "mimeType": "application/pdf",
370                "date": "2012-01-08",
371                "modified": "2012-01-08T14:05:27+01:00",
372                "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
373                "size": 82930,
374                "accessUrl": "https://oparl.example.org/files/57739.pdf",
375                "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
376                "created": "2012-01-06T12:01:00+01:00"
377            },
378            "resultsProtocol": {
379                "id": "https://oparl.example.org/files/57739",
380                "type": "https://schema.oparl.org/1.1/File",
381                "name": "Protokoll",
382                "fileName": "protokoll.pdf",
383                "mimeType": "application/pdf",
384                "date": "2012-01-08",
385                "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
386                "size": 82930,
387                "accessUrl": "https://oparl.example.org/files/57739.pdf",
388                "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
389                "modified": "2012-01-08T14:05:27+01:00",
390                "created": "2012-01-06T12:01:00+01:00"
391            },
392            "verbatimProtocol": {
393                "id": "https://oparl.example.org/files/57739",
394                "type": "https://schema.oparl.org/1.1/File",
395                "name": "Wortprotokoll",
396                "fileName": "wortprotokoll.pdf",
397                "mimeType": "application/pdf",
398                "date": "2012-01-08",
399                "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
400                "size": 82930,
401                "accessUrl": "https://oparl.example.org/files/57739.pdf",
402                "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
403                "modified": "2012-01-08T14:05:27+01:00",
404                "created": "2012-01-08T14:05:27+01:00"
405            },
406            "auxiliaryFile": [
407                {
408                    "id": "https://oparl.example.org/files/57739",
409                    "type": "https://schema.oparl.org/1.1/File",
410                    "name": "Nachtrags-Tagesordnung",
411                    "fileName": "nachtrag-TO.pdf",
412                    "mimeType": "application/pdf",
413                    "date": "2012-01-08",
414                    "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
415                    "size": 82930,
416                    "accessUrl": "https://oparl.example.org/files/57739.pdf",
417                    "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
418                    "modified": "2012-01-08T14:05:27+01:00",
419                    "created": "2012-01-08T14:05:27+01:00"
420                }
421            ],
422            "agendaItem": [
423                {
424                    "id": "https://oparl.example.org/agendaitem/3271",
425                    "type": "https://schema.oparl.org/1.1/AgendaItem",
426                    "meeting": "https://oparl.example.org/meeting/281",
427                    "number": "10.1",
428                    "order": 0,
429                    "name": "Satzungsänderung für Ausschreibungen",
430                    "public": true,
431                    "consultation": "https://oparl.example.org/consultation/1034",
432                    "result": "Geändert beschlossen",
433                    "resolutionText": "Der Beschluss weicht wie folgt vom Antrag ab: ...",
434                    "created": "2012-01-06T12:01:00+01:00",
435                    "modified": "2012-01-08T14:05:27+01:00"
436                }
437            ],
438            "created": "2012-01-06T12:01:00+01:00",
439            "modified": "2012-01-08T14:05:27+01:00"
440        })
441    }
442
443    #[test]
444    fn serialize() {
445        assert_eq!(json!(example_meeting()), example_meeting_json());
446    }
447
448    #[test]
449    fn deserialize_good() {
450        let deserialized: Meeting = serde_json::from_value(example_meeting_json())
451            .expect("value must be deserializable as Meeting");
452        assert_eq!(deserialized, example_meeting());
453    }
454
455    #[test]
456    fn deserialize_bad() {
457        assert!(serde_json::from_value::<Meeting>(json!("xyzabcd")).is_err());
458        assert!(serde_json::from_value::<Meeting>(json!(true)).is_err());
459        assert!(serde_json::from_value::<Meeting>(json!(123)).is_err());
460    }
461}