ifc_element_type/
authoring.rs1use ifc_model::guid::Guid;
24use ifc_model::{Entity, EntityId, Transaction, Value};
25
26use crate::table::{ElementType, Family};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum ElementTypeError {
32 Invalid {
34 entity: &'static str,
36 attribute: &'static str,
38 value: String,
40 },
41}
42
43impl std::fmt::Display for ElementTypeError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 let Self::Invalid {
46 entity,
47 attribute,
48 value,
49 } = self;
50 write!(f, "{entity}.{attribute}: {value}")
51 }
52}
53
54impl std::error::Error for ElementTypeError {}
55
56pub type ElementTypeResult<T> = Result<T, ElementTypeError>;
58
59fn invalid(
60 entity: &'static str,
61 attribute: &'static str,
62 value: impl Into<String>,
63) -> ElementTypeError {
64 ElementTypeError::Invalid {
65 entity,
66 attribute,
67 value: value.into(),
68 }
69}
70
71#[derive(Debug, Clone, Copy, Default)]
78pub struct TypeDraft<'a> {
79 pub name: Option<&'a str>,
81 pub description: Option<&'a str>,
83 pub applicable_occurrence: Option<&'a str>,
85 pub maps_or_identification: Option<Slot6<'a>>,
87 pub tag_or_long_description: Option<&'a str>,
89 pub fallback: Option<&'a str>,
92}
93
94#[derive(Debug, Clone, Copy)]
96pub enum Slot6<'a> {
97 RepresentationMaps(&'a [EntityId]),
99 Identification(&'a str),
101}
102
103pub fn create_type(
117 tx: &mut Transaction,
118 kind: ElementType,
119 global_id: &str,
120 predefined_type: Option<&str>,
121 draft: TypeDraft<'_>,
122) -> ElementTypeResult<EntityId> {
123 let entity = kind.type_name;
124 if Guid::parse(global_id).is_none() {
125 return Err(invalid(entity, "GlobalId", global_id));
126 }
127 if blank(draft.name) {
132 return Err(invalid(entity, "Name", "NameRequired"));
133 }
134
135 match predefined_type {
136 None if !kind.predefined_optional => {
137 return Err(invalid(entity, "PredefinedType", "required"));
138 }
139 Some(token) if !kind.members.contains(&token) => {
140 return Err(invalid(entity, "PredefinedType", token));
141 }
142 Some(token) if token == "USERDEFINED" && blank(draft.fallback) => {
143 return Err(invalid(
144 entity,
145 kind.fallback_attr,
146 "required by USERDEFINED",
147 ));
148 }
149 _ => {}
150 }
151
152 let mut attrs = vec![Value::Null; kind.arity];
153 attrs[0] = Value::Text(global_id.into());
154 attrs[2] = text(draft.name);
155 attrs[3] = text(draft.description);
156 attrs[4] = text(draft.applicable_occurrence);
157
158 match (draft.maps_or_identification, kind.family) {
159 (Some(Slot6::RepresentationMaps(maps)), Family::Element) => {
160 if maps.is_empty() {
161 return Err(invalid(entity, "RepresentationMaps", "empty"));
162 }
163 attrs[6] = Value::List(maps.iter().copied().map(Value::Ref).collect());
164 }
165 (Some(Slot6::Identification(id)), Family::ResourceOrProcess) => {
166 attrs[6] = Value::Text(id.into());
167 }
168 (Some(Slot6::RepresentationMaps(_)), Family::ResourceOrProcess) => {
169 return Err(invalid(entity, "Identification", "expected text, got maps"));
170 }
171 (Some(Slot6::Identification(_)), Family::Element) => {
172 return Err(invalid(
173 entity,
174 "RepresentationMaps",
175 "expected maps, got text",
176 ));
177 }
178 (None, _) => {}
179 }
180
181 attrs[7] = text(draft.tag_or_long_description);
182 attrs[kind.fallback_slot] = text(draft.fallback);
183 attrs[kind.predefined_slot] = predefined_type.map_or(Value::Null, |t| Value::Enum(t.into()));
184
185 Ok(tx.create(Entity::new(entity, attrs)))
186}
187
188fn text(value: Option<&str>) -> Value {
189 value.map_or(Value::Null, |v| Value::Text(v.into()))
190}
191
192fn blank(value: Option<&str>) -> bool {
193 value.is_none_or(|v| v.trim().is_empty())
194}