use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::path::Path;
use crate::v1_1::bmm::core::bmm_definitions::BmmDefinitionsData;
use crate::v1_1::bmm::core::bmm_model::BmmModel;
use crate::v1_1::bmm::rm_access::error::RmAccessError;
use crate::v1_1::bmm::rm_access::schema_descriptor::SchemaDescriptor;
use crate::v1_1::bmm_persistence::create_model::create_bmm_model;
use crate::v1_1::bmm_persistence::include_resolution::resolve_includes;
use crate::v1_1::bmm_persistence::p_bmm_schema::PBmmSchema;
use crate::v1_1::bmm_persistence::p_bmm_schema_descriptor::PBmmSchemaDescriptor;
use crate::v1_1::bmm_persistence::reader::read_schema;
use openehr_base::containers::present;
pub const P_BMM_GENERATION: &str = "2";
pub const META_SCHEMA_PATH: &str = "schema_path";
pub const META_BMM_VERSION: &str = "bmm_version";
pub const META_MODEL_PUBLISHER: &str = "model_publisher";
pub const META_SCHEMA_NAME: &str = "schema_name";
pub const META_MODEL_RELEASE: &str = "model_release";
pub const META_SCHEMA_REVISION: &str = "schema_revision";
pub const META_SCHEMA_LIFECYCLE_STATE: &str = "schema_lifecycle_state";
pub const META_SCHEMA_DESCRIPTION: &str = "schema_description";
macro_rules! descriptor {
($value:expr, |$leaf:ident| $body:expr) => {
match $value {
SchemaDescriptor::PBmmSchemaDescriptor($leaf) => $body,
SchemaDescriptor::SchemaDescriptor($leaf) => $body,
}
};
}
impl SchemaDescriptor {
pub fn from_schema_file(path: &Path) -> Result<Self, RmAccessError> {
let display = path.display().to_string();
let src = std::fs::read_to_string(path).map_err(|source| RmAccessError::File {
path: display.clone(),
source,
})?;
let schema = read_schema(&src).map_err(|source| RmAccessError::Schema {
path: display.clone(),
source,
})?;
Ok(Self::PBmmSchemaDescriptor(PBmmSchemaDescriptor {
p_schema: None,
schema: None,
schema_id: schema.schema_id(),
meta_data: meta_data_of(&schema, &display),
includes: present(include_ids_of(&schema)),
bmm_schema: None,
}))
}
#[must_use]
pub fn schema_id(&self) -> &str {
descriptor!(self, |leaf| leaf.schema_id.as_str())
}
#[must_use]
pub fn meta_data(&self) -> &BTreeMap<String, String> {
descriptor!(self, |leaf| &leaf.meta_data)
}
#[must_use]
pub fn includes(&self) -> &[String] {
descriptor!(self, |leaf| leaf.includes.as_deref().unwrap_or_default())
}
#[must_use]
pub fn schema_path(&self) -> Option<&str> {
self.meta_data().get(META_SCHEMA_PATH).map(String::as_str)
}
#[must_use]
pub fn p_schema(&self) -> Option<&PBmmSchema> {
match self {
SchemaDescriptor::PBmmSchemaDescriptor(leaf) => {
leaf.p_schema.as_ref().or(leaf.bmm_schema.as_ref())
}
SchemaDescriptor::SchemaDescriptor(leaf) => leaf.p_schema.as_ref(),
}
}
#[must_use]
pub fn schema(&self) -> Option<&BmmModel> {
descriptor!(self, |leaf| leaf.schema.as_ref())
}
#[must_use]
pub fn is_top_level(&self, all_schemas: &BTreeMap<String, SchemaDescriptor>) -> bool {
let own = self.schema_id().to_lowercase();
!all_schemas.values().any(|other| {
other.schema_id().to_lowercase() != own
&& other.includes().iter().any(|id| id.to_lowercase() == own)
})
}
#[must_use]
pub fn is_bmm_compatible(&self) -> bool {
match self.meta_data().get(META_BMM_VERSION) {
None => true,
Some(found) => major_of(found) == expected_generation(),
}
}
pub fn load(&mut self) -> Result<(), RmAccessError> {
let path = self
.schema_path()
.ok_or_else(|| RmAccessError::NoSchemaPath {
schema_id: self.schema_id().to_owned(),
})?
.to_owned();
let src = std::fs::read_to_string(&path).map_err(|source| RmAccessError::File {
path: path.clone(),
source,
})?;
let schema = read_schema(&src).map_err(|source| RmAccessError::Schema {
path: path.clone(),
source,
})?;
let meta_data = meta_data_of(&schema, &path);
let includes = include_ids_of(&schema);
match self {
SchemaDescriptor::PBmmSchemaDescriptor(leaf) => {
leaf.meta_data = meta_data;
leaf.includes = present(includes);
leaf.schema = None;
leaf.bmm_schema = None;
leaf.p_schema = Some(schema);
}
SchemaDescriptor::SchemaDescriptor(leaf) => {
leaf.meta_data = meta_data;
leaf.includes = present(includes);
leaf.schema = None;
leaf.p_schema = Some(schema);
}
}
Ok(())
}
pub fn validate(&self) -> Result<(), RmAccessError> {
let Some(schema) = self.p_schema() else {
return Err(RmAccessError::NotLoaded {
schema_id: self.schema_id().to_owned(),
});
};
if !self.is_bmm_compatible() {
return Err(RmAccessError::IncompatibleBmmVersion {
schema_id: self.schema_id().to_owned(),
found: self
.meta_data()
.get(META_BMM_VERSION)
.cloned()
.unwrap_or_default(),
expected: expected_generation().to_owned(),
});
}
let loaded = schema.schema_id();
if loaded != self.schema_id() {
return Err(RmAccessError::SchemaIdMismatch {
path: self.schema_path().unwrap_or("").to_owned(),
descriptor: self.schema_id().to_owned(),
loaded,
});
}
Ok(())
}
pub fn validate_includes(&self, all_schemas_list: &[String]) -> Result<(), RmAccessError> {
let available: BTreeSet<String> = all_schemas_list
.iter()
.map(|id| id.to_lowercase())
.collect();
for id in self.includes() {
if !available.contains(&id.to_lowercase()) {
return Err(RmAccessError::MissingInclude {
requester: self.schema_id().to_owned(),
id: id.clone(),
});
}
}
Ok(())
}
pub fn create_schema(
&mut self,
all_p_schemas: &BTreeMap<String, PBmmSchema>,
) -> Result<(), RmAccessError> {
let Some(persisted) = self.p_schema().cloned() else {
return Err(RmAccessError::NotLoaded {
schema_id: self.schema_id().to_owned(),
});
};
let path = self.schema_path().unwrap_or("").to_owned();
let resolved =
resolve_includes(persisted, all_p_schemas).map_err(|source| RmAccessError::Schema {
path: path.clone(),
source,
})?;
let model =
create_bmm_model(&resolved).map_err(|source| RmAccessError::Schema { path, source })?;
descriptor!(self, |leaf| leaf.schema = Some(model));
Ok(())
}
#[must_use]
pub fn logical_model_name(&self) -> String {
let publisher = self
.meta_data()
.get(META_MODEL_PUBLISHER)
.map(String::as_str)
.unwrap_or_default();
let name = self
.meta_data()
.get(META_SCHEMA_NAME)
.map(String::as_str)
.unwrap_or_default();
format!("{publisher}_{name}").to_lowercase()
}
}
fn expected_generation() -> &'static str {
if BmmDefinitionsData::BMM_INTERNAL_VERSION.is_empty() {
P_BMM_GENERATION
} else {
major_of(BmmDefinitionsData::BMM_INTERNAL_VERSION)
}
}
fn major_of(version: &str) -> &str {
version.split('.').next().unwrap_or(version)
}
fn meta_data_of(schema: &PBmmSchema, path: &str) -> BTreeMap<String, String> {
let mut out: BTreeMap<String, String> = BTreeMap::new();
for (key, value) in [
(META_BMM_VERSION, schema.bmm_version.as_str()),
(META_MODEL_PUBLISHER, schema.rm_publisher.as_str()),
(META_SCHEMA_NAME, schema.schema_name.as_str()),
(META_MODEL_RELEASE, schema.rm_release.as_str()),
(META_SCHEMA_REVISION, schema.schema_revision.as_str()),
(
META_SCHEMA_LIFECYCLE_STATE,
schema.schema_lifecycle_state.as_str(),
),
(META_SCHEMA_DESCRIPTION, schema.schema_description.as_str()),
(META_SCHEMA_PATH, path),
] {
if !value.is_empty() {
out.insert(key.to_owned(), value.to_owned());
}
}
out
}
fn include_ids_of(schema: &PBmmSchema) -> Vec<String> {
schema
.includes
.iter()
.flat_map(BTreeMap::values)
.map(|spec| spec.id.clone())
.collect()
}