#![expect(
clippy::disallowed_types,
reason = "canonical JSON / Simplified Formats operate on the wire value by definition \
(ITS-JSON; ITS-REST Simplified Formats) — serde_json::Value IS the subject matter \
(#1694)"
)]
use serde_json::{Map, Value, json};
use crate::flat::build::is_locatable;
use crate::flat::webtemplate::model::{CodedName, WebTemplateNode};
pub(crate) fn name_value(display: &str, coded: Option<&CodedName>) -> Value {
match coded {
Some(CodedName {
terminology, code, ..
}) => json!({
"_type": "DV_CODED_TEXT",
"value": display,
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {"_type": "TERMINOLOGY_ID", "value": terminology},
"code_string": code,
},
}),
None => json!({"_type": "DV_TEXT", "value": display}),
}
}
pub(crate) fn stamp_locatable_identity(
obj: &mut Map<String, Value>,
node: &WebTemplateNode,
rm_type: &str,
) {
if !is_locatable(rm_type) {
return;
}
obj.entry("name".to_owned()).or_insert_with(|| {
let text = node
.name
.as_deref()
.map_or_else(|| default_name(rm_type), str::to_owned);
name_value(&text, node.name_coded.as_ref())
});
if let Some(nid) = recorded_archetype_id(node) {
obj.entry("archetype_node_id".to_owned())
.or_insert_with(|| json!(nid));
}
}
pub(crate) fn recorded_archetype_id(node: &WebTemplateNode) -> Option<&str> {
node.archetype_ref.as_deref().or(node.node_id.as_deref())
}
pub(crate) fn default_name(rm_type: &str) -> String {
let words = rm_type
.strip_prefix("ITEM_")
.unwrap_or(rm_type)
.to_lowercase()
.replace('_', " ");
let mut chars = words.chars();
chars.next().map_or_else(String::new, |first| {
first.to_uppercase().chain(chars).collect()
})
}
pub(crate) fn stamp_archetype_details(
obj: &mut Map<String, Value>,
node: &WebTemplateNode,
is_root: bool,
template_id: &str,
rm_version: &str,
) {
let is_root_arch = node
.node_id
.as_deref()
.is_some_and(openehr_rm::v1_2::paths::is_archetype_root_node_id);
if !is_root_arch {
return;
}
obj.entry("archetype_details".to_owned())
.or_insert_with(|| {
let mut a = Map::new();
a.insert("_type".into(), json!("ARCHETYPED"));
a.insert(
"archetype_id".into(),
json!({"_type": "ARCHETYPE_ID", "value": recorded_archetype_id(node)}),
);
if is_root && !template_id.is_empty() {
a.insert(
"template_id".into(),
json!({"_type": "TEMPLATE_ID", "value": template_id}),
);
}
a.insert("rm_version".into(), json!(rm_version));
Value::Object(a)
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_node_without_term_text_is_named_for_its_rm_type() {
let names: Vec<String> = [
"HISTORY",
"ITEM_TREE",
"ITEM_LIST",
"ITEM_SINGLE",
"ITEM_TABLE",
"POINT_EVENT",
]
.into_iter()
.map(default_name)
.collect();
assert_eq!(
names,
["History", "Tree", "List", "Single", "Table", "Point event"]
);
}
#[test]
fn name_value_stamps_coded_and_plain_names() {
let coded = CodedName {
terminology: "local".to_owned(),
code: "at0007".to_owned(),
incoherent: false,
};
let v = name_value("Exclusion", Some(&coded));
assert_eq!(v["_type"], "DV_CODED_TEXT");
assert_eq!(v["defining_code"]["code_string"], "at0007");
assert_eq!(
name_value("Systolic", None),
json!({"_type": "DV_TEXT", "value": "Systolic"})
);
}
}