oparl_types/
location.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 geojson::GeoJson;
7use url::Url;
8
9use crate::{
10    namespace::LocationNamespaceUrl, DateTime, Keyword, LocationId, MeetingId, OrganizationId,
11    PaperId, PersonId,
12};
13
14#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Location {
17    pub id: LocationId,
18
19    #[serde(rename = "type")]
20    pub namespace: LocationNamespaceUrl,
21
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub description: Option<String>,
24
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub geojson: Option<GeoJson>,
27
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    pub street_address: Option<String>,
30
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub room: Option<String>,
33
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub postal_code: Option<String>,
36
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub sub_locality: Option<String>,
39
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub locality: Option<String>,
42
43    #[serde(default, skip_serializing_if = "Vec::is_empty")]
44    pub bodies: Vec<Url>,
45
46    #[serde(default, skip_serializing_if = "Vec::is_empty")]
47    pub organizations: Vec<OrganizationId>,
48
49    #[serde(default, skip_serializing_if = "Vec::is_empty")]
50    pub persons: Vec<PersonId>,
51
52    #[serde(default, skip_serializing_if = "Vec::is_empty")]
53    pub meetings: Vec<MeetingId>,
54
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub papers: Vec<PaperId>,
57
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub license: Option<Url>,
60
61    #[serde(default, skip_serializing_if = "Vec::is_empty")]
62    pub keyword: Vec<Keyword>,
63
64    pub created: DateTime,
65
66    pub modified: DateTime,
67
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub web: Option<Url>,
70
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub deleted: Option<bool>,
73}
74
75#[cfg(test)]
76mod serde_tests {
77    use super::Location;
78    use crate::namespace::LocationNamespaceUrl;
79
80    use pretty_assertions::assert_eq;
81    use serde_json::json;
82    use time::macros::datetime;
83
84    fn example_location() -> Location {
85        let geojson_feature = {
86            let mut f =
87                geojson::Feature::from(geojson::Geometry::new(geojson::Value::Point(vec![
88                    50.1234, 10.4321,
89                ])));
90            f.set_property("name", "Rathausplatz");
91            f
92        };
93
94        Location {
95            id: "https://oparl.example.org/location/0"
96                .parse()
97                .expect("value must be parseable as id"),
98            namespace: LocationNamespaceUrl::Identifier,
99            description: Some(
100                "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt".to_string(),
101            ),
102            geojson: Some(geojson_feature.into()),
103            street_address: None,
104            room: None,
105            postal_code: None,
106            sub_locality: None,
107            locality: None,
108            bodies: vec![],
109            organizations: vec![],
110            persons: vec![],
111            meetings: vec![],
112            papers: vec![],
113            license: None,
114            keyword: vec![],
115            created: datetime!(2014-01-08 14:28:31 +01:00).into(),
116            modified: datetime!(2014-01-08 14:28:31 +01:00).into(),
117            web: None,
118            deleted: None,
119        }
120    }
121
122    fn example_location_json() -> serde_json::Value {
123        json!({
124            "id": "https://oparl.example.org/location/0",
125            "type": "https://schema.oparl.org/1.1/Location",
126            "description": "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt",
127            "created": "2014-01-08T14:28:31+01:00",
128            "modified": "2014-01-08T14:28:31+01:00",
129            "geojson": {
130                "type": "Feature",
131                "geometry": {
132                    "type": "Point",
133                    "coordinates": [
134                        50.1234,
135                        10.4321
136                    ]
137                },
138                "properties": {
139                    "name": "Rathausplatz"
140                }
141            }
142        })
143    }
144
145    #[test]
146    fn serialize() {
147        assert_eq!(json!(example_location()), example_location_json());
148    }
149
150    #[test]
151    fn deserialize_good() {
152        let deserialized: Location = serde_json::from_value(example_location_json())
153            .expect("value must be deserializable as Location");
154        assert_eq!(deserialized, example_location());
155    }
156
157    #[test]
158    fn deserialize_bad() {
159        assert!(serde_json::from_value::<Location>(json!("xyzabcd")).is_err());
160        assert!(serde_json::from_value::<Location>(json!(true)).is_err());
161        assert!(serde_json::from_value::<Location>(json!(123)).is_err());
162    }
163}