openbim_ifcx/
model.rs

1//! Serde types for one IFCX file, following `schema/ifcx.tsp` of the
2//! `ifcx_alpha` draft.
3//!
4//! Every struct keeps fields it does not know in `extra`, and optional fields
5//! stay `None` when absent, so a file read and written again keeps its
6//! content. Maps keep their key order.
7
8use indexmap::IndexMap;
9use serde::{Deserialize, Serialize};
10use serde_json::{Map, Number, Value};
11
12/// Fields this crate does not model, kept in input order for round trip.
13pub type Extra = Map<String, Value>;
14
15/// One IFCX file: a single layer of a dataset.
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct IfcxFile {
18    pub header: IfcxHeader,
19    pub imports: Vec<ImportNode>,
20    /// Attribute value descriptions, keyed by namespaced attribute id.
21    pub schemas: IndexMap<String, IfcxSchema>,
22    /// Nodes in file order. Several nodes may share a path; layering merges
23    /// them later.
24    pub data: Vec<IfcxNode>,
25    #[serde(flatten)]
26    pub extra: Extra,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct IfcxHeader {
31    /// Dataset identifier, a name or a path.
32    pub id: String,
33    /// Draft revision of the format, `ifcx_alpha` for every known file.
34    #[serde(rename = "ifcxVersion")]
35    pub ifcx_version: String,
36    #[serde(rename = "dataVersion")]
37    pub data_version: String,
38    pub author: String,
39    /// ISO 8601 date or date-time, kept as written.
40    pub timestamp: String,
41    #[serde(flatten)]
42    pub extra: Extra,
43}
44
45/// A reference to another IFCX file that this layer builds on.
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct ImportNode {
48    pub uri: String,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub integrity: Option<String>,
51    #[serde(flatten)]
52    pub extra: Extra,
53}
54
55/// One node opinion. `children` and `inherits` map a local name to a
56/// referenced path; a `None` value deletes that entry when layers merge.
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub struct IfcxNode {
59    pub path: String,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub children: Option<IndexMap<String, Option<String>>>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub inherits: Option<IndexMap<String, Option<String>>>,
64    /// Attribute values keyed by namespaced attribute id, as raw JSON.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub attributes: Option<IndexMap<String, Value>>,
67    #[serde(flatten)]
68    pub extra: Extra,
69}
70
71/// Describes the values allowed for one attribute id.
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct IfcxSchema {
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub uri: Option<String>,
76    pub value: IfcxValueDescription,
77    #[serde(flatten)]
78    pub extra: Extra,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct IfcxValueDescription {
83    #[serde(rename = "dataType")]
84    pub data_type: DataType,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub optional: Option<bool>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub inherits: Option<Vec<String>>,
89    /// Quantity kind name, kept as a string so new kinds still read.
90    #[serde(
91        rename = "quantityKind",
92        default,
93        skip_serializing_if = "Option::is_none"
94    )]
95    pub quantity_kind: Option<String>,
96    #[serde(
97        rename = "enumRestrictions",
98        default,
99        skip_serializing_if = "Option::is_none"
100    )]
101    pub enum_restrictions: Option<EnumRestrictions>,
102    #[serde(
103        rename = "arrayRestrictions",
104        default,
105        skip_serializing_if = "Option::is_none"
106    )]
107    pub array_restrictions: Option<Box<ArrayRestrictions>>,
108    #[serde(
109        rename = "objectRestrictions",
110        default,
111        skip_serializing_if = "Option::is_none"
112    )]
113    pub object_restrictions: Option<ObjectRestrictions>,
114    #[serde(flatten)]
115    pub extra: Extra,
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct EnumRestrictions {
120    pub options: Vec<String>,
121    #[serde(flatten)]
122    pub extra: Extra,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub struct ArrayRestrictions {
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub min: Option<Number>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub max: Option<Number>,
131    pub value: IfcxValueDescription,
132    #[serde(flatten)]
133    pub extra: Extra,
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct ObjectRestrictions {
138    pub values: IndexMap<String, IfcxValueDescription>,
139    #[serde(flatten)]
140    pub extra: Extra,
141}
142
143/// The `dataType` of a value description. Names this crate does not know
144/// read as [`DataType::Other`] and are written back unchanged.
145#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
146#[serde(from = "String", into = "String")]
147pub enum DataType {
148    Real,
149    Boolean,
150    Integer,
151    String,
152    DateTime,
153    Enum,
154    Array,
155    Object,
156    Reference,
157    Blob,
158    Other(String),
159}
160
161impl DataType {
162    pub fn as_str(&self) -> &str {
163        match self {
164            Self::Real => "Real",
165            Self::Boolean => "Boolean",
166            Self::Integer => "Integer",
167            Self::String => "String",
168            Self::DateTime => "DateTime",
169            Self::Enum => "Enum",
170            Self::Array => "Array",
171            Self::Object => "Object",
172            Self::Reference => "Reference",
173            Self::Blob => "Blob",
174            Self::Other(name) => name,
175        }
176    }
177}
178
179impl From<String> for DataType {
180    fn from(name: String) -> Self {
181        match name.as_str() {
182            "Real" => Self::Real,
183            "Boolean" => Self::Boolean,
184            "Integer" => Self::Integer,
185            "String" => Self::String,
186            "DateTime" => Self::DateTime,
187            "Enum" => Self::Enum,
188            "Array" => Self::Array,
189            "Object" => Self::Object,
190            "Reference" => Self::Reference,
191            "Blob" => Self::Blob,
192            _ => Self::Other(name),
193        }
194    }
195}
196
197impl From<DataType> for String {
198    fn from(data_type: DataType) -> Self {
199        match data_type {
200            DataType::Other(name) => name,
201            known => known.as_str().to_owned(),
202        }
203    }
204}