oparl_types/
body.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::BodyNamespaceUrl, AgendaItemListId, BodyClassification, BodyId, ConsultationListId,
10    DateTime, EmailAddress, FileListId, Keyword, LegislativeTerm, LegislativeTermListId, Location,
11    LocationListId, MeetingListId, MembershipListId, Name, OrganizationListId, PaperListId,
12    PersonListId, SystemId,
13};
14
15#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct Body {
18    pub id: BodyId,
19
20    #[serde(rename = "type")]
21    pub namespace: BodyNamespaceUrl,
22
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub system: Option<SystemId>,
25
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub short_name: Option<Name>,
28
29    pub name: Name,
30
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub website: Option<Url>,
33
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub license: Option<Url>,
36
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub license_valid_since: Option<DateTime>,
39
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub oparl_since: Option<DateTime>,
42
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub ags: Option<String>,
45
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub rgs: Option<String>,
48
49    #[serde(default, skip_serializing_if = "Vec::is_empty")]
50    pub equivalent: Vec<Url>,
51
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub contact_email: Option<EmailAddress>,
54
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub contact_name: Option<Name>,
57
58    pub organization: OrganizationListId,
59
60    pub person: PersonListId,
61
62    pub meeting: MeetingListId,
63
64    pub paper: PaperListId,
65
66    #[serde(default, skip_serializing_if = "Vec::is_empty")]
67    pub legislative_term: Vec<LegislativeTerm>,
68
69    pub agenda_item: AgendaItemListId,
70
71    pub consultation: ConsultationListId,
72
73    pub file: FileListId,
74
75    pub location_list: LocationListId,
76
77    pub legislative_term_list: LegislativeTermListId,
78
79    pub membership: MembershipListId,
80
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub classification: Option<BodyClassification>,
83
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub location: Option<Location>,
86
87    #[serde(default, skip_serializing_if = "Vec::is_empty")]
88    pub keyword: Vec<Keyword>,
89
90    pub created: DateTime,
91
92    pub modified: DateTime,
93
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub web: Option<Url>,
96
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub deleted: Option<bool>,
99}
100
101#[cfg(test)]
102mod serde_tests {
103    use super::Body;
104    use crate::{
105        namespace::{BodyNamespaceUrl, LegislativeTermNamespaceUrl, LocationNamespaceUrl},
106        LegislativeTerm, Location,
107    };
108
109    use pretty_assertions::assert_eq;
110    use serde_json::json;
111    use time::macros::datetime;
112
113    fn example_body() -> Body {
114        let geojson_feature = {
115            let mut f =
116                geojson::Feature::from(geojson::Geometry::new(geojson::Value::Point(vec![
117                    50.1234, 10.4321,
118                ])));
119            f.set_property("name", "Rathausplatz");
120            f
121        };
122
123        Body {
124            id: "https://oparl.example.org/body/0"
125                .parse()
126                .expect("value must be parseable as id"),
127            namespace: BodyNamespaceUrl::Identifier,
128            system: Some(
129                "https://oparl.example.org/"
130                    .parse()
131                    .expect("value must be parseable as url"),
132            ),
133            short_name: Some("Köln".into()),
134            name: "Stadt Köln, kreisfreie Stadt".into(),
135            website: Some(
136                "http://www.beispielstadt.de/"
137                    .parse()
138                    .expect("value must be parseable as url"),
139            ),
140            license: Some(
141                "http://creativecommons.org/licenses/by/4.0/"
142                    .parse()
143                    .expect("value must be parseable as url"),
144            ),
145            license_valid_since: Some(datetime!(2014-01-01 00:00:00 +02:00).into()),
146            oparl_since: None,
147            ags: Some("05315000".to_string()),
148            rgs: Some("053150000000".to_string()),
149            equivalent: vec![
150                "http://d-nb.info/gnd/2015732-0"
151                    .parse()
152                    .expect("value must be parseable as url"),
153                "http://dbpedia.org/resource/Cologne"
154                    .parse()
155                    .expect("value must be parseable as url"),
156            ],
157            contact_email: Some(
158                "ris@beispielstadt.de"
159                    .parse()
160                    .expect("value must be parseable as email"),
161            ),
162            contact_name: Some(
163                "RIS-Betreuung"
164                    .parse()
165                    .expect("value must be parseable as name"),
166            ),
167            organization: "https://oparl.example.org/body/0/organizations/"
168                .parse()
169                .expect("value must be parseable as url"),
170            person: "https://oparl.example.org/body/0/persons/"
171                .parse()
172                .expect("value must be parseable as url"),
173            meeting: "https://oparl.example.org/body/0/meetings/"
174                .parse()
175                .expect("value must be parseable as url"),
176            paper: "https://oparl.example.org/body/0/papers/"
177                .parse()
178                .expect("value must be parseable as url"),
179            legislative_term: vec![LegislativeTerm {
180                id: "https://oparl.example.org/term/21"
181                    .parse()
182                    .expect("value must be parseable as id"),
183                namespace: LegislativeTermNamespaceUrl::Identifier,
184                body: Some(
185                    "https://oparl.example.org/body/0"
186                        .parse()
187                        .expect("value must be parseable as url"),
188                ),
189                name: Some(
190                    "21. Wahlperiode"
191                        .parse()
192                        .expect("value must be parseable as name"),
193                ),
194                start_date: Some(
195                    "2010-12-03"
196                        .parse()
197                        .expect("value must be parseable as date"),
198                ),
199                end_date: Some(
200                    "2013-12-03"
201                        .parse()
202                        .expect("value must be parseable as date"),
203                ),
204                license: None,
205                keyword: vec![],
206                created: datetime!(2014-01-08 14:28:31 +01:00).into(),
207                modified: datetime!(2014-01-08 14:28:31 +01:00).into(),
208                web: None,
209                deleted: None,
210            }],
211            agenda_item: "https://oparl.example.org/body/0/agendaItems/"
212                .parse()
213                .expect("value must be parseable as url"),
214            consultation: "https://oparl.example.org/body/0/consultations/"
215                .parse()
216                .expect("value must be parseable as url"),
217            file: "https://oparl.example.org/body/0/files/"
218                .parse()
219                .expect("value must be parseable as url"),
220            location_list: "https://oparl.example.org/body/0/location_list/"
221                .parse()
222                .expect("value must be parseable as url"),
223            legislative_term_list: "https://oparl.example.org/body/0/legislative_term_list/"
224                .parse()
225                .expect("value must be parseable as url"),
226            membership: "https://oparl.example.org/body/0/memberships/"
227                .parse()
228                .expect("value must be parseable as url"),
229            classification: Some("Kreisfreie Stadt".into()),
230            location: Some(Location {
231                id: "https://oparl.example.org/location/0"
232                    .parse()
233                    .expect("value must be parseable as id"),
234                namespace: LocationNamespaceUrl::Identifier,
235                description: Some(
236                    "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt".to_string(),
237                ),
238                geojson: Some(geojson_feature.into()),
239                street_address: None,
240                room: None,
241                postal_code: None,
242                sub_locality: None,
243                locality: None,
244                bodies: vec![],
245                organizations: vec![],
246                persons: vec![],
247                meetings: vec![],
248                papers: vec![],
249                license: None,
250                keyword: vec![],
251                created: datetime!(2014-01-08 14:28:31 +01:00).into(),
252                modified: datetime!(2014-01-08 14:28:31 +01:00).into(),
253                web: None,
254                deleted: None,
255            }),
256            keyword: vec![],
257            created: datetime!(2014-01-08 14:28:31 +01:00).into(),
258            modified: datetime!(2014-01-08 14:28:31 +01:00).into(),
259            web: None,
260            deleted: None,
261        }
262    }
263
264    fn example_body_json() -> serde_json::Value {
265        json!({
266            "id": "https://oparl.example.org/body/0",
267            "type": "https://schema.oparl.org/1.1/Body",
268            "system": "https://oparl.example.org/",
269            "contactEmail": "ris@beispielstadt.de",
270            "contactName": "RIS-Betreuung",
271            "ags": "05315000",
272            "rgs": "053150000000",
273            "equivalent": [
274                "http://d-nb.info/gnd/2015732-0",
275                "http://dbpedia.org/resource/Cologne"
276            ],
277            "shortName": "Köln",
278            "name": "Stadt Köln, kreisfreie Stadt",
279            "website": "http://www.beispielstadt.de/",
280            "license": "http://creativecommons.org/licenses/by/4.0/",
281            "licenseValidSince": "2014-01-01T00:00:00+02:00",
282            "organization": "https://oparl.example.org/body/0/organizations/",
283            "person": "https://oparl.example.org/body/0/persons/",
284            "meeting": "https://oparl.example.org/body/0/meetings/",
285            "paper": "https://oparl.example.org/body/0/papers/",
286            "agendaItem": "https://oparl.example.org/body/0/agendaItems/",
287            "consultation": "https://oparl.example.org/body/0/consultations/",
288            "file": "https://oparl.example.org/body/0/files/",
289            "locationList": "https://oparl.example.org/body/0/location_list/",
290            "membership": "https://oparl.example.org/body/0/memberships/",
291            "legislativeTermList": "https://oparl.example.org/body/0/legislative_term_list/",
292            "legislativeTerm": [
293                {
294                    "id": "https://oparl.example.org/term/21",
295                    "type": "https://schema.oparl.org/1.1/LegislativeTerm",
296                    "body": "https://oparl.example.org/body/0",
297                    "name": "21. Wahlperiode",
298                    "startDate": "2010-12-03",
299                    "endDate": "2013-12-03",
300                    "created": "2014-01-08T14:28:31+01:00",
301                    "modified": "2014-01-08T14:28:31+01:00"
302                }
303            ],
304            "location": {
305                "id": "https://oparl.example.org/location/0",
306                "type": "https://schema.oparl.org/1.1/Location",
307                "description": "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt",
308                "created": "2014-01-08T14:28:31+01:00",
309                "modified": "2014-01-08T14:28:31+01:00",
310                "geojson": {
311                    "type": "Feature",
312                    "geometry": {
313                        "type": "Point",
314                        "coordinates": [
315                            50.1234,
316                            10.4321
317                        ]
318                    },
319                    "properties": {
320                        "name": "Rathausplatz"
321                    }
322                }
323            },
324            "classification": "Kreisfreie Stadt",
325            "created": "2014-01-08T14:28:31+01:00",
326            "modified": "2014-01-08T14:28:31+01:00"
327        })
328    }
329
330    #[test]
331    fn serialize() {
332        assert_eq!(json!(example_body()), example_body_json());
333    }
334
335    #[test]
336    fn deserialize_good() {
337        let deserialized: Body = serde_json::from_value(example_body_json())
338            .expect("value must be deserializable as Body");
339        assert_eq!(deserialized, example_body());
340    }
341
342    #[test]
343    fn deserialize_bad() {
344        assert!(serde_json::from_value::<Body>(json!("xyzabcd")).is_err());
345        assert!(serde_json::from_value::<Body>(json!(true)).is_err());
346        assert!(serde_json::from_value::<Body>(json!(123)).is_err());
347    }
348}