mod association;
mod records;
pub use association::{
associate_classification, associate_classification_with_owner_history, associate_document,
associate_document_with_owner_history, associate_library, associate_library_with_owner_history,
relate_documents,
};
pub use records::{
create_classification, create_classification_in, create_classification_reference,
create_document, create_document_reference, create_library, create_library_reference,
};
use std::sync::Arc;
use ifc_model::{Edit, EntityId, Model, Transaction, Value};
use crate::release::Release;
use crate::{ClassificationError, ClassificationResult};
#[derive(Debug, Clone, Copy)]
pub struct ClassificationDraft<'a> {
pub source: Option<&'a str>,
pub edition: Option<&'a str>,
pub edition_date: Option<&'a str>,
pub name: &'a str,
pub description: Option<&'a str>,
pub location: Option<&'a str>,
pub reference_tokens: Option<&'a [&'a str]>,
}
#[derive(Debug, Clone, Copy)]
pub struct ClassificationReferenceDraft<'a> {
pub location: Option<&'a str>,
pub identification: Option<&'a str>,
pub name: Option<&'a str>,
pub referenced_source: Option<EntityId>,
pub description: Option<&'a str>,
pub sort: Option<&'a str>,
}
#[derive(Debug, Clone, Copy)]
pub struct DocumentDraft<'a> {
pub identification: &'a str,
pub name: &'a str,
pub description: Option<&'a str>,
pub location: Option<&'a str>,
pub purpose: Option<&'a str>,
pub intended_use: Option<&'a str>,
pub scope: Option<&'a str>,
pub revision: Option<&'a str>,
pub document_owner: Option<EntityId>,
pub editors: Option<&'a [EntityId]>,
pub creation_time: Option<&'a str>,
pub last_revision_time: Option<&'a str>,
pub electronic_format: Option<&'a str>,
pub valid_from: Option<&'a str>,
pub valid_until: Option<&'a str>,
pub confidentiality: Option<&'a str>,
pub status: Option<&'a str>,
}
#[derive(Debug, Clone, Copy)]
pub struct DocumentReferenceDraft<'a> {
pub location: Option<&'a str>,
pub identification: Option<&'a str>,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub referenced_document: Option<EntityId>,
}
#[derive(Debug, Clone, Copy)]
pub struct LibraryDraft<'a> {
pub name: &'a str,
pub version: Option<&'a str>,
pub publisher: Option<EntityId>,
pub version_date: Option<&'a str>,
pub location: Option<&'a str>,
pub description: Option<&'a str>,
}
#[derive(Debug, Clone, Copy)]
pub struct LibraryReferenceDraft<'a> {
pub location: Option<&'a str>,
pub identification: Option<&'a str>,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub language: Option<&'a str>,
pub referenced_library: Option<EntityId>,
}
#[derive(Debug, Clone, Copy)]
pub struct AssociationDraft<'a> {
pub global_id: &'a str,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub related_objects: &'a [EntityId],
}
pub(crate) fn text(value: &str) -> Value {
Value::Text(Arc::from(value))
}
fn optional_text(value: Option<&str>) -> Value {
value.map_or(Value::Null, text)
}
fn optional_ref(value: Option<EntityId>) -> Value {
value.map_or(Value::Null, Value::Ref)
}
fn refs(values: &[EntityId]) -> Value {
Value::List(values.iter().copied().map(Value::Ref).collect())
}
pub(crate) fn final_type<'a>(
tx: &'a Transaction,
model: &'a Model,
id: EntityId,
) -> Option<&'a str> {
for edit in tx.edits().iter().rev() {
match edit {
Edit::Create {
id: edit_id,
entity,
} if *edit_id == id => return Some(&entity.type_name),
Edit::Retype {
id: edit_id,
type_name,
} if *edit_id == id => return Some(type_name),
Edit::Remove { id: edit_id } if *edit_id == id => return None,
_ => {}
}
}
model.get(id).map(|entity| entity.type_name.as_ref())
}
pub(crate) fn require_accepts(
tx: &Transaction,
model: &Model,
release: Release<'_>,
entity: &'static str,
attribute: &'static str,
target: EntityId,
) -> ClassificationResult<()> {
let declared = release.declared(entity, attribute)?;
let actual =
final_type(tx, model, target).ok_or(ClassificationError::UnknownEntity { id: target })?;
let (_, schema) = release.bound()?;
if schema.accepts_type(&declared.type_name, actual) {
Ok(())
} else {
Err(ClassificationError::AuthoringReferenceType {
target,
expected: declared.type_name.as_str(),
actual: actual.to_owned(),
})
}
}
fn require_set(
tx: &Transaction,
model: &Model,
release: Release<'_>,
entity: &'static str,
attribute: &'static str,
values: &[EntityId],
) -> ClassificationResult<()> {
if values.is_empty() {
return Err(ClassificationError::AuthoringInvalid {
entity,
attribute,
value: "empty SET [1:?]".into(),
});
}
let mut seen = std::collections::HashSet::new();
for &value in values {
if !seen.insert(value) {
return Err(ClassificationError::AuthoringInvalid {
entity,
attribute,
value: format!("duplicate {value}"),
});
}
require_accepts(tx, model, release, entity, attribute, value)?;
}
Ok(())
}
fn require_external_identity(
entity: &'static str,
location: Option<&str>,
identification: Option<&str>,
name: Option<&str>,
) -> ClassificationResult<()> {
if location.is_some() || identification.is_some() || name.is_some() {
Ok(())
} else {
Err(ClassificationError::AuthoringInvalid {
entity,
attribute: "WR1",
value: "Location, Identification, and Name are all unstated".into(),
})
}
}
fn require_enum(
release: Release<'_>,
entity: &'static str,
attribute: &'static str,
value: Option<&str>,
) -> ClassificationResult<()> {
let Some(value) = value else {
return Ok(());
};
let declared = release.declared(entity, attribute)?;
let (_, schema) = release.bound()?;
if crate::release::enumeration(schema, &declared.type_name)
.iter()
.any(|candidate| candidate.eq_ignore_ascii_case(value))
{
Ok(())
} else {
Err(ClassificationError::AuthoringInvalid {
entity,
attribute,
value: value.into(),
})
}
}