use ifc_model::guid::Guid;
use ifc_model::{EntityId, Model, Transaction, Value};
use crate::draft::{specific_values, OccurrenceDraft};
use crate::error::{OccurrenceError, OccurrenceResult};
use crate::release::{bind, require_owner_history};
use crate::table::Occurrence;
fn text(v: Option<&str>) -> Value {
v.map_or(Value::Null, |s| Value::Text(s.into()))
}
fn slot_ref(v: Option<EntityId>) -> Value {
v.map_or(Value::Null, Value::Ref)
}
pub fn create(
tx: &mut Transaction,
model: &Model,
kind: Occurrence,
global_id: &str,
predefined_type: Option<&str>,
typed_by: Option<EntityId>,
draft: OccurrenceDraft<'_>,
) -> OccurrenceResult<EntityId> {
let request = Request {
kind,
global_id,
predefined_type,
typed_by,
draft,
};
author(tx, model, request, None)
}
#[allow(clippy::too_many_arguments)]
pub fn create_with_owner_history(
tx: &mut Transaction,
model: &Model,
kind: Occurrence,
global_id: &str,
predefined_type: Option<&str>,
typed_by: Option<EntityId>,
draft: OccurrenceDraft<'_>,
owner_history: EntityId,
) -> OccurrenceResult<EntityId> {
let request = Request {
kind,
global_id,
predefined_type,
typed_by,
draft,
};
author(tx, model, request, Some(owner_history))
}
struct Request<'a> {
kind: Occurrence,
global_id: &'a str,
predefined_type: Option<&'a str>,
typed_by: Option<EntityId>,
draft: OccurrenceDraft<'a>,
}
fn author(
tx: &mut Transaction,
model: &Model,
request: Request<'_>,
owner_history: Option<EntityId>,
) -> OccurrenceResult<EntityId> {
let Request {
kind,
global_id,
predefined_type,
typed_by,
draft,
} = request;
let entity = kind.type_name;
if Guid::parse(global_id).is_none() {
return Err(OccurrenceError::MalformedGuid {
value: global_id.into(),
});
}
let layout = bind(model)?;
layout.require_entity(entity)?;
if let Some(token) = predefined_type {
let Some(members) = layout.members(entity, "PredefinedType") else {
return Err(OccurrenceError::NoPredefinedType { entity });
};
if !members.contains(&token) {
return Err(OccurrenceError::UnknownPredefinedType {
entity,
token: token.into(),
});
}
if token == "USERDEFINED" && draft.object_type.is_none_or(|s| s.trim().is_empty()) {
return Err(OccurrenceError::UserDefinedWithoutObjectType { entity });
}
}
if let Some(id) = typed_by {
let Some(expected) = layout.type_class(kind)? else {
if kind.type_class.is_none() {
return Err(OccurrenceError::TypingNotPermitted { entity });
}
return Err(OccurrenceError::TypeClassNotInSchema {
entity,
schema: layout.version(),
});
};
let found = model
.get(id)
.ok_or(OccurrenceError::UnresolvedType { id })?
.type_name
.to_ascii_uppercase();
if !layout.is_a(&found, expected) {
return Err(OccurrenceError::WrongTypeClass {
entity,
expected,
found,
});
}
}
let specific = specific_values(layout, entity, &draft)?;
if let Some(owner_history) = owner_history {
require_owner_history(tx, model, owner_history)?;
}
let mut values = vec![
("GlobalId", Value::Text(global_id.into())),
("OwnerHistory", slot_ref(owner_history)),
("Name", text(draft.name)),
("Description", text(draft.description)),
("ObjectType", text(draft.object_type)),
("ObjectPlacement", slot_ref(draft.placement)),
("Representation", slot_ref(draft.representation)),
("Tag", text(draft.tag)),
(
"PredefinedType",
predefined_type.map_or(Value::Null, |token| Value::Enum(token.into())),
),
];
values.extend(specific);
let record = layout.named_record(entity, values)?;
Ok(tx.create(record))
}