knowledge_base_models/
property.rs1use crate::{EntityTypeId, LocalizedMap, PropertyId, deserialize_optional_non_null};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4
5#[derive(Clone, Debug, Deserialize, Serialize)]
6#[serde(deny_unknown_fields)]
7pub struct EntityType {
8 pub id: EntityTypeId,
9 pub labels: LocalizedMap,
10 #[serde(default)]
11 pub descriptions: LocalizedMap,
12}
13
14#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
15#[serde(rename_all = "lowercase")]
16pub enum ValueType {
17 Entity,
18 String,
19 Integer,
20 Decimal,
21 Quantity,
22 Boolean,
23 Date,
24 Datetime,
25 Url,
26 Coordinate,
27}
28
29#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
30#[serde(rename_all = "lowercase")]
31pub enum Cardinality {
32 One,
33 #[default]
34 Many,
35}
36
37#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
38#[serde(rename_all = "lowercase")]
39pub enum PropertyUsage {
40 Statement,
41 Qualifier,
42 Both,
43}
44
45impl PropertyUsage {
46 pub fn allows_statement(self) -> bool {
47 matches!(self, Self::Statement | Self::Both)
48 }
49
50 pub fn allows_qualifier(self) -> bool {
51 matches!(self, Self::Qualifier | Self::Both)
52 }
53}
54
55#[derive(Clone, Debug, Deserialize, Serialize)]
56#[serde(deny_unknown_fields)]
57pub struct Property {
58 pub id: PropertyId,
59 pub labels: LocalizedMap,
60 #[serde(default)]
61 pub descriptions: LocalizedMap,
62 pub subject_types: Vec<EntityTypeId>,
63 pub value_type: ValueType,
64 pub usage: PropertyUsage,
65 #[serde(default, deserialize_with = "deserialize_optional_non_null")]
66 pub target_types: Option<Vec<EntityTypeId>>,
67 #[serde(default)]
68 pub allowed_qualifiers: Vec<PropertyId>,
69 #[serde(default)]
70 pub cardinality: Cardinality,
71 #[serde(default)]
72 pub external_ids: BTreeMap<String, Vec<String>>,
73}
74
75#[cfg(test)]
76mod tests {
77 use super::{Property, PropertyUsage};
78
79 fn property(usage: &str) -> String {
80 format!("id: P1\nlabels:\n en:\n text: population\n references: [R1]\nsubject_types: [T1]\nvalue_type: integer\nusage: {usage}\n")
81 }
82
83 #[test]
84 fn property_usage_serializes_and_deserializes_all_variants() {
85 for (source, usage) in [
86 ("statement", PropertyUsage::Statement),
87 ("qualifier", PropertyUsage::Qualifier),
88 ("both", PropertyUsage::Both),
89 ] {
90 let parsed: Property = serde_yaml::from_str(&property(source)).expect("property parses");
91 assert_eq!(parsed.usage, usage);
92 assert_eq!(serde_yaml::to_string(&usage).expect("usage serializes"), format!("{source}\n"));
93 }
94 }
95
96 #[test]
97 fn property_usage_is_required_and_rejects_unknown_values() {
98 let missing = property("statement").replace("usage: statement\n", "");
99 assert!(serde_yaml::from_str::<Property>(&missing).is_err());
100 assert!(serde_yaml::from_str::<Property>(&property("context")).is_err());
101 }
102
103 #[test]
104 fn external_ids_default_to_empty_and_preserve_namespaces_and_values() {
105 let parsed: Property = serde_yaml::from_str(&property("statement")).expect("property parses");
106 assert!(parsed.external_ids.is_empty());
107
108 let source = format!("{}external_ids:\n wikidata: [P1082, P2046]\n osm: []\n", property("statement"));
109 let parsed: Property = serde_yaml::from_str(&source).expect("property parses");
110 assert_eq!(parsed.external_ids["wikidata"], ["P1082", "P2046"]);
111 assert!(parsed.external_ids["osm"].is_empty());
112 let serialized = serde_yaml::to_string(&parsed).expect("property serializes");
113 assert!(serialized.contains("wikidata:\n - P1082\n - P2046"));
114 assert!(serialized.contains("osm: []"));
115 }
116}