use crate::directory_validator::{ComponentRef, ElementRef, Repr, SegmentDefinition, Status};
use Status::{Conditional as C, Mandatory as M};
pub const S001: &[ComponentRef] = &[
ComponentRef::new(1, "0001", M).with_repr(Repr::a(4)), ComponentRef::new(2, "0002", M).with_repr(Repr::an(1)), ComponentRef::new(3, "0080", C).with_repr(Repr::an_up_to(6)), ComponentRef::new(4, "0133", C).with_repr(Repr::an_up_to(3)), ];
pub const S002: &[ComponentRef] = &[
ComponentRef::new(1, "0004", M).with_repr(Repr::an_up_to(35)), ComponentRef::new(2, "0007", C).with_repr(Repr::an_up_to(4)), ComponentRef::new(3, "0008", C).with_repr(Repr::an_up_to(35)), ComponentRef::new(4, "0042", C).with_repr(Repr::an_up_to(35)), ];
pub const S003: &[ComponentRef] = &[
ComponentRef::new(1, "0010", M).with_repr(Repr::an_up_to(35)), ComponentRef::new(2, "0007", C).with_repr(Repr::an_up_to(4)), ComponentRef::new(3, "0014", C).with_repr(Repr::an_up_to(35)), ComponentRef::new(4, "0046", C).with_repr(Repr::an_up_to(35)), ];
pub const S004: &[ComponentRef] = &[
ComponentRef::new(1, "0017", M).with_repr_by_syntax_version(Repr::n(6), Repr::n(8)), ComponentRef::new(2, "0019", M).with_repr(Repr::n_up_to(4)), ];
pub const S005: &[ComponentRef] = &[
ComponentRef::new(1, "0022", M).with_repr(Repr::an_up_to(14)), ComponentRef::new(2, "0025", C).with_repr(Repr::an(2)), ];
pub const S006: &[ComponentRef] = &[
ComponentRef::new(1, "0040", M).with_repr(Repr::an_up_to(35)), ComponentRef::new(2, "0007", C).with_repr(Repr::an_up_to(4)), ];
pub const S007: &[ComponentRef] = &[
ComponentRef::new(1, "0044", M).with_repr(Repr::an_up_to(35)), ComponentRef::new(2, "0007", C).with_repr(Repr::an_up_to(4)), ];
pub const S008: &[ComponentRef] = &[
ComponentRef::new(1, "0052", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(2, "0054", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(3, "0057", C).with_repr(Repr::an_up_to(6)), ];
pub const S009: &[ComponentRef] = &[
ComponentRef::new(1, "0065", M).with_repr(Repr::an_up_to(6)), ComponentRef::new(2, "0052", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(3, "0054", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(4, "0051", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(5, "0057", C).with_repr(Repr::an_up_to(6)), ComponentRef::new(6, "0110", C).with_repr(Repr::an_up_to(6)), ComponentRef::new(7, "0113", C).with_repr(Repr::an_up_to(6)), ];
pub const S010: &[ComponentRef] = &[
ComponentRef::new(1, "0070", M).with_repr(Repr::n_up_to(2)), ComponentRef::new(2, "0073", C).with_repr(Repr::a(1)), ];
pub const S016: &[ComponentRef] = &[
ComponentRef::new(1, "0115", M).with_repr(Repr::an_up_to(14)), ComponentRef::new(2, "0116", C).with_repr(Repr::an_up_to(3)), ComponentRef::new(3, "0118", C).with_repr(Repr::an_up_to(3)), ComponentRef::new(4, "0051", C).with_repr(Repr::an_up_to(3)), ];
pub const S017: &[ComponentRef] = &[
ComponentRef::new(1, "0121", M).with_repr(Repr::an_up_to(14)), ComponentRef::new(2, "0122", C).with_repr(Repr::an_up_to(3)), ComponentRef::new(3, "0124", C).with_repr(Repr::an_up_to(3)), ComponentRef::new(4, "0051", C).with_repr(Repr::an_up_to(3)), ];
pub const S018: &[ComponentRef] = &[
ComponentRef::new(1, "0127", M).with_repr(Repr::an_up_to(14)), ComponentRef::new(2, "0128", C).with_repr(Repr::an_up_to(3)), ComponentRef::new(3, "0130", C).with_repr(Repr::an_up_to(3)), ComponentRef::new(4, "0051", C).with_repr(Repr::an_up_to(3)), ];
pub const S020: &[ComponentRef] = &[
ComponentRef::new(1, "0813", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(2, "0802", M).with_repr(Repr::an_up_to(35)), ];
pub const S021: &[ComponentRef] = &[
ComponentRef::new(1, "0805", M).with_repr(Repr::an_up_to(3)), ComponentRef::new(2, "0809", C).with_repr(Repr::an_up_to(256)), ComponentRef::new(3, "0808", C).with_repr(Repr::an_up_to(256)), ComponentRef::new(4, "0051", C).with_repr(Repr::an_up_to(3)), ];
pub const S022: &[ComponentRef] = &[
ComponentRef::new(1, "0810", M).with_repr(Repr::n_up_to(18)), ComponentRef::new(2, "0814", C).with_repr(Repr::n_up_to(3)), ComponentRef::new(3, "0070", C).with_repr(Repr::n_up_to(2)), ComponentRef::new(4, "0073", C).with_repr(Repr::a(1)), ];
pub const S011: &[ComponentRef] = &[
ComponentRef::new(1, "0098", M).with_repr(Repr::n_up_to(3)), ComponentRef::new(2, "0104", C).with_repr(Repr::n_up_to(3)), ComponentRef::new(3, "0136", C).with_repr(Repr::n_up_to(6)), ];
const UNB_ELEMENTS: &[ElementRef] = &[
ElementRef::composite(1, "S001", M, 1, S001),
ElementRef::composite(2, "S002", M, 1, S002),
ElementRef::composite(3, "S003", M, 1, S003),
ElementRef::composite(4, "S004", M, 1, S004),
ElementRef::new(5, "0020", M, 1).with_repr(Repr::an_up_to(14)), ElementRef::composite(6, "S005", C, 1, S005),
ElementRef::new(7, "0026", C, 1).with_repr(Repr::an_up_to(14)), ElementRef::new(8, "0029", C, 1).with_repr(Repr::a(1)), ElementRef::new(9, "0031", C, 1).with_repr(Repr::n(1)), ElementRef::new(10, "0032", C, 1).with_repr(Repr::an_up_to(35)), ElementRef::new(11, "0035", C, 1).with_repr(Repr::n(1)), ];
pub const UNB: SegmentDefinition =
SegmentDefinition::new("UNB", "Interchange header", UNB_ELEMENTS);
const UNZ_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0036", M, 1).with_repr(Repr::n_up_to(6)), ElementRef::new(2, "0020", M, 1).with_repr(Repr::an_up_to(14)), ];
pub const UNZ: SegmentDefinition =
SegmentDefinition::new("UNZ", "Interchange trailer", UNZ_ELEMENTS);
const UNG_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0038", C, 1).with_repr(Repr::an_up_to(6)), ElementRef::composite(2, "S006", C, 1, S006),
ElementRef::composite(3, "S007", C, 1, S007),
ElementRef::composite(4, "S004", C, 1, S004),
ElementRef::new(5, "0048", M, 1).with_repr(Repr::an_up_to(14)), ElementRef::new(6, "0051", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::composite(7, "S008", C, 1, S008),
ElementRef::new(8, "0058", C, 1).with_repr(Repr::an_up_to(14)), ];
pub const UNG: SegmentDefinition = SegmentDefinition::new("UNG", "Group header", UNG_ELEMENTS);
const UNE_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0060", M, 1).with_repr(Repr::n_up_to(6)), ElementRef::new(2, "0048", M, 1).with_repr(Repr::an_up_to(14)), ];
pub const UNE: SegmentDefinition = SegmentDefinition::new("UNE", "Group trailer", UNE_ELEMENTS);
const UNH_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0062", M, 1).with_repr(Repr::an_up_to(14)), ElementRef::composite(2, "S009", M, 1, S009),
ElementRef::new(3, "0068", C, 1).with_repr(Repr::an_up_to(35)), ElementRef::composite(4, "S010", C, 1, S010),
ElementRef::composite(5, "S016", C, 1, S016),
ElementRef::composite(6, "S017", C, 1, S017),
ElementRef::composite(7, "S018", C, 1, S018),
];
pub const UNH: SegmentDefinition = SegmentDefinition::new("UNH", "Message header", UNH_ELEMENTS);
const UNT_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0074", M, 1).with_repr(Repr::n_up_to(10)), ElementRef::new(2, "0062", M, 1).with_repr(Repr::an_up_to(14)), ];
pub const UNT: SegmentDefinition = SegmentDefinition::new("UNT", "Message trailer", UNT_ELEMENTS);
const UNS_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0081", M, 1).with_repr(Repr::a(1)), ];
pub const UNS: SegmentDefinition = SegmentDefinition::new("UNS", "Section control", UNS_ELEMENTS);
const UNO_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0800", M, 1).with_repr(Repr::an_up_to(35)), ElementRef::composite(2, "S020", M, 99, S020),
ElementRef::composite(3, "S021", M, 99, S021),
ElementRef::composite(4, "S022", M, 1, S022),
];
pub const UNO: SegmentDefinition = SegmentDefinition::new("UNO", "Object header", UNO_ELEMENTS);
const UNP_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0810", M, 1).with_repr(Repr::n_up_to(18)), ElementRef::new(2, "0800", M, 1).with_repr(Repr::an_up_to(35)), ];
pub const UNP: SegmentDefinition = SegmentDefinition::new("UNP", "Object trailer", UNP_ELEMENTS);
const UGH_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0087", M, 1).with_repr(Repr::an_up_to(4)), ];
pub const UGH: SegmentDefinition =
SegmentDefinition::new("UGH", "Anti-collision segment group header", UGH_ELEMENTS);
const UGT_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0087", M, 1).with_repr(Repr::an_up_to(4)), ];
pub const UGT: SegmentDefinition =
SegmentDefinition::new("UGT", "Anti-collision segment group trailer", UGT_ELEMENTS);
const UCI_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0020", M, 1).with_repr(Repr::an_up_to(14)), ElementRef::composite(2, "S002", M, 1, S002),
ElementRef::composite(3, "S003", M, 1, S003),
ElementRef::new(4, "0083", M, 1).with_repr(Repr::an_up_to(3)), ElementRef::new(5, "0085", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::new(6, "0135", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::composite(7, "S011", C, 1, S011),
ElementRef::new(8, "0534", C, 1).with_repr(Repr::an_up_to(14)), ElementRef::new(9, "0138", C, 1).with_repr(Repr::n_up_to(6)), ];
pub const UCI: SegmentDefinition =
SegmentDefinition::new("UCI", "Interchange response", UCI_ELEMENTS);
const UCF_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0048", M, 1).with_repr(Repr::an_up_to(14)), ElementRef::composite(2, "S006", C, 1, S006),
ElementRef::composite(3, "S007", C, 1, S007),
ElementRef::new(4, "0083", M, 1).with_repr(Repr::an_up_to(3)), ElementRef::new(5, "0085", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::new(6, "0135", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::composite(7, "S011", C, 1, S011),
ElementRef::new(8, "0534", C, 1).with_repr(Repr::an_up_to(14)), ElementRef::new(9, "0138", C, 1).with_repr(Repr::n_up_to(6)), ];
pub const UCF: SegmentDefinition = SegmentDefinition::new("UCF", "Group response", UCF_ELEMENTS);
const UCM_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0062", C, 1).with_repr(Repr::an_up_to(14)), ElementRef::composite(2, "S009", C, 1, S009),
ElementRef::new(3, "0083", M, 1).with_repr(Repr::an_up_to(3)), ElementRef::new(4, "0085", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::new(5, "0135", C, 1).with_repr(Repr::an_up_to(3)), ElementRef::composite(6, "S011", C, 1, S011),
ElementRef::new(7, "0800", C, 1).with_repr(Repr::an_up_to(35)), ElementRef::composite(8, "S020", C, 1, S020),
ElementRef::new(9, "0534", C, 1).with_repr(Repr::an_up_to(14)), ElementRef::new(10, "0138", C, 1).with_repr(Repr::n_up_to(6)), ];
pub const UCM: SegmentDefinition =
SegmentDefinition::new("UCM", "Message/package response", UCM_ELEMENTS);
const UCS_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0096", M, 1).with_repr(Repr::n_up_to(6)), ElementRef::new(2, "0085", C, 1).with_repr(Repr::an_up_to(3)), ];
pub const UCS: SegmentDefinition =
SegmentDefinition::new("UCS", "Segment error indication", UCS_ELEMENTS);
const UCD_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0085", M, 1).with_repr(Repr::an_up_to(3)), ElementRef::composite(2, "S011", M, 1, S011),
];
pub const UCD: SegmentDefinition =
SegmentDefinition::new("UCD", "Data element error indication", UCD_ELEMENTS);
#[must_use]
pub fn lookup(tag: &str) -> Option<&'static SegmentDefinition> {
Some(match tag {
"UNB" => &UNB,
"UNZ" => &UNZ,
"UNG" => &UNG,
"UNE" => &UNE,
"UNH" => &UNH,
"UNT" => &UNT,
"UNS" => &UNS,
"UNO" => &UNO,
"UNP" => &UNP,
"UGH" => &UGH,
"UGT" => &UGT,
"UCI" => &UCI,
"UCF" => &UCF,
"UCM" => &UCM,
"UCS" => &UCS,
"UCD" => &UCD,
_ => return None,
})
}
pub const ENVELOPE: &[&SegmentDefinition] = &[
&UNB, &UNG, &UNH, &UNT, &UNE, &UNZ, &UNS, &UNO, &UNP, &UGH, &UGT,
];
pub const CONTRL: &[&SegmentDefinition] = &[&UCI, &UCF, &UCM, &UCS, &UCD];
pub const ALL: &[&SegmentDefinition] = &[
&UNB, &UNG, &UNH, &UNT, &UNE, &UNZ, &UNS, &UNO, &UNP, &UGH, &UGT, &UCI, &UCF, &UCM, &UCS, &UCD,
];
#[cfg(test)]
mod tests {
use super::*;
use crate::directory_validator::SegmentLayout;
#[test]
fn every_definition_numbers_its_positions_consecutively_from_one() {
for definition in ALL {
for (index, element) in definition.elements.iter().enumerate() {
assert_eq!(
element.position() as usize,
index + 1,
"{}: element positions must be consecutive and one-based",
definition.tag,
);
for (component_index, component) in element.components().iter().enumerate() {
assert_eq!(
component.position() as usize,
component_index + 1,
"{} {}: component positions must be consecutive and one-based",
definition.tag,
element.data_element(),
);
}
}
}
}
#[test]
fn the_lookup_and_the_list_agree() {
for definition in ALL {
assert_eq!(
lookup(definition.tag).map(|d| d.tag),
Some(definition.tag),
"{} is in ALL but not in lookup()",
definition.tag,
);
}
assert_eq!(ALL.len(), ENVELOPE.len() + CONTRL.len());
assert_eq!(ALL.len(), 16);
}
#[test]
fn ung_matches_annex_c_statuses() {
use crate::directory_validator::Status;
let statuses: Vec<_> = UNG
.elements
.iter()
.map(|e| (e.data_element(), e.status()))
.collect();
assert_eq!(
statuses,
vec![
("0038", Status::Conditional),
("S006", Status::Conditional),
("S007", Status::Conditional),
("S004", Status::Conditional),
("0048", Status::Mandatory),
("0051", Status::Conditional),
("S008", Status::Conditional),
("0058", Status::Conditional),
],
);
}
#[test]
fn package_and_anti_collision_segments_resolve_their_codes() {
assert_eq!(UNO.resolve_code("0800").unwrap().element, 0);
let length = UNO.resolve_code("0810").unwrap();
assert_eq!((length.element, length.component), (3, Some(0)));
assert_eq!(UNP.resolve_code("0800").unwrap().element, 1);
assert_eq!(UGH.resolve_code("0087").unwrap().element, 0);
assert_eq!(UGT.resolve_code("0087").unwrap().element, 0);
}
#[test]
fn envelope_positions_match_the_ones_the_envelope_parser_assumes() {
let cases: [(&SegmentDefinition, &str, usize, Option<usize>); 9] = [
(&UNB, "0001", 0, Some(0)), (&UNB, "0004", 1, Some(0)), (&UNB, "0010", 2, Some(0)), (&UNB, "0017", 3, Some(0)), (&UNB, "0020", 4, None), (&UNB, "0035", 10, None), (&UNZ, "0036", 0, None), (&UNH, "0062", 0, None), (&UNH, "0065", 1, Some(0)), ];
for (definition, code, element, component) in cases {
let path = definition
.resolve_code(code)
.unwrap_or_else(|e| panic!("{} {code}: {e}", definition.tag));
assert_eq!(
(path.element, path.component),
(element, component),
"{} {code}",
definition.tag,
);
}
}
#[test]
fn codes_that_genuinely_repeat_are_reported_as_ambiguous() {
assert!(
UNB.resolve_code("0007").is_err(),
"UNB 0007 (S002 and S003)"
);
assert!(
UNH.resolve_code("0051").is_err(),
"UNH 0051 (S009/S016/S017/S018)"
);
assert!(UNB.resolve_code("0004").is_ok());
assert!(UNB.resolve_code("0010").is_ok());
}
#[test]
fn a_real_interchange_resolves_through_the_shipped_layouts() {
let input = b"UNB+UNOC:3+SENDER:14+RECEIVER:14+260101:0900+IC4711++ORDERS'\
UNH+MSG1+ORDERS:D:96A:UN:EAN008'\
UNT+2+MSG1'UNZ+1+IC4711'";
let segments: Vec<_> = crate::from_bytes(input)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(
segments[0].value_by_code(&UNB, "0020").unwrap(),
Some("IC4711")
);
assert_eq!(
segments[0].value_by_code(&UNB, "0004").unwrap(),
Some("SENDER")
);
assert_eq!(
segments[0].value_by_code(&UNB, "0010").unwrap(),
Some("RECEIVER")
);
assert_eq!(
segments[0].value_by_code(&UNB, "0019").unwrap(),
Some("0900")
);
assert_eq!(
segments[0].value_by_code(&UNB, "0026").unwrap(),
Some("ORDERS")
);
assert_eq!(
segments[1].value_by_code(&UNH, "0062").unwrap(),
Some("MSG1")
);
assert_eq!(
segments[1].value_by_code(&UNH, "0057").unwrap(),
Some("EAN008")
);
assert_eq!(segments[2].value_by_code(&UNT, "0074").unwrap(), Some("2"));
assert_eq!(segments[3].value_by_code(&UNZ, "0036").unwrap(), Some("1"));
assert!(segments[0].value_by_code(&UNH, "0062").is_err());
}
#[test]
fn the_shipped_layouts_drive_the_directory_validator() {
use crate::{ValidationContext, ValidationLayer};
let input = b"UNB+UNOC:3+S+R+260101:0900+IC1'UNZ+1+IC1'";
let segments: Vec<_> = crate::from_bytes(input)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let validator = crate::DirectoryValidator::new(
"iso-9735-service",
lookup,
|_, _| true,
|_, _| None,
|_, _| None,
None,
);
let report = ValidationContext::builder()
.with_validator(ValidationLayer::Structure, validator)
.build()
.validate_lenient(&segments);
assert!(!report.has_errors(), "{:#?}", report.errors());
}
}