#![allow(
dead_code,
reason = "consumed by the RM ⇄ sim walkers landing in this same rewrite; drop this allow with their arrival"
)]
#![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::paths::{PathSegment, RmPath, select_children};
use serde_json::Value;
pub(crate) fn parse(rel: &str) -> Vec<PathSegment> {
rel.parse::<RmPath>()
.map(|p| p.segments)
.unwrap_or_default()
}
pub(crate) fn relative(parent_aql: &str, child_aql: &str) -> Vec<PathSegment> {
parse(child_aql.strip_prefix(parent_aql).unwrap_or(child_aql))
}
pub(crate) fn navigate<'a>(roots: &[&'a Value], segs: &[PathSegment]) -> Vec<&'a Value> {
let mut current: Vec<&Value> = roots.to_vec();
for seg in segs {
current = current
.iter()
.flat_map(|n| select_children(n, seg))
.collect();
}
current
}
pub(crate) fn select_children_matched<'a>(
container: &'a Value,
seg: &PathSegment,
allow_name_fallback: bool,
) -> Vec<&'a Value> {
let strict = select_children(container, seg);
if strict.is_empty() && allow_name_fallback && seg.predicate.name_value.is_some() {
let mut id_only = seg.clone();
id_only.predicate.name_value = None;
return select_children(container, &id_only);
}
strict
}
pub(crate) fn select_children_excluding_names<'a>(
container: &'a Value,
seg: &PathSegment,
excluded_names: &[String],
) -> Vec<&'a Value> {
select_children(container, seg)
.into_iter()
.filter(|node| {
node.get("name")
.and_then(|n| n.get("value"))
.and_then(Value::as_str)
.is_none_or(|name| !excluded_names.iter().any(|e| e == name))
})
.collect()
}
pub(crate) fn resolve<'a>(rm: &'a Value, segs: &[PathSegment]) -> Vec<&'a Value> {
navigate(&[rm], segs)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_segments() {
let segs = parse(
"/content[openEHR-EHR-SECTION.ispek_dialog.v1,'Vitals']/items[openEHR-EHR-OBSERVATION.lab_test-hba1c.v1]/data[at0001]/value",
);
assert_eq!(segs.len(), 4);
assert_eq!(segs[0].attribute, "content");
assert_eq!(
segs[0].predicate.archetype_node_id.as_deref(),
Some("openEHR-EHR-SECTION.ispek_dialog.v1")
);
assert_eq!(segs[0].predicate.name_value.as_deref(), Some("Vitals"));
assert_eq!(segs[2].attribute, "data");
assert_eq!(
segs[2].predicate.archetype_node_id.as_deref(),
Some("at0001")
);
assert_eq!(segs[3].attribute, "value");
assert!(segs[3].predicate.is_empty());
}
#[test]
fn relative_strips_parent() {
let rel = relative(
"/content[openEHR-EHR-OBSERVATION.x.v1]",
"/content[openEHR-EHR-OBSERVATION.x.v1]/data[at0001]/events[at0002]",
);
assert_eq!(rel.len(), 2);
assert_eq!(rel[0].attribute, "data");
assert_eq!(rel[1].attribute, "events");
}
#[test]
fn matched_step_falls_back_to_id_only_for_renamed_instances() {
let rm = serde_json::json!({
"content": [
{"archetype_node_id": "openEHR-EHR-EVALUATION.a.v1",
"name": {"value": "Runtime Name"},
"data": {"archetype_node_id": "at0001", "_type": "ITEM_LIST"}}
]
});
let segs = parse("/content[openEHR-EHR-EVALUATION.a.v1,'Template Name']/data[at0001]");
let step = select_children_matched(&rm, &segs[0], true);
assert_eq!(step.len(), 1);
assert_eq!(step[0]["name"]["value"], "Runtime Name");
assert!(select_children_matched(&rm, &segs[0], false).is_empty());
}
#[test]
fn matched_step_still_disambiguates_matching_siblings() {
let rm = serde_json::json!({
"content": [
{"archetype_node_id": "openEHR-EHR-OBSERVATION.x.v1",
"name": {"value": "First"}, "tag": 1},
{"archetype_node_id": "openEHR-EHR-OBSERVATION.x.v1",
"name": {"value": "Second"}, "tag": 2}
]
});
let segs = parse("/content[openEHR-EHR-OBSERVATION.x.v1,'Second']");
let found = select_children_matched(&rm, &segs[0], true);
assert_eq!(found.len(), 1);
assert_eq!(found[0]["tag"], 2);
}
#[test]
fn resolves_through_arrays_and_objects() {
let rm = serde_json::json!({
"data": {
"events": [
{"archetype_node_id": "at0002", "value": {"_type": "DV_TEXT", "value": "a"}},
{"archetype_node_id": "at0009", "value": {"_type": "DV_TEXT", "value": "b"}}
]
}
});
let segs = parse("/data/events[at0002]/value");
let found = resolve(&rm, &segs);
assert_eq!(found.len(), 1);
assert_eq!(found[0]["value"], "a");
}
}