mod assemble;
mod interval;
mod leaf;
mod path;
mod redefine;
mod slot;
use std::collections::BTreeMap;
use openehr_base::v1_3::base_types::identification::archetype_id::ArchetypeId;
use openehr_base::v1_3::base_types::identification::hier_object_id::HierObjectId;
use openehr_base::v1_3::base_types::identification::template_id::TemplateId;
use openehr_base::v1_3::base_types::terminology::code_phrase::CodePhrase;
use crate::opt14::types::{
Archetype, CAttribute, CObject, Intervalofinteger, OperationalTemplate, ResourceDescription,
};
use crate::xml::runtime::XmlError;
#[derive(Debug, Clone, Default)]
pub struct ArchetypeLibrary {
by_id: BTreeMap<String, Archetype>,
}
impl ArchetypeLibrary {
pub fn insert(&mut self, archetype: Archetype) -> Option<Archetype> {
self.by_id
.insert(archetype.archetype_id.value.clone(), archetype)
}
#[must_use]
pub fn get(&self, archetype_id: &str) -> Option<&Archetype> {
self.by_id.get(archetype_id)
}
#[must_use]
pub fn len(&self) -> usize {
self.by_id.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.by_id.is_empty()
}
}
impl FromIterator<Archetype> for ArchetypeLibrary {
fn from_iter<I: IntoIterator<Item = Archetype>>(iter: I) -> Self {
let mut library = Self::default();
for archetype in iter {
library.insert(archetype);
}
library
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TemplateDefinition {
pub template_id: TemplateId,
pub concept: String,
pub language: CodePhrase,
pub description: Option<ResourceDescription>,
pub uid: Option<HierObjectId>,
pub root: ArchetypeUse,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArchetypeUse {
pub archetype_id: ArchetypeId,
pub fills: Vec<SlotFill>,
pub narrowings: Vec<Narrowing>,
}
impl ArchetypeUse {
#[must_use]
pub fn new(archetype_id: ArchetypeId) -> Self {
Self {
archetype_id,
fills: Vec::new(),
narrowings: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SlotFill {
pub path: String,
pub occurrences: Option<Intervalofinteger>,
pub filler: ArchetypeUse,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Narrowing {
Occurrences {
path: String,
occurrences: Intervalofinteger,
},
Existence {
path: String,
existence: Intervalofinteger,
},
Exclude {
path: String,
},
Constrain {
path: String,
attribute: CAttribute,
},
Redefine {
path: String,
objects: Vec<CObject>,
},
Clone(NodeClone),
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodeClone {
pub path: String,
pub name: CAttribute,
pub occurrences: Option<Intervalofinteger>,
pub fills: Vec<SlotFill>,
pub narrowings: Vec<Narrowing>,
}
impl NodeClone {
#[must_use]
pub fn new(path: impl Into<String>, name: CAttribute) -> Self {
Self {
path: path.into(),
name,
occurrences: None,
fills: Vec::new(),
narrowings: Vec::new(),
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum WriteError {
#[error("archetype {archetype_id} is not in the archetype library")]
ArchetypeNotFound {
archetype_id: String,
},
#[error("archetype {archetype_id} has no term definitions in language {language}")]
MissingLanguage {
archetype_id: String,
language: String,
},
#[error("{path:?} in {archetype_id} is not an ADL path: {reason}")]
MalformedPath {
archetype_id: String,
path: String,
reason: &'static str,
},
#[error("{path:?} addresses {found} nodes in {archetype_id}, not exactly one")]
PathNotFound {
archetype_id: String,
path: String,
found: usize,
},
#[error("{path:?} in {archetype_id} is not an archetype slot")]
NotASlot {
archetype_id: String,
path: String,
},
#[error(
"the slot {slot_path:?} in {archetype_id} carries an assertion other than \
`archetype_id/value matches {{/regex/}}`"
)]
UnsupportedSlotAssertion {
archetype_id: String,
slot_path: String,
},
#[error("the slot {slot_path:?} in {archetype_id} has the invalid pattern {pattern:?}")]
InvalidSlotPattern {
archetype_id: String,
slot_path: String,
pattern: String,
#[source]
source: Box<regex::Error>,
},
#[error("the slot {slot_path:?} in {archetype_id} does not admit {filler}")]
SlotRefusesFiller {
archetype_id: String,
slot_path: String,
filler: String,
},
#[error(
"the slot {slot_path:?} in {archetype_id} takes {slot_type}, and {filler} is a {filler_type}"
)]
FillerTypeMismatch {
archetype_id: String,
slot_path: String,
slot_type: String,
filler: String,
filler_type: String,
},
#[error("the {what} {given} at {path:?} in {archetype_id} is not within {archetype}")]
Widening {
archetype_id: String,
path: String,
what: &'static str,
given: String,
archetype: String,
},
#[error("{path:?} in {archetype_id} is not a complex object")]
NotAComplexObject {
archetype_id: String,
path: String,
},
#[error("{rm_type} at {path:?} in {archetype_id} has no attribute {attribute}")]
AttributeNotInModel {
archetype_id: String,
path: String,
rm_type: String,
attribute: String,
},
#[error("{attribute} at {path:?} in {archetype_id} is constrained by the archetype already")]
AttributeConstrained {
archetype_id: String,
path: String,
attribute: String,
},
#[error("{given} does not conform to {archetype} at {path:?} in {archetype_id}")]
TypeNotConforming {
archetype_id: String,
path: String,
archetype: String,
given: String,
},
#[error("{node} at {path:?} in {archetype_id} redefines no single archetype node")]
RedefinitionUnmatched {
archetype_id: String,
path: String,
node: String,
},
#[error("{path:?} in {archetype_id} admits no clone: {reason}")]
CloneNotAdmitted {
archetype_id: String,
path: String,
reason: &'static str,
},
#[error("the clones of {path:?} in {archetype_id} share a name constraint")]
CloneNameNotDistinct {
archetype_id: String,
path: String,
},
#[error(
"the internal reference at {path:?} in {archetype_id} targets its own ancestor {target}"
)]
RecursiveInternalRef {
archetype_id: String,
path: String,
target: String,
},
#[error("the operational template does not serialize")]
Xml(#[from] XmlError),
}
pub fn build(
definition: &TemplateDefinition,
library: &ArchetypeLibrary,
) -> Result<OperationalTemplate, WriteError> {
let mut flattener = assemble::Flattener {
library,
language: &definition.language,
components: Vec::new(),
};
let mut root = flattener.archetype_root(&definition.root, None)?;
root.template_id = Some(definition.template_id.clone());
let component_ontologies = flattener
.components
.iter()
.filter_map(|id| library.get(id))
.map(assemble::flat_ontology)
.collect();
Ok(OperationalTemplate {
language: definition.language.clone(),
is_controlled: None,
description: definition.description.clone(),
revision_history: None,
uid: definition.uid.clone(),
template_id: definition.template_id.clone(),
concept: definition.concept.clone(),
definition: root,
ontology: None,
component_ontologies,
annotations: Vec::new(),
constraints: None,
view: None,
})
}
pub fn write(
definition: &TemplateDefinition,
library: &ArchetypeLibrary,
) -> Result<String, WriteError> {
let template = build(definition, library)?;
Ok(crate::opt14::to_xml(&template)?)
}