1mod table;
17
18use ifc_model::guid::Guid;
19use ifc_model::{Entity, EntityId, Transaction, Value};
20
21pub use table::{ALL, IFCBRIDGE, IFCBRIDGEPART, IFCFACILITY, IFCFACILITYPARTCOMMON};
22pub use table::{IFCMARINEFACILITY, IFCMARINEPART, IFCRAILWAY, IFCRAILWAYPART};
23pub use table::{IFCROAD, IFCROADPART};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Facility {
28 pub type_name: &'static str,
30 pub arity: usize,
32 pub predefined_slot: Option<usize>,
34 pub usage_slot: Option<usize>,
36 pub members: &'static [&'static str],
38}
39
40impl Facility {
41 #[must_use]
45 pub const fn is_part(&self) -> bool {
46 self.usage_slot.is_some()
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52#[non_exhaustive]
53pub enum FacilityError {
54 MalformedGuid {
56 entity: &'static str,
58 offered: String,
60 },
61 UnknownToken {
63 entity: &'static str,
65 attribute: &'static str,
67 offered: String,
69 },
70 NoPredefinedType {
72 entity: &'static str,
74 },
75 MissingUsageType {
77 entity: &'static str,
79 },
80 UnexpectedUsageType {
82 entity: &'static str,
84 },
85 UserDefinedWithoutObjectType {
87 entity: &'static str,
89 attribute: &'static str,
91 },
92}
93
94impl core::fmt::Display for FacilityError {
95 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96 match self {
97 Self::MalformedGuid { entity, offered } => {
98 write!(f, "{entity}: GlobalId {offered:?} is not an IFC GUID")
99 }
100 Self::UnknownToken {
101 entity,
102 attribute,
103 offered,
104 } => {
105 write!(f, "{entity}: {offered:?} is not a {attribute} of {entity}")
106 }
107 Self::NoPredefinedType { entity } => {
108 write!(f, "{entity} declares no PredefinedType")
109 }
110 Self::MissingUsageType { entity } => {
111 write!(f, "{entity}: UsageType is mandatory on a facility part")
112 }
113 Self::UnexpectedUsageType { entity } => {
114 write!(f, "{entity} declares no UsageType")
115 }
116 Self::UserDefinedWithoutObjectType { entity, attribute } => {
117 write!(
118 f,
119 "{entity}: {attribute} is USERDEFINED without an ObjectType naming it"
120 )
121 }
122 }
123 }
124}
125
126impl std::error::Error for FacilityError {}
127
128pub type FacilityResult<T> = Result<T, FacilityError>;
130
131#[derive(Debug, Clone, Copy, Default)]
133pub struct FacilityDraft<'a> {
134 pub name: Option<&'a str>,
136 pub description: Option<&'a str>,
138 pub object_type: Option<&'a str>,
140 pub placement: Option<EntityId>,
142 pub long_name: Option<&'a str>,
144 pub composition: Option<&'a str>,
146 pub usage: Option<&'a str>,
148}
149
150const USAGE_TOKENS: &[&str] = &[
151 "LATERAL",
152 "LONGITUDINAL",
153 "REGION",
154 "VERTICAL",
155 "USERDEFINED",
156 "NOTDEFINED",
157];
158
159fn names_itself(draft: &FacilityDraft<'_>) -> bool {
160 draft.object_type.is_some_and(|s| !s.trim().is_empty())
161}
162
163pub fn create_facility(
176 tx: &mut Transaction,
177 kind: Facility,
178 global_id: &str,
179 predefined_type: Option<&str>,
180 draft: FacilityDraft<'_>,
181) -> FacilityResult<EntityId> {
182 let entity = kind.type_name;
183 if Guid::parse(global_id).is_none() {
184 return Err(FacilityError::MalformedGuid {
185 entity,
186 offered: global_id.to_owned(),
187 });
188 }
189
190 let mut attributes = vec![Value::Null; kind.arity];
191 attributes[0] = Value::Text(global_id.into());
192 attributes[2] = text(draft.name);
193 attributes[3] = text(draft.description);
194 attributes[4] = text(draft.object_type);
195 attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
196 attributes[7] = text(draft.long_name);
197 attributes[8] = draft
198 .composition
199 .map_or(Value::Null, |t| Value::Enum(t.into()));
200
201 match (kind.usage_slot, draft.usage) {
202 (Some(slot), Some(token)) => {
203 if !USAGE_TOKENS.contains(&token) {
204 return Err(FacilityError::UnknownToken {
205 entity,
206 attribute: "UsageType",
207 offered: token.to_owned(),
208 });
209 }
210 if token == "USERDEFINED" && !names_itself(&draft) {
211 return Err(FacilityError::UserDefinedWithoutObjectType {
212 entity,
213 attribute: "UsageType",
214 });
215 }
216 attributes[slot] = Value::Enum(token.into());
217 }
218 (Some(_), None) => return Err(FacilityError::MissingUsageType { entity }),
219 (None, Some(_)) => return Err(FacilityError::UnexpectedUsageType { entity }),
220 (None, None) => {}
221 }
222
223 match (kind.predefined_slot, predefined_type) {
224 (Some(slot), Some(token)) => {
225 if !kind.members.contains(&token) {
226 return Err(FacilityError::UnknownToken {
227 entity,
228 attribute: "PredefinedType",
229 offered: token.to_owned(),
230 });
231 }
232 if token == "USERDEFINED" && !names_itself(&draft) {
233 return Err(FacilityError::UserDefinedWithoutObjectType {
234 entity,
235 attribute: "PredefinedType",
236 });
237 }
238 attributes[slot] = Value::Enum(token.into());
239 }
240 (None, Some(_)) => return Err(FacilityError::NoPredefinedType { entity }),
241 (Some(_), None) | (None, None) => {}
242 }
243
244 Ok(tx.create(Entity::new(entity, attributes)))
245}
246
247fn text(value: Option<&str>) -> Value {
248 value.map_or(Value::Null, |t| Value::Text(t.into()))
249}