#![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 openehr_rm::v1_2::model::declared_concrete_type;
use openehr_rm::v1_2::validate::terminology::{Slot, slot_is_violated, slots_for};
use serde_json::Value;
use super::{ValidationKind, ValidationMessage, norm_path, push};
pub(crate) fn terminology_pass(
out: &mut Vec<ValidationMessage>,
v: &Value,
path: &mut String,
declared: Option<&str>,
) {
use std::fmt::Write as _;
let Some(obj) = v.as_object() else { return };
let this_type = obj
.get("_type")
.and_then(Value::as_str)
.or(declared)
.unwrap_or("");
for slot in slots_for(this_type) {
if let Some(node) = obj.get(slot.field) {
let base = path.len();
let _ = write!(path, "/{}", slot.field);
check_code(out, slot, node, path);
path.truncate(base);
}
}
for (k, val) in obj {
if k.starts_with('_') {
continue;
}
let child_declared = declared_concrete_type(this_type, k);
match val {
Value::Array(a) => {
for (i, item) in a.iter().enumerate() {
if item.is_object() {
let base = path.len();
let _ = write!(path, "/{k}[{i}]");
terminology_pass(out, item, path, child_declared);
path.truncate(base);
}
}
}
Value::Object(_) => {
let base = path.len();
let _ = write!(path, "/{k}");
terminology_pass(out, val, path, child_declared);
path.truncate(base);
}
_ => {}
}
}
}
fn check_code(out: &mut Vec<ValidationMessage>, slot: &Slot, node: &Value, path: &str) {
if !slot_is_violated(slot, node) {
return;
}
let code = node
.get("defining_code")
.unwrap_or(node)
.get("code_string")
.and_then(Value::as_str)
.unwrap_or("");
push(
out,
norm_path(path),
format!(
"code '{code}' is not a valid {} (openEHR terminology)",
slot.binding.label()
),
ValidationKind::Terminology,
);
}