1use serde::{Deserialize, Deserializer, Serialize};
2use std::collections::BTreeMap;
3use std::fmt;
4use std::str::FromStr;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct IdentifierParseError {
8 value: String,
9 prefix: &'static str,
10}
11
12impl fmt::Display for IdentifierParseError {
13 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
14 write!(
15 formatter,
16 "invalid identifier {:?}; expected canonical {}<positive integer> syntax",
17 self.value, self.prefix
18 )
19 }
20}
21
22impl std::error::Error for IdentifierParseError {}
23
24macro_rules! identifier {
25 ($name:ident, $prefix:literal) => {
26 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
27 #[serde(transparent)]
28 pub struct $name(String);
29
30 impl $name {
31 pub fn as_str(&self) -> &str {
32 &self.0
33 }
34
35 pub fn number(&self) -> u64 {
36 self.0[1..].parse().expect("validated identifiers contain a u64")
37 }
38 }
39
40 impl fmt::Display for $name {
41 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
42 formatter.write_str(&self.0)
43 }
44 }
45
46 impl FromStr for $name {
47 type Err = IdentifierParseError;
48
49 fn from_str(value: &str) -> Result<Self, Self::Err> {
50 let digits = value.strip_prefix($prefix).ok_or_else(|| IdentifierParseError {
51 value: value.to_owned(),
52 prefix: $prefix,
53 })?;
54 let canonical = !digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit()) && !digits.starts_with('0') && digits.parse::<u64>().is_ok();
55 if !canonical {
56 return Err(IdentifierParseError {
57 value: value.to_owned(),
58 prefix: $prefix,
59 });
60 }
61 Ok(Self(value.to_owned()))
62 }
63 }
64
65 impl<'de> Deserialize<'de> for $name {
66 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67 where
68 D: Deserializer<'de>,
69 {
70 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
71 }
72 }
73 };
74}
75
76identifier!(EntityId, "Q");
77identifier!(PropertyId, "P");
78identifier!(ReferenceId, "R");
79identifier!(StatementId, "S");
80identifier!(EntityTypeId, "T");
81
82pub type LocalizedMap = BTreeMap<String, LocalizedText>;
83
84#[derive(Clone, Debug, Deserialize, Serialize)]
85#[serde(deny_unknown_fields)]
86pub struct LocalizedText {
87 pub text: String,
88 pub references: Vec<ReferenceId>,
89}
90
91#[derive(Clone, Debug, Deserialize, Serialize)]
92#[serde(deny_unknown_fields)]
93pub struct EntityType {
94 pub id: EntityTypeId,
95 pub labels: LocalizedMap,
96 #[serde(default)]
97 pub descriptions: LocalizedMap,
98}
99
100#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
101#[serde(rename_all = "lowercase")]
102pub enum ValueType {
103 Entity,
104 String,
105 Integer,
106 Decimal,
107 Boolean,
108 Date,
109 Datetime,
110 Url,
111 Coordinate,
112}
113
114#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "lowercase")]
116pub enum Cardinality {
117 One,
118 #[default]
119 Many,
120}
121
122#[derive(Clone, Debug, Deserialize, Serialize)]
123#[serde(deny_unknown_fields)]
124pub struct Property {
125 pub id: PropertyId,
126 pub labels: LocalizedMap,
127 #[serde(default)]
128 pub descriptions: LocalizedMap,
129 pub subject_types: Vec<EntityTypeId>,
130 pub value_type: ValueType,
131 #[serde(default, deserialize_with = "deserialize_optional_non_null")]
132 pub target_types: Option<Vec<EntityTypeId>>,
133 #[serde(default)]
134 pub allowed_qualifiers: Vec<PropertyId>,
135 #[serde(default)]
136 pub cardinality: Cardinality,
137}
138
139#[derive(Clone, Debug, Deserialize, Serialize)]
140#[serde(deny_unknown_fields)]
141pub struct Classification {
142 pub value: EntityTypeId,
143 pub references: Vec<ReferenceId>,
144}
145
146#[derive(Clone, Debug, Deserialize, Serialize)]
147#[serde(deny_unknown_fields)]
148pub struct Statement {
149 pub id: StatementId,
150 pub property: PropertyId,
151 pub value: Value,
152 #[serde(default)]
153 pub qualifiers: Vec<Qualifier>,
154 pub references: Vec<ReferenceId>,
155}
156
157#[derive(Clone, Debug, Deserialize, Serialize)]
158#[serde(deny_unknown_fields)]
159pub struct Qualifier {
160 pub property: PropertyId,
161 pub value: Value,
162}
163
164#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
165#[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)]
166pub enum Value {
167 Entity { value: EntityId },
168 String { value: String },
169 Integer { value: i64 },
170 Decimal { value: String },
171 Boolean { value: bool },
172 Date { value: String },
173 Datetime { value: String },
174 Url { value: String },
175 Coordinate { latitude: String, longitude: String },
176}
177
178impl Value {
179 pub fn value_type(&self) -> ValueType {
180 match self {
181 Self::Entity { .. } => ValueType::Entity,
182 Self::String { .. } => ValueType::String,
183 Self::Integer { .. } => ValueType::Integer,
184 Self::Decimal { .. } => ValueType::Decimal,
185 Self::Boolean { .. } => ValueType::Boolean,
186 Self::Date { .. } => ValueType::Date,
187 Self::Datetime { .. } => ValueType::Datetime,
188 Self::Url { .. } => ValueType::Url,
189 Self::Coordinate { .. } => ValueType::Coordinate,
190 }
191 }
192}
193
194#[derive(Clone, Debug, Deserialize, Serialize)]
195#[serde(deny_unknown_fields)]
196pub struct Image {
197 pub url: String,
198 pub attribution: String,
199 #[serde(default, deserialize_with = "deserialize_optional_non_null")]
200 pub attribution_url: Option<String>,
201 pub references: Vec<ReferenceId>,
202}
203
204#[derive(Clone, Debug, Deserialize, Serialize)]
205#[serde(deny_unknown_fields)]
206pub struct Entity {
207 pub id: EntityId,
208 pub labels: LocalizedMap,
209 #[serde(default)]
210 pub descriptions: LocalizedMap,
211 pub entity_types: Vec<Classification>,
212 #[serde(default)]
213 pub images: Vec<Image>,
214 pub statements: Vec<Statement>,
215}
216
217#[derive(Clone, Debug, Deserialize, Serialize)]
218#[serde(deny_unknown_fields)]
219pub struct Reference {
220 pub id: ReferenceId,
221 pub url: String,
222 pub retrieved_at: String,
223 #[serde(default, deserialize_with = "deserialize_optional_non_null")]
224 pub archive_url: Option<String>,
225}
226
227#[derive(Clone, Debug, Deserialize, Serialize)]
228#[serde(deny_unknown_fields)]
229pub struct IdAllocation {
230 pub version: u64,
231 pub next: NextIds,
232}
233
234#[derive(Clone, Debug, Deserialize, Serialize)]
235#[serde(deny_unknown_fields)]
236pub struct NextIds {
237 pub entity: u64,
238 pub property: u64,
239 pub reference: u64,
240 pub entity_type: u64,
241}
242
243fn deserialize_optional_non_null<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
244where
245 D: Deserializer<'de>,
246 T: Deserialize<'de>,
247{
248 T::deserialize(deserializer).map(Some)
249}
250
251#[cfg(test)]
252mod tests {
253 use super::{EntityId, EntityTypeId, PropertyId, ReferenceId, StatementId};
254 use serde::de::DeserializeOwned;
255
256 fn parses<T: DeserializeOwned>(value: &str) -> bool {
257 serde_yaml::from_str::<T>(value).is_ok()
258 }
259
260 #[test]
261 fn typed_identifiers_accept_their_canonical_forms() {
262 assert!(parses::<EntityId>("Q1"));
263 assert!(parses::<PropertyId>("P2"));
264 assert!(parses::<ReferenceId>("R3"));
265 assert!(parses::<StatementId>("S4"));
266 assert!(parses::<EntityTypeId>("T5"));
267 }
268
269 #[test]
270 fn typed_identifiers_reject_noncanonical_forms() {
271 for value in ["Q0", "Q01", "Q-1", "Q", "P1", "q1", "1"] {
272 assert!(!parses::<EntityId>(value), "{value} unexpectedly parsed as an entity identifier");
273 }
274 }
275
276 #[test]
277 fn typed_identifiers_parse_from_strings() {
278 assert_eq!("Q42".parse::<EntityId>().expect("valid identifier").as_str(), "Q42");
279
280 for value in ["Q0", "Q01", "P1", "../Q1", "Q1.yaml"] {
281 assert!(value.parse::<EntityId>().is_err(), "{value} unexpectedly parsed as an entity identifier");
282 }
283 }
284}