use crate::directory_validator::{ComponentRef, ElementRef, SegmentDefinition, Status};
use Status::{Conditional as C, Mandatory as M};
pub const S001: &[ComponentRef] = &[
ComponentRef::new(1, "0001", M), ComponentRef::new(2, "0002", M), ComponentRef::new(3, "0080", C), ComponentRef::new(4, "0133", C), ];
pub const S002: &[ComponentRef] = &[
ComponentRef::new(1, "0004", M), ComponentRef::new(2, "0007", C), ComponentRef::new(3, "0008", C), ComponentRef::new(4, "0042", C), ];
pub const S003: &[ComponentRef] = &[
ComponentRef::new(1, "0010", M), ComponentRef::new(2, "0007", C), ComponentRef::new(3, "0014", C), ComponentRef::new(4, "0046", C), ];
pub const S004: &[ComponentRef] = &[
ComponentRef::new(1, "0017", M), ComponentRef::new(2, "0019", M), ];
pub const S005: &[ComponentRef] = &[
ComponentRef::new(1, "0022", M), ComponentRef::new(2, "0025", C), ];
pub const S006: &[ComponentRef] = &[
ComponentRef::new(1, "0040", M), ComponentRef::new(2, "0007", C), ];
pub const S007: &[ComponentRef] = &[
ComponentRef::new(1, "0044", M), ComponentRef::new(2, "0007", C), ];
pub const S008: &[ComponentRef] = &[
ComponentRef::new(1, "0052", M), ComponentRef::new(2, "0054", M), ComponentRef::new(3, "0057", C), ];
pub const S009: &[ComponentRef] = &[
ComponentRef::new(1, "0065", M), ComponentRef::new(2, "0052", M), ComponentRef::new(3, "0054", M), ComponentRef::new(4, "0051", M), ComponentRef::new(5, "0057", C), ComponentRef::new(6, "0110", C), ComponentRef::new(7, "0113", C), ];
pub const S010: &[ComponentRef] = &[
ComponentRef::new(1, "0070", M), ComponentRef::new(2, "0073", C), ];
pub const S016: &[ComponentRef] = &[
ComponentRef::new(1, "0115", M), ComponentRef::new(2, "0116", C), ComponentRef::new(3, "0118", C), ComponentRef::new(4, "0051", C), ];
pub const S017: &[ComponentRef] = &[
ComponentRef::new(1, "0121", M), ComponentRef::new(2, "0122", C), ComponentRef::new(3, "0124", C), ComponentRef::new(4, "0051", C), ];
pub const S018: &[ComponentRef] = &[
ComponentRef::new(1, "0127", M), ComponentRef::new(2, "0128", C), ComponentRef::new(3, "0130", C), ComponentRef::new(4, "0051", C), ];
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), ElementRef::composite(6, "S005", C, 1, S005),
ElementRef::new(7, "0026", C, 1), ElementRef::new(8, "0029", C, 1), ElementRef::new(9, "0031", C, 1), ElementRef::new(10, "0032", C, 1), ElementRef::new(11, "0035", C, 1), ];
pub const UNB: SegmentDefinition =
SegmentDefinition::new("UNB", "Interchange header", UNB_ELEMENTS);
const UNZ_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0036", M, 1), ElementRef::new(2, "0020", M, 1), ];
pub const UNZ: SegmentDefinition =
SegmentDefinition::new("UNZ", "Interchange trailer", UNZ_ELEMENTS);
const UNG_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0038", M, 1), ElementRef::composite(2, "S006", M, 1, S006),
ElementRef::composite(3, "S007", M, 1, S007),
ElementRef::composite(4, "S004", M, 1, S004),
ElementRef::new(5, "0048", M, 1), ElementRef::new(6, "0051", M, 1), ElementRef::composite(7, "S008", M, 1, S008),
];
pub const UNG: SegmentDefinition =
SegmentDefinition::new("UNG", "Functional group header", UNG_ELEMENTS);
const UNE_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0060", M, 1), ElementRef::new(2, "0048", M, 1), ];
pub const UNE: SegmentDefinition =
SegmentDefinition::new("UNE", "Functional group trailer", UNE_ELEMENTS);
const UNH_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0062", M, 1), ElementRef::composite(2, "S009", M, 1, S009),
ElementRef::new(3, "0068", C, 1), 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), ElementRef::new(2, "0062", M, 1), ];
pub const UNT: SegmentDefinition = SegmentDefinition::new("UNT", "Message trailer", UNT_ELEMENTS);
const UNS_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "0081", M, 1), ];
pub const UNS: SegmentDefinition = SegmentDefinition::new("UNS", "Section control", UNS_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,
_ => return None,
})
}
pub const ALL: &[&SegmentDefinition] = &[&UNB, &UNG, &UNH, &UNT, &UNE, &UNZ, &UNS];
#[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(), 7);
}
#[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());
}
}