use std::collections::BTreeMap;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct MigProfile {
pub schema_version: u32,
pub message_type: String,
pub release: String,
#[serde(default)]
pub track: Option<String>,
pub valid_from: String,
#[serde(default)]
pub valid_until: Option<String>,
#[serde(default)]
pub publikationsdatum: Option<String>,
pub ahb_version: String,
#[serde(default)]
pub pid_source: Option<String>,
#[serde(default)]
pub pid_exempt: bool,
pub source: Source,
pub structure: Vec<Node>,
#[serde(default)]
pub envelope: Vec<SegmentNode>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Source {
pub file: String,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub sha256: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum Node {
Group(GroupNode),
Segment(SegmentNode),
}
#[derive(Debug, Clone, Deserialize)]
pub struct GroupNode {
pub group: String,
#[serde(default)]
pub zaehler: String,
pub status: String,
pub max: u32,
#[serde(default)]
pub name: String,
pub children: Vec<Node>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SegmentNode {
pub nr: String,
pub tag: String,
#[serde(default)]
pub zaehler: String,
pub status: String,
pub max: u32,
#[serde(default)]
pub name: String,
#[serde(default)]
pub elements: Vec<Element>,
#[serde(default)]
pub example: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Element {
pub id: String,
#[serde(default)]
pub name: String,
pub status: String,
#[serde(default)]
pub format: Option<String>,
#[serde(default)]
pub note: String,
#[serde(default)]
pub codes: Vec<Code>,
#[serde(default)]
pub components: Vec<Element>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Code {
pub code: String,
#[serde(default)]
pub name: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AhbProfile {
pub schema_version: u32,
pub message_type: String,
pub release: String,
pub ahb_version: String,
pub source: Source,
#[serde(default)]
pub conditions: BTreeMap<String, String>,
#[serde(default)]
pub packages: BTreeMap<String, String>,
pub anwendungsfaelle: Vec<Anwendungsfall>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Anwendungsfall {
#[serde(default)]
pub pid: Option<u32>,
pub name: String,
#[serde(default)]
pub communication: Option<String>,
#[serde(default)]
pub chapter: Option<String>,
pub rows: Vec<Row>,
#[serde(default)]
pub elements: Vec<ElementRule>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Row {
#[serde(default)]
pub nr: Option<String>,
#[serde(default)]
pub group: Option<String>,
#[serde(default)]
pub before: Option<String>,
pub status: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ElementRule {
pub nr: String,
pub de: String,
#[serde(default)]
pub occurrence: u8,
pub operands: Vec<Operand>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Operand {
#[serde(default)]
pub code: Option<String>,
pub operand: String,
}
impl MigProfile {
pub fn segments(&self) -> Vec<&SegmentNode> {
fn walk<'a>(nodes: &'a [Node], out: &mut Vec<&'a SegmentNode>) {
for node in nodes {
match node {
Node::Group(g) => walk(&g.children, out),
Node::Segment(s) => out.push(s),
}
}
}
let mut out = Vec::new();
walk(&self.structure, &mut out);
out
}
#[must_use]
pub fn segment(&self, nr: &str) -> Option<&SegmentNode> {
self.segments().into_iter().find(|s| s.nr == nr)
}
}
impl SegmentNode {
#[must_use]
pub fn locate(&self, de: &str, occurrence: u8) -> Option<(usize, usize, &Element)> {
let mut seen = 0u8;
for (ei, el) in self.elements.iter().enumerate() {
if el.id == de {
if seen == occurrence {
return Some((ei, 0, el));
}
seen += 1;
}
for (ci, comp) in el.components.iter().enumerate() {
if comp.id == de {
if seen == occurrence {
return Some((ei, ci, comp));
}
seen += 1;
}
}
}
None
}
pub fn leaves(&self) -> impl Iterator<Item = (usize, usize, &Element)> {
self.elements.iter().enumerate().flat_map(|(ei, el)| {
if el.components.is_empty() {
vec![(ei, 0, el)]
} else {
el.components
.iter()
.enumerate()
.map(|(ci, c)| (ei, ci, c))
.collect::<Vec<_>>()
}
})
}
}
impl Element {
#[must_use]
pub fn is_code_list(&self) -> bool {
!self.codes.is_empty()
&& self.codes.iter().all(|c| {
c.code.chars().count() <= 8
&& c.code
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '.' || ch == '_')
&& !c.code.eq_ignore_ascii_case("xyz")
&& !c.code.eq_ignore_ascii_case("xxx")
})
}
}
impl Anwendungsfall {
#[must_use]
pub fn segment_status(&self, nr: &str) -> Option<&[String]> {
self.rows
.iter()
.find(|r| r.nr.as_deref() == Some(nr))
.map(|r| r.status.as_slice())
}
#[must_use]
pub fn group_status(&self, group: &str, trigger_nr: &str) -> Option<&[String]> {
self.rows
.iter()
.find(|r| r.group.as_deref() == Some(group) && r.before.as_deref() == Some(trigger_nr))
.map(|r| r.status.as_slice())
}
pub fn element_rules(&self, nr: &str) -> impl Iterator<Item = &ElementRule> {
self.elements.iter().filter(move |e| e.nr == nr)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nodes_deserialise_untagged() {
let json = r#"[
{"nr":"00003","tag":"UNH","status":"M","max":1,"elements":[
{"id":"0062","status":"M","format":"an..14"},
{"id":"S009","status":"M","components":[{"id":"0065","status":"M","format":"an..6","codes":[{"code":"UTILMD","name":""}]}]}
]},
{"group":"SG4","status":"D","max":99999,"children":[{"nr":"00018","tag":"IDE","status":"M","max":1}]}
]"#;
let nodes: Vec<Node> = serde_json::from_str(json).unwrap();
assert!(matches!(&nodes[0], Node::Segment(s) if s.tag == "UNH"));
let Node::Segment(unh) = &nodes[0] else {
panic!()
};
assert_eq!(unh.locate("0065", 0).map(|(e, c, _)| (e, c)), Some((1, 0)));
assert!(matches!(&nodes[1], Node::Group(g) if g.group == "SG4" && g.children.len() == 1));
}
}