oparl_types/
organization.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    date::Date, namespace::OrganizationNamespaceUrl, BodyId, ConsultationId, DateTime, Keyword,
10    Location, MeetingListId, MembershipId, Name, OrganizationClassification, OrganizationId,
11    OrganizationType, Post,
12};
13
14#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Organization {
17    pub id: OrganizationId,
18
19    #[serde(rename = "type")]
20    pub namespace: OrganizationNamespaceUrl,
21
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub body: Option<BodyId>,
24
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub name: Option<Name>,
27
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    pub membership: Vec<MembershipId>,
30
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub meeting: Option<MeetingListId>,
33
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub consultation: Option<ConsultationId>,
36
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub short_name: Option<Name>,
39
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    pub post: Vec<Post>,
42
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub sub_organization_of: Option<OrganizationId>,
45
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub organization_type: Option<OrganizationType>,
48
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub classification: Option<OrganizationClassification>,
51
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub start_date: Option<Date>,
54
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub end_date: Option<Date>,
57
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub website: Option<Url>,
60
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub location: Option<Location>,
63
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub external_body: Option<BodyId>,
66
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub license: Option<Url>,
69
70    #[serde(default, skip_serializing_if = "Vec::is_empty")]
71    pub keyword: Vec<Keyword>,
72
73    pub created: DateTime,
74
75    pub modified: DateTime,
76
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub web: Option<Url>,
79
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub deleted: Option<bool>,
82}
83
84#[cfg(test)]
85mod serde_tests {
86    use super::Organization;
87    use crate::{
88        namespace::{LocationNamespaceUrl, OrganizationNamespaceUrl},
89        Location, OrganizationType,
90    };
91
92    use pretty_assertions::assert_eq;
93    use serde_json::json;
94    use time::macros::{date, datetime};
95
96    fn example_organization() -> Organization {
97        let geojson_feature = {
98            let mut f =
99                geojson::Feature::from(geojson::Geometry::new(geojson::Value::Point(vec![
100                    50.1234, 10.4321,
101                ])));
102            f.set_property("name", "Rathausplatz");
103            f
104        };
105
106        Organization {
107            id: "https://oparl.example.org/organization/34"
108                .parse()
109                .expect("value must be parseable as id"),
110            namespace: OrganizationNamespaceUrl::Identifier,
111            body: Some(
112                "https://oparl.example.org/bodies/1"
113                    .parse()
114                    .expect("value must be parseable as url"),
115            ),
116            name: Some("Ausschuss für Haushalt und Finanzen".into()),
117            membership: vec![
118                "https://oparl.example.org/membership/27"
119                    .parse()
120                    .expect("value must be parseable as url"),
121                "https://oparl.example.org/membership/48"
122                    .parse()
123                    .expect("value must be parseable as url"),
124                "https://oparl.example.org/membership/57"
125                    .parse()
126                    .expect("value must be parseable as url"),
127            ],
128            meeting: Some(
129                "https://oparl.example.org/organization/34/meetings"
130                    .parse()
131                    .expect("value must be parseable as url"),
132            ),
133            consultation: None,
134            short_name: Some("Finanzausschuss".into()),
135            post: vec![
136                "Vorsitzender".into(),
137                "1. Stellvertreter".into(),
138                "Mitglied".into(),
139            ],
140            sub_organization_of: None,
141            organization_type: Some(OrganizationType::Council),
142            classification: Some("Ausschuss".into()),
143            start_date: Some(date!(2012 - 07 - 17).into()),
144            end_date: None,
145            website: None,
146            location: Some(Location {
147                id: "https://oparl.example.org/location/0"
148                    .parse()
149                    .expect("value must be parseable as id"),
150                namespace: LocationNamespaceUrl::Identifier,
151                description: Some(
152                    "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt".to_string(),
153                ),
154                geojson: Some(geojson_feature.into()),
155                street_address: None,
156                room: None,
157                postal_code: None,
158                sub_locality: None,
159                locality: None,
160                bodies: vec![],
161                organizations: vec![],
162                persons: vec![],
163                meetings: vec![],
164                papers: vec![],
165                license: None,
166                keyword: vec![],
167                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
168                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
169                web: None,
170                deleted: None,
171            }),
172            external_body: None,
173            license: None,
174            keyword: vec!["finanzen".into(), "haushalt".into()],
175            created: datetime!(2012-07-16 00:00:00 +02:00).into(),
176            modified: datetime!(2012-08-16 12:34:56 +02:00).into(),
177            web: None,
178            deleted: None,
179        }
180    }
181
182    fn example_organization_json() -> serde_json::Value {
183        json!({
184            "id": "https://oparl.example.org/organization/34",
185            "type": "https://schema.oparl.org/1.1/Organization",
186            "body": "https://oparl.example.org/bodies/1",
187            "name": "Ausschuss für Haushalt und Finanzen",
188            "shortName": "Finanzausschuss",
189            "startDate": "2012-07-17",
190            "organizationType": "Gremium",
191            "location": {
192                "id": "https://oparl.example.org/location/0",
193                "type": "https://schema.oparl.org/1.1/Location",
194                "description": "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt",
195                "created": "2012-01-06T12:01:00+01:00",
196                "modified": "2012-01-08T14:05:27+01:00",
197                "geojson": {
198                    "type": "Feature",
199                    "geometry": {
200                        "type": "Point",
201                        "coordinates": [
202                            50.1234,
203                            10.4321
204                        ]
205                    },
206                    "properties": {
207                        "name": "Rathausplatz"
208                    }
209                }
210            },
211            "post": [
212                "Vorsitzender",
213                "1. Stellvertreter",
214                "Mitglied"
215            ],
216            "meeting": "https://oparl.example.org/organization/34/meetings",
217            "membership": [
218                "https://oparl.example.org/membership/27",
219                "https://oparl.example.org/membership/48",
220                "https://oparl.example.org/membership/57"
221            ],
222            "classification": "Ausschuss",
223            "keyword": [
224                "finanzen",
225                "haushalt"
226            ],
227            "created": "2012-07-16T00:00:00+02:00",
228            "modified": "2012-08-16T12:34:56+02:00"
229        })
230    }
231
232    #[test]
233    fn serialize() {
234        assert_eq!(json!(example_organization()), example_organization_json());
235    }
236
237    #[test]
238    fn deserialize_good() {
239        let deserialized: Organization = serde_json::from_value(example_organization_json())
240            .expect("value must be deserializable as Organization");
241        assert_eq!(deserialized, example_organization());
242    }
243
244    #[test]
245    fn deserialize_bad() {
246        assert!(serde_json::from_value::<Organization>(json!("xyzabcd")).is_err());
247        assert!(serde_json::from_value::<Organization>(json!(true)).is_err());
248        assert!(serde_json::from_value::<Organization>(json!(123)).is_err());
249    }
250}