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