#![expect(
clippy::disallowed_types,
reason = "ODIN-to-JSON conversion targets the JSON data model by specification (LANG odin \
spec) (#1694)"
)]
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use crate::v1_1::bmm_persistence::create_bmm3_assertion::AssertionScope;
use crate::v1_1::bmm_persistence::create_bmm3_assertion::build_assertions;
use crate::v1_1::bmm_persistence::create_model::Builder;
use crate::v1_1::bmm_persistence::create_model::ClassEntry;
use crate::v1_1::bmm_persistence::create_model::check_enumeration_validity;
use crate::v1_1::bmm_persistence::create_model::multiplicity_of;
use crate::v1_1::bmm_persistence::create_model::property_context;
use crate::v1_1::bmm_persistence::create_model::qualify;
use crate::v1_1::bmm_persistence::error::PBmmReadError;
use crate::v1_1::bmm_persistence::generic_binding::ActualParameter;
use crate::v1_1::bmm_persistence::generic_binding::parameter_bindings;
use crate::v1_1::bmm_persistence::p_bmm_base_type::PBmmBaseType;
use crate::v1_1::bmm_persistence::p_bmm_class::PBmmClass;
use crate::v1_1::bmm_persistence::p_bmm_constant::PBmmConstant;
use crate::v1_1::bmm_persistence::p_bmm_container_property::PBmmContainerProperty;
use crate::v1_1::bmm_persistence::p_bmm_container_type::PBmmContainerType;
use crate::v1_1::bmm_persistence::p_bmm_enumeration::PBmmEnumeration;
use crate::v1_1::bmm_persistence::p_bmm_function::PBmmFunction;
use crate::v1_1::bmm_persistence::p_bmm_function_parameter::PBmmFunctionParameter;
use crate::v1_1::bmm_persistence::p_bmm_generic_type::PBmmGenericType;
use crate::v1_1::bmm_persistence::p_bmm_indexed_container_type::PBmmIndexedContainerType;
use crate::v1_1::bmm_persistence::p_bmm_package::PBmmPackage;
use crate::v1_1::bmm_persistence::p_bmm_property::PBmmProperty;
use crate::v1_1::bmm_persistence::p_bmm_schema::PBmmSchema;
use crate::v1_1::bmm_persistence::p_bmm_type::PBmmType;
use crate::v1_1::bmm_persistence::validate::AssertionKind;
use crate::v1_1::bmm_persistence::validate::PBmmValidityFinding;
use crate::v1_1::bmm3::core::entity::bmm_class::BmmClass;
use crate::v1_1::bmm3::core::entity::bmm_container_type::BmmContainerType;
use crate::v1_1::bmm3::core::entity::bmm_container_type::BmmContainerTypeData;
use crate::v1_1::bmm3::core::entity::bmm_effective_type::BmmEffectiveType;
use crate::v1_1::bmm3::core::entity::bmm_generic_class::BmmGenericClass;
use crate::v1_1::bmm3::core::entity::bmm_generic_type::BmmGenericType;
use crate::v1_1::bmm3::core::entity::bmm_indexed_container_type::BmmIndexedContainerType;
use crate::v1_1::bmm3::core::entity::bmm_model_type::BmmModelType;
use crate::v1_1::bmm3::core::entity::bmm_module::BmmModule;
use crate::v1_1::bmm3::core::entity::bmm_parameter_type::BmmParameterType;
use crate::v1_1::bmm3::core::entity::bmm_simple_class::BmmSimpleClass;
use crate::v1_1::bmm3::core::entity::bmm_simple_class::BmmSimpleClassData;
use crate::v1_1::bmm3::core::entity::bmm_simple_type::BmmSimpleType;
use crate::v1_1::bmm3::core::entity::bmm_type::BmmType;
use crate::v1_1::bmm3::core::entity::bmm_unitary_type::BmmUnitaryType;
use crate::v1_1::bmm3::core::entity::range_constrained::bmm_enumeration::BmmEnumeration;
use crate::v1_1::bmm3::core::entity::range_constrained::bmm_enumeration::BmmEnumerationData;
use crate::v1_1::bmm3::core::entity::range_constrained::bmm_enumeration_integer::BmmEnumerationInteger;
use crate::v1_1::bmm3::core::entity::range_constrained::bmm_enumeration_string::BmmEnumerationString;
use crate::v1_1::bmm3::core::entity::range_constrained::bmm_value_set_spec::BmmValueSetSpec;
use crate::v1_1::bmm3::core::feature::bmm_constant::BmmConstant;
use crate::v1_1::bmm3::core::feature::bmm_container_property::BmmContainerProperty;
use crate::v1_1::bmm3::core::feature::bmm_container_property::BmmContainerPropertyData;
use crate::v1_1::bmm3::core::feature::bmm_feature::BmmFeature;
use crate::v1_1::bmm3::core::feature::bmm_feature_group::BmmFeatureGroup;
use crate::v1_1::bmm3::core::feature::bmm_function::BmmFunction;
use crate::v1_1::bmm3::core::feature::bmm_indexed_container_property::BmmIndexedContainerProperty;
use crate::v1_1::bmm3::core::feature::bmm_parameter::BmmParameter;
use crate::v1_1::bmm3::core::feature::bmm_procedure::BmmProcedure;
use crate::v1_1::bmm3::core::feature::bmm_property::BmmProperty;
use crate::v1_1::bmm3::core::feature::bmm_result::BmmResult;
use crate::v1_1::bmm3::core::feature::bmm_static::BmmStatic;
use crate::v1_1::bmm3::core::literal_value::bmm_integer_value::BmmIntegerValue;
use crate::v1_1::bmm3::core::literal_value::bmm_literal_value::BmmLiteralValue;
use crate::v1_1::bmm3::core::literal_value::bmm_primitive_value::BmmPrimitiveValue;
use crate::v1_1::bmm3::core::literal_value::bmm_primitive_value::BmmPrimitiveValueData;
use crate::v1_1::bmm3::core::model::bmm_model::BmmModel;
use crate::v1_1::bmm3::core::model::bmm_package::BmmPackage;
use crate::v1_1::bmm3::statement::bmm_assertion::BmmAssertion;
use openehr_base::containers::present;
macro_rules! class_from_core {
($ty:ident, $core:expr $(, $field:ident $(: $value:expr)?)* $(,)?) => {{
let core: ClassCore = $core;
$ty {
name: core.name,
documentation: core.documentation,
extensions: None,
feature_groups: present(core.feature_groups),
features: present(core.features),
ancestors: core.ancestors,
package: core.package,
properties: core.properties,
source_schema_id: core.source_schema_id,
immediate_descendants: present(Vec::new()),
is_override: core.is_override,
static_properties: core.static_properties,
functions: core.functions,
procedures: core.procedures,
is_primitive: core.is_primitive,
is_abstract: core.is_abstract,
invariants: present(core.invariants),
creators: None,
converters: None,
$($field $(: $value)?,)*
}
}};
}
pub const DEFAULT_FEATURE_GROUP_NAME: &str = "feature";
const VALUE_SET_SEPARATOR: &str = "::";
pub fn create_bmm3_model(schema: &PBmmSchema) -> Result<BmmModel, PBmmReadError> {
create_bmm3_model_reporting(schema).map(|(model, _)| model)
}
pub fn create_bmm3_model_reporting(
schema: &PBmmSchema,
) -> Result<(BmmModel, Vec<PBmmValidityFinding>), PBmmReadError> {
let builder = Builder::new(schema)?;
let mut class_definitions: BTreeMap<String, BmmClass> = BTreeMap::new();
for entry in builder.classes.values() {
class_definitions.insert(
entry.class.name().to_owned(),
build_class(&builder, entry, Depth::Full, &mut BTreeSet::new())?,
);
}
let modules: BTreeMap<String, BmmModule> = class_definitions
.iter()
.map(|(name, class)| (name.clone(), as_module(class)))
.collect();
let packages = build_packages(&builder, &schema.packages, "")?;
let findings = builder.findings.take();
Ok((
BmmModel {
name: schema.schema_name.clone(),
documentation: None,
extensions: None,
packages: (!packages.is_empty()).then_some(packages),
rm_publisher: schema.rm_publisher.clone(),
rm_release: schema.rm_release.clone(),
class_definitions: (!class_definitions.is_empty()).then_some(class_definitions),
used_models: present(Vec::new()),
modules: (!modules.is_empty()).then_some(modules),
},
findings,
))
}
fn as_module(class: &BmmClass) -> BmmModule {
match class {
BmmClass::BmmGenericClass(generic) => BmmModule::BmmGenericClass(generic.clone()),
BmmClass::BmmSimpleClass(simple) => BmmModule::BmmSimpleClass(simple.clone()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Depth {
Full,
Stub,
}
struct ClassCore {
name: String,
documentation: Option<BTreeMap<String, serde_json::Value>>,
feature_groups: Vec<BmmFeatureGroup>,
features: Vec<BmmFeature>,
ancestors: Option<BTreeMap<String, BmmModelType>>,
package: BmmPackage,
properties: Option<BTreeMap<String, BmmProperty>>,
static_properties: Option<BTreeMap<String, BmmStatic>>,
functions: Option<BTreeMap<String, BmmFunction>>,
procedures: Option<BTreeMap<String, BmmProcedure>>,
source_schema_id: String,
is_abstract: Option<bool>,
is_primitive: Option<bool>,
is_override: bool,
invariants: Vec<BmmAssertion>,
}
fn documentation_of(text: Option<&str>) -> Option<BTreeMap<String, serde_json::Value>> {
text.map(|text| {
let mut out = BTreeMap::new();
out.insert(
"purpose".to_owned(),
serde_json::Value::String(text.to_owned()),
);
out
})
}
fn group_back_reference() -> BmmFeatureGroup {
BmmFeatureGroup {
name: DEFAULT_FEATURE_GROUP_NAME.to_owned(),
properties: BTreeMap::new(),
features: present(Vec::new()),
visibility: None,
}
}
fn value_set_spec(constraint: Option<&str>) -> Option<BmmValueSetSpec> {
constraint.map(
|constraint| match constraint.split_once(VALUE_SET_SEPARATOR) {
Some((resource_id, value_set_id)) => BmmValueSetSpec {
resource_id: resource_id.to_owned(),
value_set_id: value_set_id.to_owned(),
},
None => BmmValueSetSpec {
resource_id: String::new(),
value_set_id: constraint.to_owned(),
},
},
)
}
fn build_class(
builder: &Builder<'_>,
entry: &ClassEntry<'_>,
depth: Depth,
visiting: &mut BTreeSet<String>,
) -> Result<BmmClass, PBmmReadError> {
let persisted = entry.class;
let mut core = build_core(builder, entry, depth, visiting)?;
synthesise_generic_properties(builder, depth, visiting, &mut core)?;
if depth == Depth::Full {
let scope = AssertionScope {
builder,
owner: persisted,
properties: core.properties.as_ref(),
statics: core.static_properties.as_ref(),
functions: core.functions.as_ref(),
result_type: None,
};
let mut findings: Vec<PBmmValidityFinding> = Vec::new();
core.invariants = build_assertions(
&scope,
AssertionKind::Invariant,
None,
persisted.invariants(),
&mut findings,
);
builder.findings.borrow_mut().extend(findings);
}
if let PBmmClass::PBmmEnumeration(enumeration) = persisted {
check_enumeration_validity(core.name.as_str(), enumeration)?;
return Ok(BmmClass::BmmSimpleClass(BmmSimpleClass::BmmEnumeration(
build_enumeration(builder, core, enumeration, visiting)?,
)));
}
if persisted.is_generic() {
return Ok(BmmClass::BmmGenericClass(class_from_core!(
BmmGenericClass,
core,
generic_parameters: build_generic_parameters(builder, persisted, visiting)?,
)));
}
Ok(BmmClass::BmmSimpleClass(BmmSimpleClass::BmmSimpleClass(
class_from_core!(BmmSimpleClassData, core),
)))
}
fn build_core(
builder: &Builder<'_>,
entry: &ClassEntry<'_>,
depth: Depth,
visiting: &mut BTreeSet<String>,
) -> Result<ClassCore, PBmmReadError> {
let persisted = entry.class;
let name = persisted.name();
let (properties, statics, functions, procedures) = match depth {
Depth::Stub => (None, None, None, None),
Depth::Full => (
build_properties(builder, persisted, visiting)?,
build_constants(builder, persisted, visiting)?,
build_functions(builder, persisted, visiting)?,
build_procedures(builder, persisted, visiting)?,
),
};
let features = collect_features(
properties.as_ref(),
statics.as_ref(),
functions.as_ref(),
procedures.as_ref(),
);
Ok(ClassCore {
name: name.to_owned(),
documentation: documentation_of(persisted.documentation()),
feature_groups: if features.is_empty() {
Vec::new()
} else {
vec![BmmFeatureGroup {
name: DEFAULT_FEATURE_GROUP_NAME.to_owned(),
properties: BTreeMap::new(),
features: present(features.clone()),
visibility: None,
}]
},
features,
ancestors: build_ancestors(builder, persisted, depth, visiting)?,
package: package_of(builder, name)?,
properties,
static_properties: statics,
functions,
procedures,
source_schema_id: persisted
.source_schema_id()
.unwrap_or(builder.schema_id.as_str())
.to_owned(),
is_abstract: Some(persisted.is_abstract()),
is_primitive: Some(entry.is_primitive_type),
is_override: persisted.is_override(),
invariants: Vec::new(),
})
}
fn package_of(builder: &Builder<'_>, name: &str) -> Result<BmmPackage, PBmmReadError> {
let path = builder
.owning_package
.get(&name.to_uppercase())
.ok_or_else(|| PBmmReadError::ClassNotInAnyPackage {
class: name.to_owned(),
})?;
Ok(BmmPackage {
name: path.clone(),
documentation: None,
extensions: None,
packages: None,
members: present(Vec::new()),
})
}
fn collect_features(
properties: Option<&BTreeMap<String, BmmProperty>>,
statics: Option<&BTreeMap<String, BmmStatic>>,
functions: Option<&BTreeMap<String, BmmFunction>>,
procedures: Option<&BTreeMap<String, BmmProcedure>>,
) -> Vec<BmmFeature> {
let mut out: Vec<BmmFeature> = Vec::new();
for property in properties.into_iter().flatten().map(|(_, p)| p) {
out.push(match property {
BmmProperty::BmmContainerProperty(container) => {
BmmFeature::BmmContainerProperty(container.clone())
}
BmmProperty::BmmUnitaryProperty(unitary) => {
BmmFeature::BmmUnitaryProperty(unitary.clone())
}
});
}
for value in statics.into_iter().flatten().map(|(_, s)| s) {
out.push(match value {
BmmStatic::BmmConstant(constant) => BmmFeature::BmmConstant(constant.clone()),
BmmStatic::BmmSingleton(singleton) => BmmFeature::BmmSingleton(singleton.clone()),
});
}
for function in functions.into_iter().flatten().map(|(_, f)| f) {
out.push(BmmFeature::BmmFunction(function.clone()));
}
for procedure in procedures.into_iter().flatten().map(|(_, p)| p) {
out.push(BmmFeature::BmmProcedure(procedure.clone()));
}
out
}
fn build_ancestors(
builder: &Builder<'_>,
persisted: &PBmmClass,
depth: Depth,
visiting: &mut BTreeSet<String>,
) -> Result<Option<BTreeMap<String, BmmModelType>>, PBmmReadError> {
if !visiting.insert(persisted.name().to_uppercase()) {
return Ok(None);
}
let mut out: BTreeMap<String, BmmModelType> = BTreeMap::new();
let mut named: Vec<&str> = Vec::new();
for def in persisted
.ancestor_defs()
.into_iter()
.flat_map(BTreeMap::values)
{
let PBmmBaseType::PBmmGenericType(def) = def else {
named.push(def.base_name());
continue;
};
let context = format!("class `{}` ancestor `{}`", persisted.name(), def.root_type);
let entry = builder.entry(&context, &def.root_type)?;
out.insert(
entry.class.name().to_owned(),
BmmModelType::BmmGenericType(build_generic_type(
builder, &context, def, persisted, visiting,
)?),
);
}
named.extend(persisted.ancestors().iter().map(String::as_str));
for parent in named {
let entry = builder.classes.get(&parent.to_uppercase()).ok_or_else(|| {
PBmmReadError::UnknownAncestor {
class: persisted.name().to_owned(),
ancestor: parent.to_owned(),
}
})?;
let key = entry.class.name().to_owned();
if out.contains_key(&key) {
continue;
}
let ancestor = build_class(builder, entry, depth, visiting)?;
out.insert(key, class_as_type(ancestor));
}
visiting.remove(&persisted.name().to_uppercase());
Ok((!out.is_empty()).then_some(out))
}
fn synthesise_generic_properties(
builder: &Builder<'_>,
depth: Depth,
visiting: &mut BTreeSet<String>,
core: &mut ClassCore,
) -> Result<(), PBmmReadError> {
let generic_ancestors: Vec<BmmGenericType> = core
.ancestors
.iter()
.flat_map(|map| map.values())
.filter_map(|ancestor| match ancestor {
BmmModelType::BmmGenericType(generic) => Some(generic.clone()),
BmmModelType::BmmSimpleType(_) => None,
})
.collect();
let mut synthesised: BTreeMap<String, BmmProperty> = BTreeMap::new();
for generic in generic_ancestors {
let bindings = parameter_bindings(&generic);
if bindings.is_empty() {
continue;
}
let name = generic.base_class.name.clone();
let key = name.to_uppercase();
if visiting.contains(&key) {
continue;
}
let context = format!("generic ancestor `{name}`");
let entry = builder.entry(&context, &name)?;
visiting.insert(key.clone());
let ancestor = build_class(builder, entry, depth, visiting);
visiting.remove(&key);
for (property_name, property) in ancestor?.properties().iter().copied().flatten() {
if core
.properties
.as_ref()
.is_some_and(|own| own.contains_key(property_name))
{
continue;
}
if let Some(substituted) = substitute_property(property, &bindings) {
synthesised.insert(property_name.clone(), substituted);
}
}
}
if !synthesised.is_empty() {
core.properties
.get_or_insert_with(BTreeMap::new)
.extend(synthesised);
}
Ok(())
}
fn substitute_property(
property: &BmmProperty,
bindings: &BTreeMap<String, BmmUnitaryType>,
) -> Option<BmmProperty> {
match property {
BmmProperty::BmmUnitaryProperty(unitary) => {
let substituted = substitute_unitary(&unitary.r#type, bindings)?;
let mut out = unitary.clone();
out.r#type = substituted;
out.is_synthesised_generic = Some(true);
Some(BmmProperty::BmmUnitaryProperty(out))
}
BmmProperty::BmmContainerProperty(BmmContainerProperty::BmmContainerProperty(data)) => {
let substituted = substitute_container(&data.r#type, bindings)?;
let mut out = data.clone();
out.r#type = substituted;
out.is_synthesised_generic = Some(true);
Some(BmmProperty::BmmContainerProperty(
BmmContainerProperty::BmmContainerProperty(out),
))
}
BmmProperty::BmmContainerProperty(BmmContainerProperty::BmmIndexedContainerProperty(
indexed,
)) => {
let substituted = substitute_indexed_container(&indexed.r#type, bindings)?;
let mut out = indexed.clone();
out.r#type = substituted;
out.is_synthesised_generic = Some(true);
Some(BmmProperty::BmmContainerProperty(
BmmContainerProperty::BmmIndexedContainerProperty(out),
))
}
}
}
fn substitute_unitary(
r#type: &BmmUnitaryType,
bindings: &BTreeMap<String, BmmUnitaryType>,
) -> Option<BmmUnitaryType> {
match r#type {
BmmUnitaryType::BmmParameterType(parameter) => bindings
.iter()
.find(|(formal, _)| formal.eq_ignore_ascii_case(¶meter.name))
.map(|(_, bound)| bound.clone()),
BmmUnitaryType::BmmGenericType(generic) => {
let mut arguments = generic.generic_parameters.clone();
let mut touched = false;
for argument in &mut arguments {
if let Some(substituted) = substitute_unitary(argument, bindings) {
*argument = substituted;
touched = true;
}
}
touched.then(|| {
let mut out = generic.clone();
out.generic_parameters = arguments;
BmmUnitaryType::BmmGenericType(out)
})
}
_ => None,
}
}
fn substitute_container(
r#type: &BmmContainerType,
bindings: &BTreeMap<String, BmmUnitaryType>,
) -> Option<BmmContainerType> {
match r#type {
BmmContainerType::BmmContainerType(data) => {
let item = substitute_unitary(&data.item_type, bindings)?;
let mut out = data.clone();
out.item_type = Box::new(item);
Some(BmmContainerType::BmmContainerType(out))
}
BmmContainerType::BmmIndexedContainerType(indexed) => {
substitute_indexed_container(indexed, bindings)
.map(|out| BmmContainerType::BmmIndexedContainerType(Box::new(out)))
}
}
}
fn substitute_indexed_container(
r#type: &BmmIndexedContainerType,
bindings: &BTreeMap<String, BmmUnitaryType>,
) -> Option<BmmIndexedContainerType> {
let item = substitute_unitary(&r#type.item_type, bindings)?;
let mut out = r#type.clone();
out.item_type = item;
Some(out)
}
fn class_as_type(class: BmmClass) -> BmmModelType {
match class {
BmmClass::BmmGenericClass(generic) => BmmModelType::BmmGenericType(generic.r#type()),
BmmClass::BmmSimpleClass(simple) => BmmModelType::BmmSimpleType(simple.r#type()),
}
}
fn build_generic_parameters(
builder: &Builder<'_>,
persisted: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<indexmap::IndexMap<String, BmmParameterType>, PBmmReadError> {
let mut out = indexmap::IndexMap::new();
for (key, parameter) in persisted.generic_parameter_defs().into_iter().flatten() {
out.insert(
key.clone(),
build_parameter_type(
builder,
persisted.name(),
¶meter.name,
parameter.conforms_to_type.as_deref(),
visiting,
)?,
);
}
Ok(out)
}
fn build_parameter_type(
builder: &Builder<'_>,
owner: &str,
name: &str,
constraint: Option<&str>,
visiting: &mut BTreeSet<String>,
) -> Result<BmmParameterType, PBmmReadError> {
let type_constraint = match constraint {
None => None,
Some(constrainer) => {
let edge = format!("parameter-constraint {owner}::{name}");
if visiting.insert(edge.clone()) {
let context = format!("class `{owner}` generic parameter `{name}`");
let stub = build_class(
builder,
builder.entry(&context, constrainer)?,
Depth::Stub,
visiting,
)?;
visiting.remove(&edge);
Some(Box::new(BmmEffectiveType::from(class_as_type(stub))))
} else {
None
}
}
};
Ok(BmmParameterType {
name: name.to_owned(),
type_constraint,
inheritance_precursor: None,
})
}
fn build_properties(
builder: &Builder<'_>,
persisted: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<Option<BTreeMap<String, BmmProperty>>, PBmmReadError> {
let Some(properties) = persisted.properties() else {
return Ok(None);
};
let mut out = BTreeMap::new();
for (key, property) in properties {
out.insert(
key.clone(),
build_property(builder, persisted, property, visiting)?,
);
}
Ok((!out.is_empty()).then_some(out))
}
#[expect(
clippy::too_many_lines,
reason = "one arm per P_BMM_PROPERTY subtype; splitting the dispatch would hide the five-way persisted-property → v3 BMM_PROPERTY mapping"
)]
fn build_property(
builder: &Builder<'_>,
owner: &PBmmClass,
property: &PBmmProperty,
visiting: &mut BTreeSet<String>,
) -> Result<BmmProperty, PBmmReadError> {
let class_name = owner.name();
match property {
PBmmProperty::PBmmSingleProperty(single) => {
let context = property_context(class_name, &single.name);
let (r#type, constraint) = match (
single.type_def.as_ref(),
single.type_ref.as_ref(),
single.r#type.as_deref(),
) {
(Some(type_def), _, _) => (
build_unitary_type(builder, &context, type_def, owner, visiting)?,
None,
),
(None, Some(type_ref), _) => (
build_named_unitary_type(builder, &context, &type_ref.r#type, owner, visiting)?,
type_ref.value_constraint.as_deref(),
),
(None, None, Some(name)) => (
build_named_unitary_type(builder, &context, name, owner, visiting)?,
None,
),
(None, None, None) => {
return Err(PBmmReadError::TypeDefinitionMissing { context });
}
};
Ok(BmmProperty::BmmUnitaryProperty(unitary_property(
&single.name,
single.documentation.as_deref(),
with_value_constraint(r#type, constraint),
single.is_mandatory,
single.is_im_runtime,
single.is_im_infrastructure,
)))
}
PBmmProperty::PBmmSinglePropertyOpen(open) => {
let context = property_context(class_name, &open.name);
let parameter = match (open.type_ref.as_ref(), open.r#type.as_deref()) {
(Some(type_ref), _) => type_ref.r#type.as_str(),
(None, Some(name)) => name,
(None, None) => {
return Err(PBmmReadError::TypeDefinitionMissing { context });
}
};
let r#type = BmmUnitaryType::BmmParameterType(Box::new(open_parameter_type(
builder, owner, &open.name, parameter, visiting,
)?));
Ok(BmmProperty::BmmUnitaryProperty(unitary_property(
&open.name,
open.documentation.as_deref(),
r#type,
open.is_mandatory,
open.is_im_runtime,
open.is_im_infrastructure,
)))
}
PBmmProperty::PBmmGenericProperty(generic) => {
let context = property_context(class_name, &generic.name);
let Some(type_def) = generic.type_def.as_ref() else {
return Err(PBmmReadError::TypeDefinitionMissing { context });
};
let r#type = BmmUnitaryType::BmmGenericType(build_generic_type(
builder, &context, type_def, owner, visiting,
)?);
Ok(BmmProperty::BmmUnitaryProperty(unitary_property(
&generic.name,
generic.documentation.as_deref(),
r#type,
generic.is_mandatory,
generic.is_im_runtime,
generic.is_im_infrastructure,
)))
}
PBmmProperty::PBmmContainerProperty(PBmmContainerProperty::PBmmContainerProperty(
container,
)) => {
let context = property_context(class_name, &container.name);
let Some(type_def) = container.type_def.as_ref() else {
return Err(PBmmReadError::TypeDefinitionMissing { context });
};
let r#type = build_container_type(builder, &context, type_def, owner, visiting)?;
Ok(BmmProperty::BmmContainerProperty(
BmmContainerProperty::BmmContainerProperty(BmmContainerPropertyData {
name: container.name.clone(),
documentation: documentation_of(container.documentation.as_deref()),
extensions: None,
r#type,
is_nullable: is_nullable(container.is_mandatory),
is_synthesised_generic: None,
feature_extensions: present(Vec::new()),
group: group_back_reference(),
is_im_runtime: container.is_im_runtime,
is_im_infrastructure: container.is_im_infrastructure,
is_composition: None,
cardinality: container.cardinality.as_ref().map(multiplicity_of),
}),
))
}
PBmmProperty::PBmmContainerProperty(
PBmmContainerProperty::PBmmIndexedContainerProperty(indexed),
) => {
let context = property_context(class_name, &indexed.name);
let Some(type_def) = indexed.type_def.as_ref() else {
return Err(PBmmReadError::TypeDefinitionMissing { context });
};
let r#type =
build_indexed_container_type(builder, &context, type_def, owner, visiting)?;
Ok(BmmProperty::BmmContainerProperty(
BmmContainerProperty::BmmIndexedContainerProperty(BmmIndexedContainerProperty {
name: indexed.name.clone(),
documentation: documentation_of(indexed.documentation.as_deref()),
extensions: None,
r#type,
is_nullable: is_nullable(indexed.is_mandatory),
is_synthesised_generic: None,
feature_extensions: present(Vec::new()),
group: group_back_reference(),
is_im_runtime: indexed.is_im_runtime,
is_im_infrastructure: indexed.is_im_infrastructure,
is_composition: None,
cardinality: indexed.cardinality.as_ref().map(multiplicity_of),
}),
))
}
}
}
fn is_nullable(is_mandatory: Option<bool>) -> Option<bool> {
is_mandatory.map(|mandatory| !mandatory)
}
fn unitary_property(
name: &str,
documentation: Option<&str>,
r#type: BmmUnitaryType,
is_mandatory: Option<bool>,
is_im_runtime: Option<bool>,
is_im_infrastructure: Option<bool>,
) -> crate::v1_1::bmm3::core::feature::bmm_unitary_property::BmmUnitaryProperty {
crate::v1_1::bmm3::core::feature::bmm_unitary_property::BmmUnitaryProperty {
name: name.to_owned(),
documentation: documentation_of(documentation),
extensions: None,
r#type,
is_nullable: is_nullable(is_mandatory),
is_synthesised_generic: None,
feature_extensions: present(Vec::new()),
group: group_back_reference(),
is_im_runtime,
is_im_infrastructure,
is_composition: None,
}
}
fn with_value_constraint(r#type: BmmUnitaryType, constraint: Option<&str>) -> BmmUnitaryType {
let Some(spec) = value_set_spec(constraint) else {
return r#type;
};
match r#type {
BmmUnitaryType::BmmSimpleType(mut simple) => {
simple.value_constraint = Some(spec);
BmmUnitaryType::BmmSimpleType(simple)
}
BmmUnitaryType::BmmGenericType(mut generic) => {
generic.value_constraint = Some(spec);
BmmUnitaryType::BmmGenericType(generic)
}
other => other,
}
}
fn build_unitary_type(
builder: &Builder<'_>,
context: &str,
r#type: &PBmmType,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmUnitaryType, PBmmReadError> {
match r#type {
PBmmType::PBmmSimpleType(simple) => {
let built =
build_named_unitary_type(builder, context, &simple.r#type, owner, visiting)?;
Ok(with_value_constraint(
built,
simple.value_constraint.as_deref(),
))
}
PBmmType::PBmmOpenType(open) => Ok(BmmUnitaryType::BmmParameterType(Box::new(
open_parameter_type(builder, owner, context, &open.r#type, visiting)?,
))),
PBmmType::PBmmGenericType(generic) => Ok(BmmUnitaryType::BmmGenericType(
build_generic_type(builder, context, generic, owner, visiting)?,
)),
PBmmType::PBmmContainerType(container) => {
Ok(build_container_type(builder, context, container, owner, visiting)?.unitary_type())
}
}
}
fn build_base_unitary_type(
builder: &Builder<'_>,
context: &str,
r#type: &PBmmBaseType,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmUnitaryType, PBmmReadError> {
match r#type {
PBmmBaseType::PBmmSimpleType(simple) => {
let built =
build_named_unitary_type(builder, context, &simple.r#type, owner, visiting)?;
Ok(with_value_constraint(
built,
simple.value_constraint.as_deref(),
))
}
PBmmBaseType::PBmmOpenType(open) => Ok(BmmUnitaryType::BmmParameterType(Box::new(
open_parameter_type(builder, owner, context, &open.r#type, visiting)?,
))),
PBmmBaseType::PBmmGenericType(generic) => Ok(BmmUnitaryType::BmmGenericType(
build_generic_type(builder, context, generic, owner, visiting)?,
)),
}
}
pub(super) fn build_named_unitary_type(
builder: &Builder<'_>,
context: &str,
name: &str,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmUnitaryType, PBmmReadError> {
if builder.find_generic_parameter(owner, name).is_some() {
return Ok(BmmUnitaryType::BmmParameterType(Box::new(
open_parameter_type(builder, owner, context, name, visiting)?,
)));
}
let stub = build_class(
builder,
builder.entry(context, name)?,
Depth::Stub,
visiting,
)?;
Ok(match class_as_type(stub) {
BmmModelType::BmmGenericType(generic) => BmmUnitaryType::BmmGenericType(generic),
BmmModelType::BmmSimpleType(simple) => BmmUnitaryType::BmmSimpleType(simple),
})
}
fn open_parameter_type(
builder: &Builder<'_>,
owner: &PBmmClass,
property: &str,
parameter: &str,
visiting: &mut BTreeSet<String>,
) -> Result<BmmParameterType, PBmmReadError> {
let declared = builder
.find_generic_parameter(owner, parameter)
.ok_or_else(|| PBmmReadError::UndeclaredGenericParameter {
class: owner.name().to_owned(),
property: property.to_owned(),
parameter: parameter.to_owned(),
})?;
build_parameter_type(
builder,
owner.name(),
&declared.name,
declared.conforms_to_type.as_deref(),
visiting,
)
}
fn build_generic_type(
builder: &Builder<'_>,
context: &str,
generic: &PBmmGenericType,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmGenericType, PBmmReadError> {
let entry = builder.entry(context, &generic.root_type)?;
let BmmClass::BmmGenericClass(base_class) = build_class(builder, entry, Depth::Stub, visiting)?
else {
return Err(PBmmReadError::NotAGenericClass {
context: context.to_owned(),
type_name: generic.root_type.clone(),
});
};
let formals: Vec<&str> = base_class
.generic_parameters
.keys()
.map(String::as_str)
.collect();
let mut generic_parameters: Vec<BmmUnitaryType> = Vec::new();
for actual in generic.bind_parameters(context, &formals)? {
generic_parameters.push(match actual {
ActualParameter::Named(name) => {
build_named_unitary_type(builder, context, name, owner, visiting)?
}
ActualParameter::Structured(parameter) => {
build_unitary_type(builder, context, parameter, owner, visiting)?
}
});
}
Ok(BmmGenericType {
value_constraint: value_set_spec(generic.value_constraint.as_deref()),
base_class,
generic_parameters: openehr_base::containers::NonEmptyVec::new(generic_parameters)
.map_err(|empty| PBmmReadError::TypeDefinitionMissing {
context: format!("generic type `{}`: {empty}", generic.root_type),
})?,
})
}
fn build_container_type(
builder: &Builder<'_>,
context: &str,
container: &PBmmContainerType,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmContainerType, PBmmReadError> {
match container {
PBmmContainerType::PBmmIndexedContainerType(indexed) => {
Ok(BmmContainerType::BmmIndexedContainerType(Box::new(
build_indexed_container_type(builder, context, indexed, owner, visiting)?,
)))
}
PBmmContainerType::PBmmContainerType(data) => {
Ok(BmmContainerType::BmmContainerType(BmmContainerTypeData {
container_class: generic_container_class(
builder,
context,
&data.container_type,
visiting,
)?,
item_type: Box::new(build_container_target(
builder,
context,
data.r#type.as_deref(),
data.type_def.as_ref(),
owner,
visiting,
)?),
is_ordered: None,
is_unique: None,
}))
}
}
}
fn build_indexed_container_type(
builder: &Builder<'_>,
context: &str,
indexed: &PBmmIndexedContainerType,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmIndexedContainerType, PBmmReadError> {
let index_stub = build_class(
builder,
builder.entry(context, &indexed.index_type)?,
Depth::Stub,
visiting,
)?;
let BmmModelType::BmmSimpleType(index_type) = class_as_type(index_stub) else {
return Err(PBmmReadError::NotAGenericClass {
context: context.to_owned(),
type_name: indexed.index_type.clone(),
});
};
Ok(BmmIndexedContainerType {
container_class: generic_container_class(
builder,
context,
&indexed.container_type,
visiting,
)?,
item_type: build_container_target(
builder,
context,
indexed.r#type.as_deref(),
indexed.type_def.as_ref(),
owner,
visiting,
)?,
is_ordered: None,
is_unique: None,
index_type,
})
}
fn generic_container_class(
builder: &Builder<'_>,
context: &str,
name: &str,
visiting: &mut BTreeSet<String>,
) -> Result<BmmGenericClass, PBmmReadError> {
let stub = build_class(
builder,
builder.entry(context, name)?,
Depth::Stub,
visiting,
)?;
match stub {
BmmClass::BmmGenericClass(generic) => Ok(generic),
BmmClass::BmmSimpleClass(_) => Err(PBmmReadError::NotAGenericClass {
context: context.to_owned(),
type_name: name.to_owned(),
}),
}
}
fn build_container_target(
builder: &Builder<'_>,
context: &str,
name: Option<&str>,
type_def: Option<&PBmmBaseType>,
owner: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<BmmUnitaryType, PBmmReadError> {
match (type_def, name) {
(Some(nested), _) => build_base_unitary_type(builder, context, nested, owner, visiting),
(None, Some(name)) => build_named_unitary_type(builder, context, name, owner, visiting),
(None, None) => Err(PBmmReadError::ContainerTargetTypeMissing {
context: context.to_owned(),
}),
}
}
fn build_functions(
builder: &Builder<'_>,
persisted: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<Option<BTreeMap<String, BmmFunction>>, PBmmReadError> {
let Some(functions) = persisted.functions() else {
return Ok(None);
};
let mut out = BTreeMap::new();
for (key, function) in functions {
if function.result.is_none() {
continue;
}
out.insert(
key.clone(),
build_function(builder, persisted, function, visiting)?,
);
}
Ok((!out.is_empty()).then_some(out))
}
fn build_procedures(
builder: &Builder<'_>,
persisted: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<Option<BTreeMap<String, BmmProcedure>>, PBmmReadError> {
let Some(functions) = persisted.functions() else {
return Ok(None);
};
let mut out = BTreeMap::new();
for (key, function) in functions {
if function.result.is_some() {
continue;
}
out.insert(
key.clone(),
build_procedure(builder, persisted, function, visiting)?,
);
}
Ok((!out.is_empty()).then_some(out))
}
fn build_function(
builder: &Builder<'_>,
owner: &PBmmClass,
function: &PBmmFunction,
visiting: &mut BTreeSet<String>,
) -> Result<BmmFunction, PBmmReadError> {
let context = format!("class `{}` function `{}`", owner.name(), function.name);
let result = function
.result
.as_ref()
.ok_or_else(|| PBmmReadError::TypeDefinitionMissing {
context: context.clone(),
})?;
let result_type = BmmType::from(build_unitary_type(
builder, &context, result, owner, visiting,
)?);
Ok(BmmFunction {
name: function.name.clone(),
documentation: documentation_of(function.documentation.as_deref()),
extensions: None,
r#type: result_type.clone(),
is_nullable: function.is_nullable,
is_synthesised_generic: None,
feature_extensions: present(Vec::new()),
group: group_back_reference(),
parameters: present(build_parameters(builder, owner, function, visiting)?),
pre_conditions: present(routine_conditions(
builder,
owner,
function,
AssertionKind::PreCondition,
Some(&result_type),
)),
post_conditions: present(routine_conditions(
builder,
owner,
function,
AssertionKind::PostCondition,
Some(&result_type),
)),
definition: None,
operator_definition: None,
result: Box::new(BmmResult {
name: RESULT_VARIABLE_NAME.to_owned(),
documentation: None,
extensions: None,
r#type: result_type,
is_nullable: function.is_nullable,
}),
})
}
pub const RESULT_VARIABLE_NAME: &str = "Result";
fn build_procedure(
builder: &Builder<'_>,
owner: &PBmmClass,
function: &PBmmFunction,
visiting: &mut BTreeSet<String>,
) -> Result<BmmProcedure, PBmmReadError> {
Ok(BmmProcedure {
name: function.name.clone(),
documentation: documentation_of(function.documentation.as_deref()),
extensions: None,
r#type: crate::v1_1::bmm3::core::entity::bmm_status_type::BmmStatusType {},
is_nullable: function.is_nullable,
is_synthesised_generic: None,
feature_extensions: present(Vec::new()),
group: group_back_reference(),
parameters: present(build_parameters(builder, owner, function, visiting)?),
pre_conditions: present(routine_conditions(
builder,
owner,
function,
AssertionKind::PreCondition,
None,
)),
post_conditions: present(routine_conditions(
builder,
owner,
function,
AssertionKind::PostCondition,
None,
)),
definition: None,
})
}
fn routine_conditions(
builder: &Builder<'_>,
owner: &PBmmClass,
function: &PBmmFunction,
kind: AssertionKind,
result_type: Option<&BmmType>,
) -> Vec<BmmAssertion> {
let source = match kind {
AssertionKind::PreCondition => function.pre_conditions.as_ref(),
AssertionKind::PostCondition => function.post_conditions.as_ref(),
AssertionKind::Invariant => None,
};
let scope = AssertionScope {
builder,
owner,
properties: None,
statics: None,
functions: None,
result_type,
};
let mut findings: Vec<PBmmValidityFinding> = Vec::new();
let built = build_assertions(
&scope,
kind,
Some(function.name.as_str()),
source,
&mut findings,
);
builder.findings.borrow_mut().extend(findings);
built
}
fn build_parameters(
builder: &Builder<'_>,
owner: &PBmmClass,
function: &PBmmFunction,
visiting: &mut BTreeSet<String>,
) -> Result<Vec<BmmParameter>, PBmmReadError> {
let mut out: Vec<BmmParameter> = Vec::new();
for parameter in function.parameters.iter().flatten().map(|(_, p)| p) {
let (name, documentation, r#type, is_nullable) = match parameter {
PBmmFunctionParameter::PBmmSingleFunctionParameter(single) => {
let context = parameter_context(owner.name(), &function.name, &single.name);
let r#type = build_named_unitary_type(
builder,
&context,
single.r#type.as_str(),
owner,
visiting,
)?;
(
single.name.clone(),
single.documentation.clone(),
r#type,
single.is_nullable,
)
}
PBmmFunctionParameter::PBmmSingleFunctionParameterOpen(open) => {
let context = parameter_context(owner.name(), &function.name, &open.name);
let r#type = BmmUnitaryType::BmmParameterType(Box::new(open_parameter_type(
builder,
owner,
&context,
open.r#type.as_str(),
visiting,
)?));
(
open.name.clone(),
open.documentation.clone(),
r#type,
open.is_nullable,
)
}
PBmmFunctionParameter::PBmmGenericFunctionParameter(generic) => {
let context = parameter_context(owner.name(), &function.name, &generic.name);
(
generic.name.clone(),
generic.documentation.clone(),
BmmUnitaryType::BmmGenericType(build_generic_type(
builder,
&context,
&generic.type_def,
owner,
visiting,
)?),
generic.is_nullable,
)
}
PBmmFunctionParameter::PBmmContainerFunctionParameter(container) => {
let context = parameter_context(owner.name(), &function.name, &container.name);
(
container.name.clone(),
container.documentation.clone(),
build_container_type(builder, &context, &container.type_def, owner, visiting)?
.unitary_type(),
container.is_nullable,
)
}
};
out.push(BmmParameter {
name,
documentation: documentation_of(documentation.as_deref()),
extensions: None,
r#type: BmmType::from(r#type),
is_nullable,
direction: None,
});
}
Ok(out)
}
fn parameter_context(class: &str, routine: &str, parameter: &str) -> String {
format!("class `{class}` routine `{routine}` parameter `{parameter}`")
}
fn build_constants(
builder: &Builder<'_>,
persisted: &PBmmClass,
visiting: &mut BTreeSet<String>,
) -> Result<Option<BTreeMap<String, BmmStatic>>, PBmmReadError> {
let Some(constants) = persisted.constants() else {
return Ok(None);
};
let mut out = BTreeMap::new();
for (key, constant) in constants {
let Some(value_literal) = constant.value.clone() else {
builder
.findings
.borrow_mut()
.push(PBmmValidityFinding::ConstantNotMaterialised {
class: persisted.name().to_owned(),
constant: constant.name.clone(),
});
continue;
};
out.insert(
key.clone(),
BmmStatic::BmmConstant(build_constant(
builder,
persisted,
constant,
value_literal,
visiting,
)?),
);
}
Ok((!out.is_empty()).then_some(out))
}
fn build_constant(
builder: &Builder<'_>,
owner: &PBmmClass,
constant: &PBmmConstant,
value_literal: String,
visiting: &mut BTreeSet<String>,
) -> Result<BmmConstant, PBmmReadError> {
let context = format!("class `{}` constant `{}`", owner.name(), constant.name);
let unitary = build_named_unitary_type(builder, &context, &constant.r#type, owner, visiting)?;
let BmmUnitaryType::BmmSimpleType(simple) = unitary.clone() else {
return Err(PBmmReadError::UnknownType {
context,
type_name: constant.r#type.clone(),
});
};
Ok(BmmConstant {
name: constant.name.clone(),
documentation: documentation_of(constant.documentation.as_deref()),
extensions: None,
r#type: BmmType::from(unitary),
is_nullable: Some(false),
is_synthesised_generic: None,
feature_extensions: present(Vec::new()),
group: group_back_reference(),
generator: BmmLiteralValue::BmmPrimitiveValue(BmmPrimitiveValue::BmmPrimitiveValue(
BmmPrimitiveValueData {
value_literal,
value: None,
syntax: None,
r#type: simple,
},
)),
})
}
fn build_enumeration(
builder: &Builder<'_>,
core: ClassCore,
persisted: &PBmmEnumeration,
visiting: &mut BTreeSet<String>,
) -> Result<BmmEnumeration, PBmmReadError> {
let context = format!("enumeration class `{}`", core.name);
let underlying = build_named_unitary_type(
builder,
&context,
persisted.underlying_type_name(),
&PBmmClass::PBmmEnumeration(persisted.clone()),
visiting,
)?;
let BmmUnitaryType::BmmSimpleType(underlying) = underlying else {
return Err(PBmmReadError::UnknownType {
context,
type_name: persisted.underlying_type_name().to_owned(),
});
};
let item_names = persisted.item_names().to_vec();
match persisted {
PBmmEnumeration::PBmmEnumerationInteger(_) => {
let item_values = integer_item_values(&core.name, persisted, &underlying)?;
Ok(BmmEnumeration::BmmEnumerationInteger(class_from_core!(
BmmEnumerationInteger,
core,
item_names: present(item_names),
item_values: present(item_values),
)))
}
PBmmEnumeration::PBmmEnumerationString(_) => {
Ok(BmmEnumeration::BmmEnumerationString(class_from_core!(
BmmEnumerationString,
core,
item_names: present(item_names),
item_values: present(
persisted
.item_values()
.iter()
.map(|value| {
crate::v1_1::bmm3::core::literal_value::bmm_string_value::BmmStringValue {
value_literal: literal_form(value),
value: value
.as_str()
.map_or_else(|| literal_form(value), str::to_owned),
syntax: None,
r#type: underlying.clone(),
}
})
.collect(),
),
)))
}
PBmmEnumeration::PBmmEnumeration(_) => {
Ok(BmmEnumeration::BmmEnumeration(class_from_core!(
BmmEnumerationData,
core,
item_names: present(item_names),
item_values: present(
persisted
.item_values()
.iter()
.map(|value| {
BmmPrimitiveValue::BmmPrimitiveValue(BmmPrimitiveValueData {
value_literal: literal_form(value),
value: Some(value.clone()),
syntax: None,
r#type: underlying.clone(),
})
})
.collect(),
),
)))
}
}
}
fn integer_item_values(
class: &str,
persisted: &PBmmEnumeration,
underlying: &BmmSimpleType,
) -> Result<Vec<BmmIntegerValue>, PBmmReadError> {
let names = persisted.item_names();
persisted
.item_values()
.iter()
.enumerate()
.map(|(index, value)| {
let native = value
.as_i64()
.and_then(|wide| i32::try_from(wide).ok())
.ok_or_else(|| PBmmReadError::EnumerationItemValueNotAnInteger {
class: class.to_owned(),
index,
item: names.get(index).cloned(),
value: literal_form(value),
})?;
Ok(BmmIntegerValue {
value_literal: literal_form(value),
value: native,
syntax: None,
r#type: underlying.clone(),
})
})
.collect()
}
fn literal_form(value: &serde_json::Value) -> String {
match value {
serde_json::Value::String(text) => text.clone(),
other => other.to_string(),
}
}
fn build_packages(
builder: &Builder<'_>,
packages: &BTreeMap<String, PBmmPackage>,
prefix: &str,
) -> Result<BTreeMap<String, BmmPackage>, PBmmReadError> {
let mut out = BTreeMap::new();
for package in packages.values() {
let path = qualify(prefix, &package.name);
let mut members: Vec<BmmModule> = Vec::new();
for class in package.classes.iter().flatten() {
let entry = builder.classes.get(&class.to_uppercase()).ok_or_else(|| {
PBmmReadError::ClassNotDefined {
package: package.name.clone(),
class: class.clone(),
}
})?;
members.push(as_module(&build_class(
builder,
entry,
Depth::Stub,
&mut BTreeSet::new(),
)?));
}
let children = build_packages(builder, &package.packages, &path)?;
out.insert(
package.name.to_uppercase(),
BmmPackage {
name: path,
documentation: None,
extensions: None,
packages: (!children.is_empty()).then_some(children),
members: present(members),
},
);
}
Ok(out)
}
#[cfg(test)]
mod tests;