#![cfg(feature = "derive")]
use edifact_rs::{
ComponentRef, EdifactDeserialize, EdifactError, EdifactSerialize, ElementRef,
OwnedComponentRef, OwnedElementRef, OwnedSegmentDef, SegmentDefinition, SegmentLayout, Status,
};
const C082_COMPONENTS: &[ComponentRef] = &[
ComponentRef::new(1, "3039", Status::Mandatory),
ComponentRef::new(2, "1131", Status::Conditional),
ComponentRef::new(3, "3055", Status::Conditional),
];
const NAD_ELEMENTS: &[ElementRef] = &[
ElementRef::new(1, "3035", Status::Mandatory, 1),
ElementRef::composite(2, "C082", Status::Conditional, 1, C082_COMPONENTS),
];
pub const NAD: SegmentDefinition = SegmentDefinition::new("NAD", "Name and address", NAD_ELEMENTS);
const C507_COMPONENTS: &[ComponentRef] = &[
ComponentRef::new(1, "2005", Status::Mandatory),
ComponentRef::new(2, "2380", Status::Conditional),
ComponentRef::new(3, "2379", Status::Conditional),
];
const DTM_ELEMENTS: &[ElementRef] = &[ElementRef::composite(
1,
"C507",
Status::Mandatory,
1,
C507_COMPONENTS,
)];
pub const DTM: SegmentDefinition = SegmentDefinition::new("DTM", "Date/time/period", DTM_ELEMENTS);
const _: () = assert!(NAD.element_slot("3035") == 0);
const _: () = assert!(NAD.element_slot("3055") == 1);
const _: () = assert!(NAD.component_slot("3055") == 2);
const _: () = assert!(NAD.component_slot("C082") == 0);
const _: () = assert!(NAD.code_positions("9999") == 0);
#[derive(Debug, PartialEq, EdifactDeserialize, EdifactSerialize)]
#[edifact(segment = "NAD", qualifier = "MS", layout = NAD)]
struct SenderParty {
#[edifact(element = "3039")]
party_id: String,
#[edifact(element = "3055")]
agency: Option<String>,
}
#[derive(Debug, PartialEq, EdifactDeserialize, EdifactSerialize)]
#[edifact(segment = "DTM", layout = DTM)]
struct DocumentDate {
#[edifact(element = "2005")]
qualifier: String,
#[edifact(element = "2380")]
value: String,
#[edifact(element = "2379")]
format: Option<String>,
}
#[derive(Debug, PartialEq, EdifactDeserialize)]
#[edifact(segment = "NAD", layout = NAD)]
struct RequiredShapes {
#[edifact(element = "3035", required)]
qualifier: Option<String>,
#[edifact(element = "3055", required)]
agency: Option<String>,
}
fn parse(input: &[u8]) -> Vec<edifact_rs::Segment<'_>> {
edifact_rs::from_bytes(input)
.collect::<Result<Vec<_>, _>>()
.expect("fixture must parse")
}
#[test]
fn value_by_code_reads_elements_and_composite_components() {
let segments = parse(b"NAD+MS+9900112233445::293'");
let nad = &segments[0];
assert_eq!(nad.value_by_code(&NAD, "3035").unwrap(), Some("MS"));
assert_eq!(
nad.value_by_code(&NAD, "3039").unwrap(),
Some("9900112233445")
);
assert_eq!(nad.value_by_code(&NAD, "1131").unwrap(), Some(""));
assert_eq!(nad.value_by_code(&NAD, "3055").unwrap(), Some("293"));
assert_eq!(
nad.value_by_code(&NAD, "C082").unwrap(),
Some("9900112233445")
);
}
#[test]
fn unknown_data_element_is_an_error_not_a_wrong_read() {
let segments = parse(b"NAD+MS+9900112233445::293'");
let nad = &segments[0];
let err = nad.value_by_code(&NAD, "2380").unwrap_err();
assert!(
matches!(&err, EdifactError::UnknownDataElement { tag, data_element }
if tag == "NAD" && data_element == "2380"),
"expected UnknownDataElement, got {err:?}"
);
assert_eq!(err.stable_code(), "E033");
}
#[test]
fn applying_a_layout_to_the_wrong_segment_is_rejected() {
let segments = parse(b"NAD+MS+9900112233445::293'");
let err = segments[0].value_by_code(&DTM, "2005").unwrap_err();
assert!(
matches!(&err, EdifactError::SegmentLayoutMismatch { expected, actual }
if expected == "DTM" && actual == "NAD"),
"expected SegmentLayoutMismatch, got {err:?}"
);
}
#[test]
fn ambiguous_data_element_is_rejected() {
const REPEATED: &[ElementRef] = &[
ElementRef::new(1, "1153", Status::Mandatory, 1),
ElementRef::new(2, "1153", Status::Conditional, 1),
];
const RFF: SegmentDefinition = SegmentDefinition::new("RFF", "Reference", REPEATED);
let err = RFF.resolve_code("1153").unwrap_err();
assert!(
matches!(&err, EdifactError::AmbiguousDataElement { data_element, .. } if data_element == "1153"),
"expected AmbiguousDataElement, got {err:?}"
);
assert_eq!(err.stable_code(), "E034");
}
#[test]
fn span_by_code_points_at_the_addressed_value() {
let input = b"NAD+MS+9900112233445::293'";
let segments = parse(input);
let span = segments[0]
.span_by_code(&NAD, "3055")
.unwrap()
.expect("DE 3055 is present");
assert_eq!(&input[span.start..span.end], b"293");
}
#[test]
fn owned_and_borrowed_segments_resolve_identically() {
let input = b"NAD+MS+9900112233445::293'";
let owned: Vec<edifact_rs::OwnedSegment> =
edifact_rs::from_reader_collect(std::io::Cursor::new(input)).expect("fixture must parse");
assert_eq!(owned[0].value_by_code(&NAD, "3055").unwrap(), Some("293"));
assert_eq!(
owned[0].borrow().value_by_code(&NAD, "3039").unwrap(),
Some("9900112233445")
);
assert_eq!(
owned[0].span_by_code(&NAD, "3055").unwrap(),
owned[0].borrow().span_by_code(&NAD, "3055").unwrap(),
);
}
#[test]
fn runtime_loaded_definitions_resolve_the_same_codes() {
let def = OwnedSegmentDef::new_unchecked(
"NAD".to_owned(),
"Name and address".to_owned(),
vec![
OwnedElementRef::new_unchecked(1, "3035".to_owned(), Status::Mandatory, 1),
OwnedElementRef::new_unchecked(2, "C082".to_owned(), Status::Conditional, 1)
.with_components(vec![
OwnedComponentRef::new_unchecked(1, "3039".to_owned(), Status::Mandatory),
OwnedComponentRef::new_unchecked(2, "1131".to_owned(), Status::Conditional),
OwnedComponentRef::new_unchecked(3, "3055".to_owned(), Status::Conditional),
]),
],
);
let segments = parse(b"NAD+MS+9900112233445::293'");
assert_eq!(
segments[0].value_by_code(&def, "3055").unwrap(),
Some("293")
);
assert_eq!(
def.resolve_code("3039").unwrap(),
NAD.resolve_code("3039").unwrap()
);
assert!(segments[0].value_by_code(&def, "2380").is_err());
}
#[test]
fn code_addressed_derive_deserializes_from_the_right_slots() {
let segments = parse(b"NAD+MS+9900112233445::293'");
let party = SenderParty::edifact_deserialize(&segments).expect("deserialize");
assert_eq!(party.party_id, "9900112233445");
assert_eq!(party.agency.as_deref(), Some("293"));
}
#[test]
fn code_addressed_derive_round_trips() {
let segments = parse(b"NAD+MS+9900112233445::293'DTM+137:20260101:102'");
let party = SenderParty::edifact_deserialize(&segments).expect("deserialize NAD");
assert_eq!(
edifact_rs::to_edifact_string(&party).expect("serialize NAD"),
"NAD+MS+9900112233445::293'"
);
let date = DocumentDate::edifact_deserialize(&segments).expect("deserialize DTM");
assert_eq!(date.qualifier, "137");
assert_eq!(date.value, "20260101");
assert_eq!(date.format.as_deref(), Some("102"));
assert_eq!(
edifact_rs::to_edifact_string(&date).expect("serialize DTM"),
"DTM+137:20260101:102'"
);
}
#[test]
fn code_addressed_derive_matches_the_owned_path() {
let input = b"NAD+MS+9900112233445::293'";
let owned: Vec<edifact_rs::OwnedSegment> =
edifact_rs::from_reader_collect(std::io::Cursor::new(input)).expect("parse");
let borrowed = parse(input);
assert_eq!(
SenderParty::edifact_deserialize_owned(&owned).expect("owned"),
SenderParty::edifact_deserialize(&borrowed).expect("borrowed"),
);
}
#[test]
fn required_reports_the_variant_matching_what_the_field_addresses() {
let missing_qualifier = parse(b"NAD++9900112233445::293'");
let err = RequiredShapes::edifact_deserialize(&missing_qualifier).unwrap_err();
assert!(
matches!(&err, EdifactError::MissingRequiredElement { tag, element_index }
if tag == "NAD" && *element_index == 0),
"DE 3035 names a whole element, expected MissingRequiredElement, got {err:?}"
);
assert_eq!(err.stable_code(), "E008");
let missing_agency = parse(b"NAD+MS+9900112233445'");
let err = RequiredShapes::edifact_deserialize(&missing_agency).unwrap_err();
assert!(
matches!(&err, EdifactError::MissingRequiredComponent { tag, element_index, component_index }
if tag == "NAD" && *element_index == 1 && *component_index == 2),
"DE 3055 names a component, expected MissingRequiredComponent, got {err:?}"
);
assert_eq!(err.stable_code(), "E021");
}
#[test]
fn required_variant_agrees_across_borrowed_and_owned_paths() {
let input = b"NAD++9900112233445::293'";
let owned: Vec<edifact_rs::OwnedSegment> =
edifact_rs::from_reader_collect(std::io::Cursor::new(input)).expect("parse");
let borrowed = parse(input);
let from_owned = RequiredShapes::edifact_deserialize_owned(&owned).unwrap_err();
let from_borrowed = RequiredShapes::edifact_deserialize(&borrowed).unwrap_err();
assert_eq!(from_owned.stable_code(), from_borrowed.stable_code());
assert_eq!(from_owned, from_borrowed);
}
#[test]
fn declared_components_cap_a_composite_arity() {
use edifact_rs::{DirectoryValidator, ValidationReport, Validator};
const DEFS: &[SegmentDefinition] = &[NAD];
let validator = DirectoryValidator::from_definitions(DEFS).enforce_known_tags(false);
let too_many = parse(b"NAD+MS+9900112233445::293:EXTRA'");
let mut report = ValidationReport::default();
validator.validate_batch(
&too_many,
&mut report,
&edifact_rs::ValidationRuleContext::empty(),
);
assert!(
report
.errors()
.iter()
.any(|i| i.error_code() == Some("E013")),
"expected InvalidComponentCount, got {}",
report.render_deterministic()
);
let fewer = parse(b"NAD+MS+9900112233445'");
let mut report = ValidationReport::default();
validator.validate_batch(
&fewer,
&mut report,
&edifact_rs::ValidationRuleContext::empty(),
);
assert!(
!report.has_errors(),
"omitting conditional components must stay valid, got {}",
report.render_deterministic()
);
}
#[test]
fn absent_optional_component_serializes_as_an_empty_slot() {
let party = SenderParty {
party_id: "9900112233445".to_owned(),
agency: None,
};
assert_eq!(
edifact_rs::to_edifact_string(&party).expect("serialize"),
"NAD+MS+9900112233445::'"
);
}