use crate::{
AgendaItem, DateTime, File, Keyword, Location, MeetingUrl, Name, OrganizationUrl, Url,
namespace::MeetingNamespaceUrl,
};
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Meeting {
pub id: MeetingUrl,
#[serde(rename = "type")]
pub namespace: MeetingNamespaceUrl,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub meeting_state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cancelled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start: Option<DateTime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub end: Option<DateTime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub location: Option<Location>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub organization: Vec<OrganizationUrl>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub participant: Vec<Url>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub invitation: Option<File>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub results_protocol: Option<File>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verbatim_protocol: Option<File>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub auxiliary_file: Vec<File>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub agenda_item: Vec<AgendaItem>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<Url>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub keyword: Vec<Keyword>,
pub created: DateTime,
pub modified: DateTime,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub web: Option<Url>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deleted: Option<bool>,
#[serde(default, flatten)]
pub extensions: serde_json::Map<String, serde_json::Value>,
}
#[cfg(test)]
mod serde_tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use time::macros::{date, datetime};
use super::Meeting;
use crate::{
AgendaItem, File, Location, Sha1Sum,
namespace::{
AgendaItemNamespaceUrl, FileNamespaceUrl, LocationNamespaceUrl, MeetingNamespaceUrl,
},
};
fn example_meeting() -> Meeting {
let geojson_feature = {
let mut f =
geojson::Feature::from(geojson::Geometry::new(geojson::GeometryValue::Point {
coordinates: [50.1234, 10.4321].into(),
}));
f.set_property("name", "Rathausplatz");
f
};
Meeting {
id: "https://oparl.example.org/meeting/281"
.parse()
.expect("value must be parseable as id"),
namespace: MeetingNamespaceUrl::Identifier,
name: Some("4. Sitzung des Finanzausschusses".into()),
cancelled: None,
meeting_state: None,
start: Some(datetime!(2013 - 01 - 04 08:00:00 +01:00).into()),
end: Some(datetime!(2013 - 01 - 04 12:00:00 +01:00).into()),
location: Some(Location {
id: "https://oparl.example.org/location/0"
.parse()
.expect("value must be parseable as id"),
namespace: LocationNamespaceUrl::Identifier,
description: Some(
"Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt".to_string(),
),
geojson: Some(geojson_feature.into()),
street_address: None,
room: None,
postal_code: None,
sub_locality: None,
locality: None,
bodies: vec![],
organizations: vec![],
persons: vec![],
meetings: vec![],
papers: vec![],
license: None,
keyword: vec![],
created: datetime!(2012-01-06 12:01:00 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}),
organization: vec![
"https://oparl.example.org/organization/34"
.parse()
.expect("value must be parseable as url"),
],
participant: vec![],
invitation: Some(File {
id: "https://oparl.example.org/files/57739"
.parse()
.expect("value must be parseable as id"),
namespace: FileNamespaceUrl::Identifier,
file_name: Some("einladung.pdf".into()),
name: Some("Einladung".into()),
mime_type: Some("application/pdf".into()),
date: Some(date!(2012 - 01 - 08).into()),
size: Some(82930),
sha1_checksum: Some(Sha1Sum::from([
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
])),
sha512_checksum: None,
text: None,
access_url: "https://oparl.example.org/files/57739.pdf"
.parse()
.expect("value must be parseable as url"),
download_url: Some(
"https://oparl.example.org/files/download/57739.pdf"
.parse()
.expect("value must be parseable as url"),
),
external_service_url: None,
master_file: None,
derivative_file: vec![],
file_license: None,
meeting: vec![],
agenda_item: vec![],
paper: vec![],
license: None,
keyword: vec![],
created: datetime!(2012-01-06 12:01:00 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}),
results_protocol: Some(File {
id: "https://oparl.example.org/files/57739"
.parse()
.expect("value must be parseable as id"),
namespace: FileNamespaceUrl::Identifier,
file_name: Some("protokoll.pdf".into()),
name: Some("Protokoll".into()),
mime_type: Some("application/pdf".into()),
date: Some(date!(2012 - 01 - 08).into()),
size: Some(82930),
sha1_checksum: Some(Sha1Sum::from([
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
])),
sha512_checksum: None,
text: None,
access_url: "https://oparl.example.org/files/57739.pdf"
.parse()
.expect("value must be parseable as url"),
download_url: Some(
"https://oparl.example.org/files/download/57739.pdf"
.parse()
.expect("value must be parseable as url"),
),
external_service_url: None,
master_file: None,
derivative_file: vec![],
file_license: None,
meeting: vec![],
agenda_item: vec![],
paper: vec![],
license: None,
keyword: vec![],
created: datetime!(2012-01-06 12:01:00 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}),
verbatim_protocol: Some(File {
id: "https://oparl.example.org/files/57739"
.parse()
.expect("value must be parseable as id"),
namespace: FileNamespaceUrl::Identifier,
file_name: Some("wortprotokoll.pdf".into()),
name: Some("Wortprotokoll".into()),
mime_type: Some("application/pdf".into()),
date: Some(date!(2012 - 01 - 08).into()),
size: Some(82930),
sha1_checksum: Some(Sha1Sum::from([
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
])),
sha512_checksum: None,
text: None,
access_url: "https://oparl.example.org/files/57739.pdf"
.parse()
.expect("value must be parseable as url"),
download_url: Some(
"https://oparl.example.org/files/download/57739.pdf"
.parse()
.expect("value must be parseable as url"),
),
external_service_url: None,
master_file: None,
derivative_file: vec![],
file_license: None,
meeting: vec![],
agenda_item: vec![],
paper: vec![],
license: None,
keyword: vec![],
created: datetime!(2012-01-08 14:05:27 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}),
auxiliary_file: vec![File {
id: "https://oparl.example.org/files/57739"
.parse()
.expect("value must be parseable as id"),
namespace: FileNamespaceUrl::Identifier,
file_name: Some("nachtrag-TO.pdf".into()),
name: Some("Nachtrags-Tagesordnung".into()),
mime_type: Some("application/pdf".into()),
date: Some(date!(2012 - 01 - 08).into()),
size: Some(82930),
sha1_checksum: Some(Sha1Sum::from([
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
])),
sha512_checksum: None,
text: None,
access_url: "https://oparl.example.org/files/57739.pdf"
.parse()
.expect("value must be parseable as url"),
download_url: Some(
"https://oparl.example.org/files/download/57739.pdf"
.parse()
.expect("value must be parseable as url"),
),
external_service_url: None,
master_file: None,
derivative_file: vec![],
file_license: None,
meeting: vec![],
agenda_item: vec![],
paper: vec![],
license: None,
keyword: vec![],
created: datetime!(2012-01-08 14:05:27 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}],
agenda_item: vec![AgendaItem {
id: "https://oparl.example.org/agendaitem/3271"
.parse()
.expect("value must be parseable as id"),
namespace: AgendaItemNamespaceUrl::Identifier,
meeting: Some(
"https://oparl.example.org/meeting/281"
.parse()
.expect("value must be parseable as url"),
),
number: Some("10.1".into()),
order: 0,
name: Some("Satzungsänderung für Ausschreibungen".into()),
public: Some(true),
consultation: Some(
"https://oparl.example.org/consultation/1034"
.parse()
.expect("value must be parseable as url"),
),
result: Some("Geändert beschlossen".into()),
resolution_text: Some("Der Beschluss weicht wie folgt vom Antrag ab: ...".into()),
resolution_file: None,
auxiliary_file: vec![],
start: None,
end: None,
license: None,
keyword: vec![],
created: datetime!(2012-01-06 12:01:00 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}],
license: None,
keyword: vec![],
created: datetime!(2012-01-06 12:01:00 +01:00).into(),
modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
web: None,
deleted: None,
extensions: serde_json::Map::new(),
}
}
fn example_meeting_json() -> serde_json::Value {
json!({
"id": "https://oparl.example.org/meeting/281",
"type": "https://schema.oparl.org/1.1/Meeting",
"name": "4. Sitzung des Finanzausschusses",
"start": "2013-01-04T08:00:00+01:00",
"end": "2013-01-04T12:00:00+01:00",
"location": {
"id": "https://oparl.example.org/location/0",
"type": "https://schema.oparl.org/1.1/Location",
"description": "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt",
"created": "2012-01-06T12:01:00+01:00",
"modified": "2012-01-08T14:05:27+01:00",
"geojson": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
50.1234,
10.4321
]
},
"properties": {
"name": "Rathausplatz"
}
}
},
"organization": [
"https://oparl.example.org/organization/34"
],
"invitation": {
"id": "https://oparl.example.org/files/57739",
"type": "https://schema.oparl.org/1.1/File",
"name": "Einladung",
"fileName": "einladung.pdf",
"mimeType": "application/pdf",
"date": "2012-01-08",
"modified": "2012-01-08T14:05:27+01:00",
"sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"size": 82930,
"accessUrl": "https://oparl.example.org/files/57739.pdf",
"downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
"created": "2012-01-06T12:01:00+01:00"
},
"resultsProtocol": {
"id": "https://oparl.example.org/files/57739",
"type": "https://schema.oparl.org/1.1/File",
"name": "Protokoll",
"fileName": "protokoll.pdf",
"mimeType": "application/pdf",
"date": "2012-01-08",
"sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"size": 82930,
"accessUrl": "https://oparl.example.org/files/57739.pdf",
"downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
"modified": "2012-01-08T14:05:27+01:00",
"created": "2012-01-06T12:01:00+01:00"
},
"verbatimProtocol": {
"id": "https://oparl.example.org/files/57739",
"type": "https://schema.oparl.org/1.1/File",
"name": "Wortprotokoll",
"fileName": "wortprotokoll.pdf",
"mimeType": "application/pdf",
"date": "2012-01-08",
"sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"size": 82930,
"accessUrl": "https://oparl.example.org/files/57739.pdf",
"downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
"modified": "2012-01-08T14:05:27+01:00",
"created": "2012-01-08T14:05:27+01:00"
},
"auxiliaryFile": [
{
"id": "https://oparl.example.org/files/57739",
"type": "https://schema.oparl.org/1.1/File",
"name": "Nachtrags-Tagesordnung",
"fileName": "nachtrag-TO.pdf",
"mimeType": "application/pdf",
"date": "2012-01-08",
"sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"size": 82930,
"accessUrl": "https://oparl.example.org/files/57739.pdf",
"downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
"modified": "2012-01-08T14:05:27+01:00",
"created": "2012-01-08T14:05:27+01:00"
}
],
"agendaItem": [
{
"id": "https://oparl.example.org/agendaitem/3271",
"type": "https://schema.oparl.org/1.1/AgendaItem",
"meeting": "https://oparl.example.org/meeting/281",
"number": "10.1",
"order": 0,
"name": "Satzungsänderung für Ausschreibungen",
"public": true,
"consultation": "https://oparl.example.org/consultation/1034",
"result": "Geändert beschlossen",
"resolutionText": "Der Beschluss weicht wie folgt vom Antrag ab: ...",
"created": "2012-01-06T12:01:00+01:00",
"modified": "2012-01-08T14:05:27+01:00"
}
],
"created": "2012-01-06T12:01:00+01:00",
"modified": "2012-01-08T14:05:27+01:00"
})
}
#[test]
fn serialize() {
assert_eq!(json!(example_meeting()), example_meeting_json());
}
#[test]
fn deserialize_good() {
let deserialized: Meeting = serde_json::from_value(example_meeting_json())
.expect("value must be deserializable as Meeting");
assert_eq!(deserialized, example_meeting());
}
#[test]
fn deserialize_bad() {
assert!(serde_json::from_value::<Meeting>(json!("xyzabcd")).is_err());
assert!(serde_json::from_value::<Meeting>(json!(true)).is_err());
assert!(serde_json::from_value::<Meeting>(json!(123)).is_err());
}
}