#![expect(
clippy::disallowed_types,
reason = "dev/verification tooling over JSON artifacts (the catalogue, results, wire \
exchanges), whose shapes belong to the artifacts and the SUT"
)]
use std::collections::BTreeMap;
use serde_json::{Number, Value};
const XSI_NAMESPACE: &str = "http://www.w3.org/2001/XMLSchema-instance";
const JITTER_SEED: u64 = 0x7665_7264_6a69_7474;
const DRAW_STEPS: u32 = 1_000_000;
const MAX_DECIMALS: u32 = 6;
#[derive(Debug, thiserror::Error)]
pub enum ConstraintReadError {
#[error("the operational template is not well-formed XML: {0}")]
Malformed(String),
#[error("the `{element}` bound of a constraint interval is not a number: {text:?}")]
Bound {
element: String,
text: String,
},
}
#[derive(Debug, Clone, Copy)]
struct RealInterval {
lower: Option<f64>,
lower_included: bool,
upper: Option<f64>,
upper_included: bool,
}
impl RealInterval {
const UNBOUNDED: Self = Self {
lower: None,
lower_included: true,
upper: None,
upper_included: true,
};
fn intersect(self, other: Self) -> Self {
let (lower, lower_included) = match (self.lower, other.lower) {
(None, None) => (None, true),
(Some(a), None) => (Some(a), self.lower_included),
(None, Some(b)) => (Some(b), other.lower_included),
(Some(a), Some(b)) if a > b => (Some(a), self.lower_included),
(Some(a), Some(b)) if b > a => (Some(b), other.lower_included),
(Some(a), Some(_)) => (Some(a), self.lower_included && other.lower_included),
};
let (upper, upper_included) = match (self.upper, other.upper) {
(None, None) => (None, true),
(Some(a), None) => (Some(a), self.upper_included),
(None, Some(b)) => (Some(b), other.upper_included),
(Some(a), Some(b)) if a < b => (Some(a), self.upper_included),
(Some(a), Some(b)) if b < a => (Some(b), other.upper_included),
(Some(a), Some(_)) => (Some(a), self.upper_included && other.upper_included),
};
Self {
lower,
lower_included,
upper,
upper_included,
}
}
fn admits_lower(self, value: f64) -> bool {
self.lower.is_none_or(|lower| {
if self.lower_included {
value >= lower
} else {
value > lower
}
})
}
fn admits_upper(self, value: f64) -> bool {
self.upper.is_none_or(|upper| {
if self.upper_included {
value <= upper
} else {
value < upper
}
})
}
}
#[derive(Debug, Clone, Copy)]
struct IntInterval {
lower: Option<i64>,
lower_included: bool,
upper: Option<i64>,
upper_included: bool,
}
impl IntInterval {
fn intersect(self, other: Self) -> Self {
let (lower, lower_included) = match (self.lower, other.lower) {
(None, None) => (None, true),
(Some(a), None) => (Some(a), self.lower_included),
(None, Some(b)) => (Some(b), other.lower_included),
(Some(a), Some(b)) => match a.cmp(&b) {
std::cmp::Ordering::Greater => (Some(a), self.lower_included),
std::cmp::Ordering::Less => (Some(b), other.lower_included),
std::cmp::Ordering::Equal => (Some(a), self.lower_included && other.lower_included),
},
};
let (upper, upper_included) = match (self.upper, other.upper) {
(None, None) => (None, true),
(Some(a), None) => (Some(a), self.upper_included),
(None, Some(b)) => (Some(b), other.upper_included),
(Some(a), Some(b)) => match a.cmp(&b) {
std::cmp::Ordering::Less => (Some(a), self.upper_included),
std::cmp::Ordering::Greater => (Some(b), other.upper_included),
std::cmp::Ordering::Equal => (Some(a), self.upper_included && other.upper_included),
},
};
Self {
lower,
lower_included,
upper,
upper_included,
}
}
fn closed(self) -> Option<(i64, i64)> {
let lower = if self.lower_included {
self.lower?
} else {
self.lower?.checked_add(1)?
};
let upper = if self.upper_included {
self.upper?
} else {
self.upper?.checked_sub(1)?
};
(lower <= upper).then_some((lower, upper))
}
}
#[derive(Debug, Default)]
struct IntervalAccumulator {
lower: Option<String>,
upper: Option<String>,
lower_included: Option<bool>,
upper_included: Option<bool>,
lower_unbounded: bool,
upper_unbounded: bool,
}
impl IntervalAccumulator {
fn is_filled(&self) -> bool {
self.lower.is_some()
|| self.upper.is_some()
|| self.lower_unbounded
|| self.upper_unbounded
|| self.lower_included.is_some()
|| self.upper_included.is_some()
}
fn record(&mut self, element: &str, text: &str) {
let flag = text.trim() == "true";
match element {
"lower" => self.lower = Some(text.trim().to_owned()),
"upper" => self.upper = Some(text.trim().to_owned()),
"lower_included" => self.lower_included = Some(flag),
"upper_included" => self.upper_included = Some(flag),
"lower_unbounded" => self.lower_unbounded = flag,
"upper_unbounded" => self.upper_unbounded = flag,
_ => {}
}
}
fn real(&self) -> Result<RealInterval, ConstraintReadError> {
let parse = |element: &str, text: Option<&String>| -> Result<Option<f64>, _> {
match text.map(|raw| (raw, raw.parse::<f64>())) {
None => Ok(None),
Some((_, Ok(value))) => Ok(Some(value)),
Some((raw, Err(_))) => Err(ConstraintReadError::Bound {
element: element.to_owned(),
text: raw.clone(),
}),
}
};
Ok(RealInterval {
lower: if self.lower_unbounded {
None
} else {
parse("lower", self.lower.as_ref())?
},
lower_included: self.lower_included.unwrap_or(true),
upper: if self.upper_unbounded {
None
} else {
parse("upper", self.upper.as_ref())?
},
upper_included: self.upper_included.unwrap_or(true),
})
}
fn integer(&self) -> Result<IntInterval, ConstraintReadError> {
let parse = |element: &str, text: Option<&String>| -> Result<Option<i64>, _> {
match text.map(|raw| (raw, raw.parse::<i64>())) {
None => Ok(None),
Some((_, Ok(value))) => Ok(Some(value)),
Some((raw, Err(_))) => Err(ConstraintReadError::Bound {
element: element.to_owned(),
text: raw.clone(),
}),
}
};
Ok(IntInterval {
lower: if self.lower_unbounded {
None
} else {
parse("lower", self.lower.as_ref())?
},
lower_included: self.lower_included.unwrap_or(true),
upper: if self.upper_unbounded {
None
} else {
parse("upper", self.upper.as_ref())?
},
upper_included: self.upper_included.unwrap_or(true),
})
}
}
#[derive(Debug)]
struct Frame {
name: String,
xsi_type: Option<String>,
rm_type: Option<String>,
rm_attribute: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Filling {
Nothing,
QuantityMagnitude,
CountRange,
}
#[derive(Debug, Clone, Default)]
pub struct LeafConstraints {
quantity: BTreeMap<String, RealInterval>,
count: Option<IntInterval>,
}
impl LeafConstraints {
pub fn from_opt(opt_xml: &str) -> Result<Self, ConstraintReadError> {
let mut reader = quick_xml::NsReader::from_str(opt_xml);
let mut stack: Vec<Frame> = Vec::new();
let mut quantity: BTreeMap<String, RealInterval> = BTreeMap::new();
let mut count: Option<IntInterval> = None;
let mut filling = Filling::Nothing;
let mut accumulator = IntervalAccumulator::default();
let mut units: Option<String> = None;
let mut list_open = false;
loop {
let event = reader
.read_event()
.map_err(|e| ConstraintReadError::Malformed(e.to_string()))?;
match event {
quick_xml::events::Event::Eof if stack.is_empty() => break,
quick_xml::events::Event::Eof => {
return Err(ConstraintReadError::Malformed(format!(
"the document ends with {} element(s) still open",
stack.len()
)));
}
quick_xml::events::Event::Start(start) => {
let frame = frame_of(&mut reader, &start)?;
match frame.name.as_str() {
"list" if parent_is(&stack, "C_DV_QUANTITY") => {
list_open = true;
units = None;
accumulator = IntervalAccumulator::default();
filling = Filling::Nothing;
}
"magnitude" if list_open && filling == Filling::Nothing => {
filling = Filling::QuantityMagnitude;
accumulator = IntervalAccumulator::default();
}
"range" if parent_is(&stack, "C_INTEGER") && in_count_magnitude(&stack) => {
filling = Filling::CountRange;
accumulator = IntervalAccumulator::default();
}
_ => {}
}
stack.push(frame);
}
quick_xml::events::Event::Text(text) => {
let decoded = text
.decode()
.map_err(|e| ConstraintReadError::Malformed(e.to_string()))?;
let trimmed = decoded.trim();
if !trimmed.is_empty() {
record_text(&mut stack, &mut accumulator, &mut units, filling, trimmed);
}
}
quick_xml::events::Event::End(_) => {
let closed = stack.pop();
let name = closed.as_ref().map_or("", |frame| frame.name.as_str());
match name {
"magnitude" if filling == Filling::QuantityMagnitude => {
filling = Filling::Nothing;
}
"range" if filling == Filling::CountRange => {
let read = accumulator.integer()?;
count = Some(count.map_or(read, |held| held.intersect(read)));
filling = Filling::Nothing;
accumulator = IntervalAccumulator::default();
}
"list" if list_open => {
if let Some(key) = units.take() {
let read = if accumulator.is_filled() {
accumulator.real()?
} else {
RealInterval::UNBOUNDED
};
let held = quantity
.get(&key)
.copied()
.map_or(read, |held| held.intersect(read));
quantity.insert(key, held);
}
list_open = false;
accumulator = IntervalAccumulator::default();
}
_ => {}
}
}
_ => {}
}
}
Ok(Self { quantity, count })
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.quantity.is_empty() && self.count.is_none()
}
pub(crate) fn apply(&self, body: &mut Value, template_key: &str, arrival: u64) {
if self.is_empty() {
return;
}
let stream = template_key.bytes().fold(
crate::perf_run::fnv1a(JITTER_SEED, &[arrival]),
|seed, byte| crate::perf_run::fnv1a(seed, &[u64::from(byte)]),
);
let mut ordinal: u64 = 0;
self.redraw(body, stream, &mut ordinal);
}
fn redraw(&self, value: &mut Value, stream: u64, ordinal: &mut u64) {
match value {
Value::Object(map) => {
let leaf = map.get("_type").and_then(Value::as_str).map(str::to_owned);
if leaf.as_deref() == Some("DV_QUANTITY") || leaf.as_deref() == Some("DV_COUNT") {
let draw = crate::perf_run::fnv1a(stream, &[*ordinal]);
*ordinal = ordinal.wrapping_add(1);
if leaf.as_deref() == Some("DV_QUANTITY") {
self.redraw_quantity(map, draw);
} else {
self.redraw_count(map, draw);
}
}
for (_, child) in map.iter_mut() {
self.redraw(child, stream, ordinal);
}
}
Value::Array(items) => {
for item in items.iter_mut() {
self.redraw(item, stream, ordinal);
}
}
_ => {}
}
}
fn redraw_quantity(&self, map: &mut serde_json::Map<String, Value>, draw: u64) {
let Some(interval) = map
.get("units")
.and_then(Value::as_str)
.and_then(|units| self.quantity.get(units))
.copied()
else {
return;
};
let Some(decimals) = map.get("magnitude").and_then(decimals_of) else {
return;
};
let Some(redrawn) = redraw_real(interval, decimals, draw).and_then(Number::from_f64) else {
return;
};
map.insert("magnitude".to_owned(), Value::Number(redrawn));
}
fn redraw_count(&self, map: &mut serde_json::Map<String, Value>, draw: u64) {
let Some((lower, upper)) = self.count.and_then(IntInterval::closed) else {
return;
};
if !map.contains_key("magnitude") {
return;
}
let Some(redrawn) = redraw_integer(lower, upper, draw) else {
return;
};
map.insert("magnitude".to_owned(), Value::Number(Number::from(redrawn)));
}
}
fn record_text(
stack: &mut [Frame],
accumulator: &mut IntervalAccumulator,
units: &mut Option<String>,
filling: Filling,
text: &str,
) {
let depth = stack.len();
let name = match stack.last() {
Some(frame) => frame.name.clone(),
None => return,
};
match name.as_str() {
"rm_type_name" | "rm_attribute_name" => {
let Some(parent) = depth.checked_sub(2).and_then(|i| stack.get_mut(i)) else {
return;
};
if name == "rm_type_name" {
parent.rm_type = Some(text.to_owned());
} else {
parent.rm_attribute = Some(text.to_owned());
}
}
"units" => {
let is_quantity_item = depth
.checked_sub(2)
.and_then(|i| stack.get(i))
.is_some_and(|parent| parent.name == "list");
if is_quantity_item {
*units = Some(text.to_owned());
}
}
_ if filling != Filling::Nothing => accumulator.record(&name, text),
_ => {}
}
}
fn frame_of(
reader: &mut quick_xml::NsReader<&[u8]>,
start: &quick_xml::events::BytesStart<'_>,
) -> Result<Frame, ConstraintReadError> {
let name = String::from_utf8_lossy(start.local_name().as_ref()).into_owned();
let mut xsi_type = None;
for attribute in start.attributes() {
let attribute = attribute.map_err(|e| ConstraintReadError::Malformed(e.to_string()))?;
let (namespace, local) = reader.resolver_mut().resolve_attribute(attribute.key);
let is_xsi_type = local.as_ref() == b"type"
&& matches!(
namespace,
quick_xml::name::ResolveResult::Bound(ns) if ns.as_ref() == XSI_NAMESPACE.as_bytes()
);
if is_xsi_type {
let value = String::from_utf8_lossy(attribute.value.as_ref()).into_owned();
xsi_type = value.rsplit(':').next().map(str::to_owned);
}
}
Ok(Frame {
name,
xsi_type,
rm_type: None,
rm_attribute: None,
})
}
fn parent_is(stack: &[Frame], xsi_type: &str) -> bool {
stack
.last()
.is_some_and(|frame| frame.xsi_type.as_deref() == Some(xsi_type))
}
fn in_count_magnitude(stack: &[Frame]) -> bool {
for index in (0..stack.len()).rev() {
let Some(frame) = stack.get(index) else {
return false;
};
let Some(attribute) = frame.rm_attribute.as_deref() else {
continue;
};
let owner = index
.checked_sub(1)
.and_then(|i| stack.get(i))
.and_then(|parent| parent.rm_type.as_deref());
return attribute == "magnitude" && owner == Some("DV_COUNT");
}
false
}
fn decimals_of(value: &Value) -> Option<u32> {
let Value::Number(number) = value else {
return None;
};
let text = number.to_string();
if text.contains(['e', 'E']) {
return None;
}
let fraction = text.split_once('.').map_or("", |(_, fraction)| fraction);
u32::try_from(fraction.len())
.ok()
.map(|d| d.min(MAX_DECIMALS))
}
fn redraw_real(interval: RealInterval, decimals: u32, draw: u64) -> Option<f64> {
let (lower, upper) = (interval.lower?, interval.upper?);
if !lower.is_finite() || !upper.is_finite() || upper <= lower {
return None;
}
let scale = 10_f64.powi(i32::try_from(decimals).ok()?);
let step = 1.0 / scale;
let index = u32::try_from(draw % u64::from(DRAW_STEPS)).ok()?;
let fraction = f64::from(index) / f64::from(DRAW_STEPS);
let mut value = ((lower + fraction * (upper - lower)) * scale).round() / scale;
if !interval.admits_lower(value) {
value = (lower * scale).ceil() / scale;
if !interval.admits_lower(value) {
value += step;
}
}
if !interval.admits_upper(value) {
value = (upper * scale).floor() / scale;
if !interval.admits_upper(value) {
value -= step;
}
}
let inside = interval.admits_lower(value) && interval.admits_upper(value);
(inside && value.is_finite()).then_some(value)
}
fn redraw_integer(lower: i64, upper: i64, draw: u64) -> Option<i64> {
let span = u64::try_from(upper.checked_sub(lower)?)
.ok()?
.checked_add(1)?;
let offset = i64::try_from(draw % span).ok()?;
lower.checked_add(offset)
}
#[cfg(test)]
mod tests {
use super::*;
fn opt(body: &str) -> String {
format!(
"<template xmlns=\"http://schemas.openehr.org/v1\" \
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">{body}</template>"
)
}
fn quantity_item(units: &str, lower: &str, upper: &str, upper_included: bool) -> String {
format!(
"<children xsi:type=\"C_DV_QUANTITY\"><rm_type_name>DV_QUANTITY</rm_type_name>\
<list><magnitude><lower_included>true</lower_included>\
<upper_included>{upper_included}</upper_included>\
<lower_unbounded>false</lower_unbounded><upper_unbounded>false</upper_unbounded>\
<lower>{lower}</lower><upper>{upper}</upper></magnitude>\
<precision><lower>1</lower><upper>1</upper></precision>\
<units>{units}</units></list></children>"
)
}
fn count_range(lower: &str, upper: &str) -> String {
format!(
"<children xsi:type=\"C_COMPLEX_OBJECT\"><rm_type_name>DV_COUNT</rm_type_name>\
<attributes xsi:type=\"C_SINGLE_ATTRIBUTE\">\
<rm_attribute_name>magnitude</rm_attribute_name>\
<children xsi:type=\"C_PRIMITIVE_OBJECT\"><rm_type_name>INTEGER</rm_type_name>\
<item xsi:type=\"C_INTEGER\"><range><lower_included>true</lower_included>\
<upper_included>true</upper_included><lower_unbounded>false</lower_unbounded>\
<upper_unbounded>false</upper_unbounded><lower>{lower}</lower><upper>{upper}</upper>\
</range></item></children></attributes></children>"
)
}
#[test]
fn a_units_range_is_the_intersection_of_every_declaration_for_it() {
let xml = opt(&format!(
"{}{}",
quantity_item("/min", "0", "1000", false),
quantity_item("/min", "0", "200", false)
));
let read = LeafConstraints::from_opt(&xml).unwrap();
let interval = read.quantity.get("/min").copied().unwrap();
assert_eq!(interval.lower, Some(0.0));
assert_eq!(interval.upper, Some(200.0));
assert!(!interval.upper_included);
}
#[test]
fn an_unconstrained_declaration_never_widens_a_units_range() {
let xml = opt(&format!(
"{}<children xsi:type=\"C_DV_QUANTITY\"><list><units>Cel</units></list></children>",
quantity_item("Cel", "0", "100", false)
));
let read = LeafConstraints::from_opt(&xml).unwrap();
let interval = read.quantity.get("Cel").copied().unwrap();
assert_eq!(interval.upper, Some(100.0));
}
#[test]
fn the_count_range_is_the_intersection_of_every_dv_count_declaration() {
let xml = opt(&format!(
"{}{}",
count_range("0", "100"),
count_range("1", "31")
));
let read = LeafConstraints::from_opt(&xml).unwrap();
assert_eq!(read.count.and_then(IntInterval::closed), Some((1, 31)));
}
#[test]
fn an_occurrences_interval_is_never_mistaken_for_a_magnitude() {
let xml = opt(
"<children xsi:type=\"C_DV_QUANTITY\"><occurrences><lower>1</lower>\
<upper>1</upper></occurrences><list><units>Cel</units></list></children>",
);
let read = LeafConstraints::from_opt(&xml).unwrap();
let interval = read.quantity.get("Cel").copied().unwrap();
assert_eq!(interval.lower, None);
assert_eq!(interval.upper, None);
}
#[test]
fn a_redrawn_magnitude_stays_inside_its_declared_range_for_every_draw() {
let interval = RealInterval {
lower: Some(0.0),
lower_included: true,
upper: Some(100.0),
upper_included: false,
};
for draw in 0..5_000_u64 {
let value = redraw_real(interval, 1, draw.wrapping_mul(2_654_435_761)).unwrap();
assert!(
(0.0..100.0).contains(&value),
"draw {draw} produced {value}, outside [0, 100)"
);
}
}
#[test]
fn a_redrawn_count_stays_inside_its_declared_range_for_every_draw() {
for draw in 0..5_000_u64 {
let value = redraw_integer(1, 31, draw.wrapping_mul(2_654_435_761)).unwrap();
assert!((1..=31).contains(&value), "draw {draw} produced {value}");
}
}
#[test]
fn an_empty_or_inverted_range_redraws_nothing() {
let inverted = RealInterval {
lower: Some(10.0),
lower_included: true,
upper: Some(1.0),
upper_included: true,
};
assert!(redraw_real(inverted, 1, 7).is_none());
assert!(redraw_integer(5, 4, 7).is_none());
}
#[test]
fn a_leaf_the_template_says_nothing_about_keeps_its_committed_value() {
let read =
LeafConstraints::from_opt(&opt(&quantity_item("Cel", "0", "100", false))).unwrap();
let mut body = serde_json::json!({
"_type": "ELEMENT",
"value": { "_type": "DV_QUANTITY", "magnitude": 7.5, "units": "mm[Hg]" },
"other": { "_type": "DV_COUNT", "magnitude": 3 }
});
let before = body.clone();
read.apply(&mut body, "cnf.ckm.vital_signs", 4);
assert_eq!(body, before);
}
#[test]
fn the_same_arrival_of_the_same_template_redraws_the_same_bytes() {
let read = LeafConstraints::from_opt(&opt(&format!(
"{}{}",
quantity_item("Cel", "0", "100", false),
count_range("1", "31")
)))
.unwrap();
let skeleton = serde_json::json!({
"_type": "COMPOSITION",
"content": [
{ "_type": "ELEMENT",
"value": { "_type": "DV_QUANTITY", "magnitude": 49.5, "units": "Cel" } },
{ "_type": "ELEMENT",
"value": { "_type": "DV_COUNT", "magnitude": 3 } }
]
});
let render = |arrival: u64| {
let mut body = skeleton.clone();
read.apply(&mut body, "cnf.ckm.vital_signs", arrival);
serde_json::to_vec(&body).unwrap()
};
assert_eq!(render(11), render(11));
assert_ne!(render(11), render(12));
let first: Value = serde_json::from_slice(&render(11)).unwrap();
assert_ne!(first["content"][0]["value"]["magnitude"], 49.5);
assert_ne!(first["content"][1]["value"]["magnitude"], 3);
}
#[test]
fn a_malformed_template_is_a_typed_error_not_a_silent_empty_pack() {
let error = LeafConstraints::from_opt("<template><unclosed>").unwrap_err();
assert!(matches!(error, ConstraintReadError::Malformed(_)));
}
#[test]
fn a_non_numeric_bound_is_a_typed_error() {
let xml = opt(&quantity_item("Cel", "zero", "100", true));
let error = LeafConstraints::from_opt(&xml).unwrap_err();
assert!(matches!(error, ConstraintReadError::Bound { .. }));
}
}