use ifc_model::{Entity, EntityId, Model, Transaction, Value};
use ifc_schema::Schema;
use crate::builder::check_value;
use crate::error::{AuthorError, AuthorResult};
#[derive(Debug, Clone)]
pub struct EntityEditor<'a> {
schema: &'a Schema,
id: EntityId,
entity: Entity,
set: Vec<(String, Value)>,
}
impl<'a> EntityEditor<'a> {
pub fn new(schema: &'a Schema, model: &Model, id: EntityId) -> AuthorResult<Self> {
let entity = model
.get(id)
.cloned()
.ok_or(AuthorError::MissingEntity { id })?;
Ok(Self {
schema,
id,
entity,
set: Vec::new(),
})
}
#[must_use]
pub fn set(mut self, attribute: impl Into<String>, value: Value) -> Self {
self.set.push((attribute.into(), value));
self
}
#[must_use]
pub fn text(self, attribute: impl Into<String>, text: impl Into<std::sync::Arc<str>>) -> Self {
self.set(attribute, Value::Text(text.into()))
}
#[must_use]
pub fn real(self, attribute: impl Into<String>, value: f64) -> Self {
self.set(attribute, Value::Real(value))
}
#[must_use]
pub fn reference(self, attribute: impl Into<String>, id: EntityId) -> Self {
self.set(attribute, Value::Ref(id))
}
#[must_use]
pub fn enumeration(
self,
attribute: impl Into<String>,
constant: impl Into<std::sync::Arc<str>>,
) -> Self {
self.set(attribute, Value::Enum(constant.into()))
}
pub fn stage(self, transaction: &mut Transaction) -> AuthorResult<()> {
let entity_name = self.entity.type_name.to_string();
let declared = self.schema.attributes(&entity_name);
if declared.is_empty() && self.schema.entity(&entity_name).is_none() {
return Err(AuthorError::UnknownEntity {
entity: entity_name,
});
}
if self.entity.attributes.len() != declared.len() {
return Err(AuthorError::ArityMismatch {
entity: entity_name,
expected: declared.len(),
found: self.entity.attributes.len(),
});
}
let mut projected = self.entity.attributes.clone();
let mut edited = vec![false; declared.len()];
let mut staged = Vec::with_capacity(self.set.len());
for (name, value) in self.set {
let Some(index) = declared
.iter()
.position(|attribute| attribute.name.eq_ignore_ascii_case(&name))
else {
return Err(AuthorError::UnknownAttribute {
entity: entity_name,
attribute: name,
known: declared
.iter()
.map(|attribute| attribute.name.clone())
.collect(),
});
};
if edited[index] {
return Err(AuthorError::DuplicateAttribute {
entity: entity_name,
attribute: declared[index].name.clone(),
});
}
edited[index] = true;
projected[index] = value.clone();
staged.push((index, value));
}
for (index, attribute) in declared.iter().enumerate() {
let value = &projected[index];
check_value(self.schema, &entity_name, attribute, value)?;
if !attribute.optional && matches!(value, Value::Null) {
return Err(AuthorError::MissingRequired {
entity: entity_name,
attribute: attribute.name.clone(),
});
}
}
for (slot, value) in staged {
if self.entity.attributes[slot] != value {
transaction.set_attribute(self.id, slot, value);
}
}
Ok(())
}
}