1use 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}