mod table;
use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId, Transaction, Value};
pub use table::{ALL, IFCBRIDGE, IFCBRIDGEPART, IFCFACILITY, IFCFACILITYPARTCOMMON};
pub use table::{IFCMARINEFACILITY, IFCMARINEPART, IFCRAILWAY, IFCRAILWAYPART};
pub use table::{IFCROAD, IFCROADPART};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Facility {
pub type_name: &'static str,
pub arity: usize,
pub predefined_slot: Option<usize>,
pub usage_slot: Option<usize>,
pub members: &'static [&'static str],
}
impl Facility {
#[must_use]
pub const fn is_part(&self) -> bool {
self.usage_slot.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FacilityError {
MalformedGuid {
entity: &'static str,
offered: String,
},
UnknownToken {
entity: &'static str,
attribute: &'static str,
offered: String,
},
NoPredefinedType {
entity: &'static str,
},
MissingUsageType {
entity: &'static str,
},
UnexpectedUsageType {
entity: &'static str,
},
UserDefinedWithoutObjectType {
entity: &'static str,
attribute: &'static str,
},
}
impl core::fmt::Display for FacilityError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::MalformedGuid { entity, offered } => {
write!(f, "{entity}: GlobalId {offered:?} is not an IFC GUID")
}
Self::UnknownToken {
entity,
attribute,
offered,
} => {
write!(f, "{entity}: {offered:?} is not a {attribute} of {entity}")
}
Self::NoPredefinedType { entity } => {
write!(f, "{entity} declares no PredefinedType")
}
Self::MissingUsageType { entity } => {
write!(f, "{entity}: UsageType is mandatory on a facility part")
}
Self::UnexpectedUsageType { entity } => {
write!(f, "{entity} declares no UsageType")
}
Self::UserDefinedWithoutObjectType { entity, attribute } => {
write!(
f,
"{entity}: {attribute} is USERDEFINED without an ObjectType naming it"
)
}
}
}
}
impl std::error::Error for FacilityError {}
pub type FacilityResult<T> = Result<T, FacilityError>;
#[derive(Debug, Clone, Copy, Default)]
pub struct FacilityDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub placement: Option<EntityId>,
pub long_name: Option<&'a str>,
pub composition: Option<&'a str>,
pub usage: Option<&'a str>,
}
const USAGE_TOKENS: &[&str] = &[
"LATERAL",
"LONGITUDINAL",
"REGION",
"VERTICAL",
"USERDEFINED",
"NOTDEFINED",
];
fn names_itself(draft: &FacilityDraft<'_>) -> bool {
draft.object_type.is_some_and(|s| !s.trim().is_empty())
}
pub fn create_facility(
tx: &mut Transaction,
kind: Facility,
global_id: &str,
predefined_type: Option<&str>,
draft: FacilityDraft<'_>,
) -> FacilityResult<EntityId> {
let entity = kind.type_name;
if Guid::parse(global_id).is_none() {
return Err(FacilityError::MalformedGuid {
entity,
offered: global_id.to_owned(),
});
}
let mut attributes = vec![Value::Null; kind.arity];
attributes[0] = Value::Text(global_id.into());
attributes[2] = text(draft.name);
attributes[3] = text(draft.description);
attributes[4] = text(draft.object_type);
attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
attributes[7] = text(draft.long_name);
attributes[8] = draft
.composition
.map_or(Value::Null, |t| Value::Enum(t.into()));
match (kind.usage_slot, draft.usage) {
(Some(slot), Some(token)) => {
if !USAGE_TOKENS.contains(&token) {
return Err(FacilityError::UnknownToken {
entity,
attribute: "UsageType",
offered: token.to_owned(),
});
}
if token == "USERDEFINED" && !names_itself(&draft) {
return Err(FacilityError::UserDefinedWithoutObjectType {
entity,
attribute: "UsageType",
});
}
attributes[slot] = Value::Enum(token.into());
}
(Some(_), None) => return Err(FacilityError::MissingUsageType { entity }),
(None, Some(_)) => return Err(FacilityError::UnexpectedUsageType { entity }),
(None, None) => {}
}
match (kind.predefined_slot, predefined_type) {
(Some(slot), Some(token)) => {
if !kind.members.contains(&token) {
return Err(FacilityError::UnknownToken {
entity,
attribute: "PredefinedType",
offered: token.to_owned(),
});
}
if token == "USERDEFINED" && !names_itself(&draft) {
return Err(FacilityError::UserDefinedWithoutObjectType {
entity,
attribute: "PredefinedType",
});
}
attributes[slot] = Value::Enum(token.into());
}
(None, Some(_)) => return Err(FacilityError::NoPredefinedType { entity }),
(Some(_), None) | (None, None) => {}
}
Ok(tx.create(Entity::new(entity, attributes)))
}
fn text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |t| Value::Text(t.into()))
}