1use crate::envelope::{
61 FunctionalGroupEnvelope, InterchangeEnvelope, MessageEnvelope, ValidatedInterchange,
62};
63use crate::model::{OwnedElement, OwnedSegment, Segment};
64use crate::report::{ValidationIssue, ValidationReport};
65use crate::{EdifactError, Writer};
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74#[non_exhaustive]
75pub enum Action {
76 Rejected,
78 Acknowledged,
81 Received,
83}
84
85impl Action {
86 #[must_use]
88 pub const fn code(self) -> &'static str {
89 match self {
90 Self::Rejected => "4",
91 Self::Acknowledged => "7",
92 Self::Received => "8",
93 }
94 }
95}
96
97impl std::fmt::Display for Action {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.write_str(self.code())
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
110#[non_exhaustive]
111pub enum ReportingLevel {
112 Interchange,
114 Group,
116 Message,
118 Segment,
120 DataElement,
122}
123
124impl ReportingLevel {
125 #[must_use]
127 pub const fn tag(self) -> &'static str {
128 match self {
129 Self::Interchange => "UCI",
130 Self::Group => "UCF",
131 Self::Message => "UCM",
132 Self::Segment => "UCS",
133 Self::DataElement => "UCD",
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
148#[non_exhaustive]
149pub enum SyntaxError {
150 SyntaxVersionNotSupported,
152 NotActualRecipient,
154 InvalidValue,
156 Missing,
158 ValueNotSupportedHere,
160 NotSupportedHere,
162 TooManyConstituents,
164 NoAgreement,
166 Unspecified,
168 InvalidAsServiceCharacter,
170 InvalidCharacters,
172 InvalidServiceCharacters,
174 UnknownSender,
176 TooOld,
178 TestIndicatorNotSupported,
180 DuplicateDetected,
182 ReferencesDoNotMatch,
184 ControlCountMismatch,
186 GroupsAndMessagesMixed,
188 LowerLevelEmpty,
190 InvalidOccurrenceOutsideMessage,
192 TooManyRepetitions,
194 TooManyGroupRepetitions,
196 InvalidCharacterType,
198 DataElementTooLong,
200 DataElementTooShort,
202 TrailingSeparator,
204 CharacterSetNotSupported,
206 EnvelopeFunctionalityNotSupported,
208}
209
210impl SyntaxError {
211 #[must_use]
213 pub const fn code(self) -> &'static str {
214 match self {
215 Self::SyntaxVersionNotSupported => "2",
216 Self::NotActualRecipient => "7",
217 Self::InvalidValue => "12",
218 Self::Missing => "13",
219 Self::ValueNotSupportedHere => "14",
220 Self::NotSupportedHere => "15",
221 Self::TooManyConstituents => "16",
222 Self::NoAgreement => "17",
223 Self::Unspecified => "18",
224 Self::InvalidAsServiceCharacter => "20",
225 Self::InvalidCharacters => "21",
226 Self::InvalidServiceCharacters => "22",
227 Self::UnknownSender => "23",
228 Self::TooOld => "24",
229 Self::TestIndicatorNotSupported => "25",
230 Self::DuplicateDetected => "26",
231 Self::ReferencesDoNotMatch => "28",
232 Self::ControlCountMismatch => "29",
233 Self::GroupsAndMessagesMixed => "30",
234 Self::LowerLevelEmpty => "32",
235 Self::InvalidOccurrenceOutsideMessage => "33",
236 Self::TooManyRepetitions => "35",
237 Self::TooManyGroupRepetitions => "36",
238 Self::InvalidCharacterType => "37",
239 Self::DataElementTooLong => "39",
240 Self::DataElementTooShort => "40",
241 Self::TrailingSeparator => "45",
242 Self::CharacterSetNotSupported => "46",
243 Self::EnvelopeFunctionalityNotSupported => "47",
244 }
245 }
246
247 #[must_use]
249 pub const fn description(self) -> &'static str {
250 match self {
251 Self::SyntaxVersionNotSupported => "syntax version or level not supported",
252 Self::NotActualRecipient => "interchange recipient not actual recipient",
253 Self::InvalidValue => "invalid value",
254 Self::Missing => "missing",
255 Self::ValueNotSupportedHere => "value not supported in this position",
256 Self::NotSupportedHere => "not supported in this position",
257 Self::TooManyConstituents => "too many constituents",
258 Self::NoAgreement => "no agreement",
259 Self::Unspecified => "unspecified error",
260 Self::InvalidAsServiceCharacter => "character invalid as service character",
261 Self::InvalidCharacters => "invalid character(s)",
262 Self::InvalidServiceCharacters => "invalid service character(s)",
263 Self::UnknownSender => "unknown interchange sender",
264 Self::TooOld => "too old",
265 Self::TestIndicatorNotSupported => "test indicator not supported",
266 Self::DuplicateDetected => "duplicate detected",
267 Self::ReferencesDoNotMatch => "references do not match",
268 Self::ControlCountMismatch => {
269 "control or octet count does not match number of instances received"
270 }
271 Self::GroupsAndMessagesMixed => "groups and messages/packages mixed",
272 Self::LowerLevelEmpty => "lower level empty",
273 Self::InvalidOccurrenceOutsideMessage => {
274 "invalid occurrence outside message, package or group"
275 }
276 Self::TooManyRepetitions => "too many repetitions",
277 Self::TooManyGroupRepetitions => "too many segment group repetitions",
278 Self::InvalidCharacterType => "invalid type of character(s)",
279 Self::DataElementTooLong => "data element too long",
280 Self::DataElementTooShort => "data element too short",
281 Self::TrailingSeparator => "trailing separator",
282 Self::CharacterSetNotSupported => "character set not supported",
283 Self::EnvelopeFunctionalityNotSupported => "envelope functionality not supported",
284 }
285 }
286
287 #[must_use]
303 pub const fn permitted_at(self, level: ReportingLevel) -> bool {
304 use ReportingLevel as L;
305 match self {
306 Self::SyntaxVersionNotSupported
308 | Self::NotActualRecipient
309 | Self::InvalidAsServiceCharacter
310 | Self::UnknownSender
311 | Self::CharacterSetNotSupported => matches!(l_of(level), L::Interchange),
312 Self::InvalidValue
314 | Self::Missing
315 | Self::ValueNotSupportedHere
316 | Self::NotSupportedHere
317 | Self::TooManyConstituents
318 | Self::Unspecified
319 | Self::InvalidCharacters => true,
320 Self::NoAgreement | Self::TestIndicatorNotSupported => matches!(
322 l_of(level),
323 L::Interchange | L::Group | L::Message | L::Segment
324 ),
325 Self::InvalidServiceCharacters => true,
326 Self::TooOld => matches!(l_of(level), L::Interchange | L::Group),
327 Self::DuplicateDetected
328 | Self::ReferencesDoNotMatch
329 | Self::ControlCountMismatch
330 | Self::GroupsAndMessagesMixed => {
331 matches!(l_of(level), L::Interchange | L::Group | L::Message)
332 }
333 Self::LowerLevelEmpty | Self::InvalidOccurrenceOutsideMessage => {
334 matches!(l_of(level), L::Interchange | L::Group)
335 }
336 Self::TooManyRepetitions => {
337 matches!(l_of(level), L::Message | L::Segment | L::DataElement)
338 }
339 Self::TooManyGroupRepetitions => matches!(l_of(level), L::Segment),
340 Self::InvalidCharacterType | Self::DataElementTooLong | Self::DataElementTooShort => {
341 matches!(
342 l_of(level),
343 L::Interchange | L::Group | L::Message | L::DataElement
344 )
345 }
346 Self::TrailingSeparator => matches!(
347 l_of(level),
348 L::Interchange | L::Group | L::Message | L::Segment
349 ),
350 Self::EnvelopeFunctionalityNotSupported => matches!(l_of(level), L::Group | L::Message),
351 }
352 }
353
354 #[must_use]
373 pub fn for_error(error: &EdifactError) -> Self {
374 use EdifactError as E;
375 match error {
376 E::MessageCountMismatch { .. } | E::SegmentCountMismatch { .. } => {
377 Self::ControlCountMismatch
378 }
379 E::QualifierMismatch { .. } => Self::ReferencesDoNotMatch,
380 E::DuplicateReference { .. } => Self::DuplicateDetected,
381 E::MissingRequiredElement { .. }
382 | E::MissingRequiredComponent { .. }
383 | E::MissingSegment { .. } => Self::Missing,
384 E::InvalidCodeValue { .. } | E::InvalidFieldValue { .. } => Self::InvalidValue,
385 E::InvalidSegmentForMessage { .. } | E::ConditionalRequirementNotMet { .. } => {
386 Self::NotSupportedHere
387 }
388 E::InvalidElementCount { .. } | E::InvalidComponentCount { .. } => {
389 Self::TooManyConstituents
390 }
391 E::UnrecognisedSyntaxIdentifier(_) | E::UnsupportedCharset { .. } => {
392 Self::CharacterSetNotSupported
393 }
394 E::CharacterNotInRepertoire { .. } | E::InvalidText { .. } => Self::InvalidCharacters,
395 E::InvalidUna | E::InvalidDelimiter { .. } | E::InvalidReleaseSequence { .. } => {
396 Self::InvalidServiceCharacters
397 }
398 E::EmptyInterchange { .. } | E::EmptyMessage { .. } => Self::LowerLevelEmpty,
399 E::SegmentWithoutDataElements { .. } => Self::Missing,
400 E::BlankDataElementValue { .. } => Self::InvalidValue,
401 E::PackageNotSupported { .. } => Self::EnvelopeFunctionalityNotSupported,
402 E::SegmentTooLong { .. } | E::DataElementTooLong { .. } => Self::DataElementTooLong,
403 E::DataElementTooShort { .. } => Self::DataElementTooShort,
404 E::InvalidCharacterType { .. } => Self::InvalidCharacterType,
405 E::TooManyRepetitions { .. } => Self::TooManyRepetitions,
406 E::TrailingSeparator { .. } => Self::TrailingSeparator,
407 E::GroupsAndMessagesMixed { .. } => Self::GroupsAndMessagesMixed,
408 E::InsignificantCharacters { .. } => Self::InvalidValue,
409 E::UnexpectedDataToken { .. } | E::InvalidSegmentTag(_) => {
410 Self::InvalidOccurrenceOutsideMessage
411 }
412 _ => Self::Unspecified,
413 }
414 }
415
416 #[must_use]
424 pub fn for_issue(issue: &ValidationIssue) -> Self {
425 match issue.error_code() {
426 Some("E004" | "E005") => Self::ControlCountMismatch,
427 Some("E016") => Self::ReferencesDoNotMatch,
428 Some("E032") => Self::DuplicateDetected,
429 Some("E008" | "E015" | "E021" | "E046") => Self::Missing,
430 Some("E014" | "E027" | "E045") => Self::InvalidValue,
431 Some("E011" | "E017") => Self::NotSupportedHere,
432 Some("E012" | "E013") => Self::TooManyConstituents,
433 Some("E031" | "E039") => Self::CharacterSetNotSupported,
434 Some("E003" | "E038") => Self::InvalidCharacters,
435 Some("E002" | "E007" | "E019") => Self::InvalidServiceCharacters,
436 Some("E042" | "E043") => Self::LowerLevelEmpty,
437 Some("E044") => Self::EnvelopeFunctionalityNotSupported,
438 Some("E020" | "E049") => Self::DataElementTooLong,
439 Some("E050") => Self::DataElementTooShort,
440 Some("E048") => Self::InvalidCharacterType,
441 Some("E047") => Self::TooManyRepetitions,
442 Some("E051") => Self::TrailingSeparator,
443 Some("E052") => Self::GroupsAndMessagesMixed,
444 Some("E053") => Self::InvalidValue,
445 Some("E006" | "E028") => Self::InvalidOccurrenceOutsideMessage,
446 _ => Self::Unspecified,
447 }
448 }
449}
450
451#[inline]
458const fn l_of(level: ReportingLevel) -> ReportingLevel {
459 level
460}
461
462impl std::fmt::Display for SyntaxError {
463 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464 write!(f, "{} ({})", self.code(), self.description())
465 }
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
477enum Scope {
478 Group(usize),
480 Message {
482 index: usize,
483 segment_position: u32,
485 },
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
490struct Finding {
491 scope: Scope,
492 element_position: Option<u32>,
494 component_position: Option<u32>,
496 rejects: bool,
501 error: SyntaxError,
502}
503
504impl Finding {
505 fn level(&self) -> ReportingLevel {
514 if let Scope::Group(_) = self.scope {
515 return ReportingLevel::Group;
516 }
517 if self.element_position.is_some() && self.error.permitted_at(ReportingLevel::DataElement) {
518 return ReportingLevel::DataElement;
519 }
520 if self.error.permitted_at(ReportingLevel::Segment) {
521 return ReportingLevel::Segment;
522 }
523 ReportingLevel::Message
524 }
525
526 fn message_index(&self) -> Option<usize> {
528 match self.scope {
529 Scope::Message { index, .. } => Some(index),
530 Scope::Group(_) => None,
531 }
532 }
533}
534
535#[derive(Debug, Clone)]
546pub struct Contrl {
547 control_ref: String,
548 sender: String,
549 sender_qualifier: String,
550 recipient: String,
551 recipient_qualifier: String,
552 action: Action,
553 interchange_error: Option<SyntaxError>,
554 message_ref: Option<String>,
555 messages: Vec<(usize, MessageEnvelope, Action)>,
561 groups: Vec<GroupReport>,
565 findings: Vec<Finding>,
566}
567
568#[derive(Debug, Clone)]
570struct GroupReport {
571 envelope: FunctionalGroupEnvelope,
572 action: Action,
573 error: Option<SyntaxError>,
574 messages: Vec<(usize, MessageEnvelope, Action)>,
576}
577
578impl Contrl {
579 fn base(interchange: &InterchangeEnvelope, action: Action) -> Self {
580 Self {
581 control_ref: interchange.control_ref.clone(),
582 sender: interchange.sender_id.clone(),
583 sender_qualifier: interchange.sender_qualifier.clone(),
584 recipient: interchange.recipient_id.clone(),
585 recipient_qualifier: interchange.recipient_qualifier.clone(),
586 action,
587 interchange_error: None,
588 message_ref: None,
589 messages: Vec::new(),
590 groups: Vec::new(),
591 findings: Vec::new(),
592 }
593 }
594
595 #[must_use]
600 pub fn receipt(interchange: &InterchangeEnvelope) -> Self {
601 Self::base(interchange, Action::Received)
602 }
603
604 #[must_use]
610 pub fn acknowledgement(subject: &ValidatedInterchange) -> Self {
611 Self::base(&subject.interchange, Action::Acknowledged)
612 }
613
614 #[must_use]
644 pub fn from_report(
645 subject: &ValidatedInterchange,
646 segments: &[Segment<'_>],
647 report: &ValidationReport,
648 ) -> Self {
649 let mut contrl = Self::base(&subject.interchange, Action::Acknowledged);
650
651 let messages = message_boundaries(segments);
654 let groups = group_boundaries(segments);
655
656 for (issue, rejects) in report
657 .errors()
658 .iter()
659 .map(|i| (i, true))
660 .chain(report.warnings().iter().map(|i| (i, false)))
661 {
662 let error = SyntaxError::for_issue(issue);
663 let located = issue
664 .span
665 .and_then(|span| locate(segments, &messages, &groups, span.start));
666
667 let Some(scope) = located else {
671 if rejects {
672 contrl.action = Action::Rejected;
673 }
674 if contrl.interchange_error.is_none()
675 && error.permitted_at(ReportingLevel::Interchange)
676 {
677 contrl.interchange_error = Some(error);
678 }
679 continue;
680 };
681
682 contrl.findings.push(Finding {
683 scope,
684 element_position: issue.element_index.map(|i| u32::from(i) + 2),
687 component_position: issue.component_index.map(|i| u32::from(i) + 1),
688 rejects,
689 error,
690 });
691 }
692
693 if contrl.action == Action::Rejected {
696 contrl.findings.clear();
697 return contrl;
698 }
699
700 if subject.functional_groups.is_empty() {
701 contrl.messages = contrl.reported_messages(subject.messages.iter().enumerate());
702 } else {
703 contrl.build_group_reports(subject);
704 }
705
706 contrl
707 }
708
709 fn reported_messages<'m>(
714 &self,
715 candidates: impl Iterator<Item = (usize, &'m MessageEnvelope)>,
716 ) -> Vec<(usize, MessageEnvelope, Action)> {
717 let mut out = Vec::new();
718 for (index, message) in candidates {
719 let mut has_finding = false;
720 let mut rejected = false;
721 for finding in &self.findings {
722 if finding.message_index() == Some(index) {
723 has_finding = true;
724 rejected |= finding.rejects;
725 }
726 }
727 if !has_finding {
728 continue;
729 }
730 out.push((
731 index,
732 message.clone(),
733 if rejected {
734 Action::Rejected
735 } else {
736 Action::Acknowledged
737 },
738 ));
739 }
740 out
741 }
742
743 fn build_group_reports(&mut self, subject: &ValidatedInterchange) {
745 let mut flat = 0usize;
749 let mut reports = Vec::new();
750
751 for (group_index, group) in subject.functional_groups.iter().enumerate() {
752 let start = flat;
753 flat += group.messages.len();
754
755 let group_fault = self
756 .findings
757 .iter()
758 .find(|f| f.scope == Scope::Group(group_index));
759 let rejected = group_fault.is_some_and(|f| f.rejects);
760
761 let messages = if rejected {
764 Vec::new()
765 } else {
766 self.reported_messages(
767 group
768 .messages
769 .iter()
770 .enumerate()
771 .map(|(offset, message)| (start + offset, message)),
772 )
773 };
774
775 if group_fault.is_none() && messages.is_empty() {
776 continue; }
778
779 reports.push(GroupReport {
780 envelope: group.clone(),
781 action: if rejected {
782 Action::Rejected
783 } else {
784 Action::Acknowledged
785 },
786 error: group_fault
787 .map(|f| f.error)
788 .filter(|e| e.permitted_at(ReportingLevel::Group)),
789 messages,
790 });
791 }
792 self.groups = reports;
793 }
794
795 #[must_use]
799 pub fn with_message_reference(mut self, reference: impl Into<String>) -> Self {
800 self.message_ref = Some(reference.into());
801 self
802 }
803
804 #[must_use]
810 pub fn with_interchange_error(mut self, error: SyntaxError) -> Self {
811 if error.permitted_at(ReportingLevel::Interchange) {
812 self.interchange_error = Some(error);
813 }
814 self
815 }
816
817 #[must_use]
819 pub const fn action(&self) -> Action {
820 self.action
821 }
822
823 #[must_use]
825 pub fn message_reference(&self) -> &str {
826 self.message_ref.as_deref().unwrap_or(&self.control_ref)
827 }
828
829 #[must_use]
834 pub fn segments(&self) -> Vec<OwnedSegment> {
835 let mut out = Vec::new();
836 let reference = self.message_reference().to_owned();
837
838 out.push(OwnedSegment::new(
840 "UNH",
841 vec![
842 OwnedElement::of(&[reference.as_str()]),
843 OwnedElement::of(&["CONTRL", "4", "1", "UN"]),
844 ],
845 ));
846
847 let mut uci = vec![
849 OwnedElement::of(&[self.control_ref.as_str()]),
850 party(&self.sender, &self.sender_qualifier),
851 party(&self.recipient, &self.recipient_qualifier),
852 OwnedElement::of(&[self.action.code()]),
853 ];
854 if let Some(error) = self.interchange_error {
855 uci.push(OwnedElement::of(&[error.code()]));
856 }
857 out.push(OwnedSegment::new("UCI", uci));
858
859 for (index, message, action) in &self.messages {
861 out.extend(self.message_report(*index, message, *action));
862 }
863 for group in &self.groups {
864 let mut ucf = vec![
865 OwnedElement::of(&[group.envelope.group_ref.as_str()]),
866 party(
867 &group.envelope.app_sender,
868 &group.envelope.app_sender_qualifier,
869 ),
870 party(
871 &group.envelope.app_recipient,
872 &group.envelope.app_recipient_qualifier,
873 ),
874 OwnedElement::of(&[group.action.code()]),
875 ];
876 if let Some(error) = group.error {
877 ucf.push(OwnedElement::of(&[error.code()]));
878 }
879 out.push(OwnedSegment::new("UCF", ucf));
880 for (index, message, action) in &group.messages {
881 out.extend(self.message_report(*index, message, *action));
882 }
883 }
884
885 let count = (out.len() + 1).to_string();
886 out.push(OwnedSegment::new(
887 "UNT",
888 vec![
889 OwnedElement::of(&[count.as_str()]),
890 OwnedElement::of(&[reference.as_str()]),
891 ],
892 ));
893 out
894 }
895
896 fn message_report(
898 &self,
899 index: usize,
900 message: &MessageEnvelope,
901 action: Action,
902 ) -> Vec<OwnedSegment> {
903 let mut ucm = vec![
904 OwnedElement::of(&[message.message_ref.as_str()]),
905 OwnedElement::of(&[
906 message.message_type.as_str(),
907 message.version.as_str(),
908 message.release.as_str(),
909 message.controlling_agency.as_str(),
910 ]),
911 OwnedElement::of(&[action.code()]),
912 ];
913 if let Some(finding) = self
915 .findings
916 .iter()
917 .find(|f| f.message_index() == Some(index) && f.level() == ReportingLevel::Message)
918 {
919 ucm.push(OwnedElement::of(&[finding.error.code()]));
920 }
921 let mut out = vec![OwnedSegment::new("UCM", ucm)];
922 out.extend(self.segment_reports(index));
923 out
924 }
925
926 fn segment_reports(&self, message_index: usize) -> Vec<OwnedSegment> {
931 let mut out = Vec::new();
932 let mut reported_positions: Vec<u32> = Vec::new();
933
934 for finding in self
935 .findings
936 .iter()
937 .filter(|f| f.message_index() == Some(message_index))
938 {
939 let level = finding.level();
940 let Scope::Message {
941 segment_position: position,
942 ..
943 } = finding.scope
944 else {
945 continue;
946 };
947 if level == ReportingLevel::Message {
948 continue;
949 }
950 if !reported_positions.contains(&position) {
954 reported_positions.push(position);
955 let position_text = position.to_string();
956 let mut ucs = vec![OwnedElement::of(&[position_text.as_str()])];
957 if level == ReportingLevel::Segment {
960 ucs.push(OwnedElement::of(&[finding.error.code()]));
961 }
962 out.push(OwnedSegment::new("UCS", ucs));
963 }
964
965 if level == ReportingLevel::DataElement {
966 let element_text = finding
967 .element_position
968 .expect("DataElement level implies a known element position")
969 .to_string();
970 let mut identification = vec![element_text];
971 if let Some(component) = finding.component_position {
972 identification.push(component.to_string());
973 }
974 out.push(OwnedSegment::new(
975 "UCD",
976 vec![
977 OwnedElement::of(&[finding.error.code()]),
978 OwnedElement::of(&identification),
979 ],
980 ));
981 }
982 }
983 out
984 }
985
986 pub fn to_bytes(&self) -> Result<Vec<u8>, EdifactError> {
992 crate::segments_to_bytes_owned(&self.segments())
993 }
994
995 pub fn to_edifact_string(&self) -> Result<String, EdifactError> {
1001 String::from_utf8(self.to_bytes()?).map_err(|_| EdifactError::InvalidUtf8)
1002 }
1003
1004 pub fn to_interchange_bytes(
1031 &self,
1032 syntax_identifier: &str,
1033 syntax_version: &str,
1034 date: &str,
1035 time: &str,
1036 control_reference: &str,
1037 ) -> Result<Vec<u8>, EdifactError> {
1038 let mut writer = Writer::new(Vec::new());
1039 writer.begin_interchange(
1040 syntax_identifier,
1041 syntax_version,
1042 &self.recipient,
1044 &self.sender,
1045 date,
1046 time,
1047 control_reference,
1048 )?;
1049 for segment in self.segments() {
1050 writer.write_segment(&segment.as_borrowed())?;
1051 }
1052 writer.end_interchange(1, control_reference)?;
1053 writer.finish()
1054 }
1055
1056 pub fn to_interchange_string(
1063 &self,
1064 syntax_identifier: &str,
1065 syntax_version: &str,
1066 date: &str,
1067 time: &str,
1068 control_reference: &str,
1069 ) -> Result<String, EdifactError> {
1070 let bytes = self.to_interchange_bytes(
1071 syntax_identifier,
1072 syntax_version,
1073 date,
1074 time,
1075 control_reference,
1076 )?;
1077 String::from_utf8(bytes).map_err(|_| EdifactError::InvalidUtf8)
1078 }
1079}
1080
1081fn party(id: &str, qualifier: &str) -> OwnedElement {
1083 if qualifier.is_empty() {
1084 OwnedElement::of(&[id])
1085 } else {
1086 OwnedElement::of(&[id, qualifier])
1087 }
1088}
1089
1090fn message_boundaries(segments: &[Segment<'_>]) -> Vec<(usize, usize)> {
1095 spans_between(segments, "UNH", "UNT")
1096}
1097
1098fn group_boundaries(segments: &[Segment<'_>]) -> Vec<(usize, usize)> {
1102 spans_between(segments, "UNG", "UNE")
1103}
1104
1105fn spans_between(segments: &[Segment<'_>], open: &str, close: &str) -> Vec<(usize, usize)> {
1107 let mut out = Vec::new();
1108 let mut start: Option<usize> = None;
1109 for (index, segment) in segments.iter().enumerate() {
1110 if segment.tag == open {
1111 start = Some(index);
1112 } else if segment.tag == close {
1113 if let Some(from) = start.take() {
1114 out.push((from, index));
1115 }
1116 }
1117 }
1118 out
1119}
1120
1121fn locate(
1135 segments: &[Segment<'_>],
1136 messages: &[(usize, usize)],
1137 groups: &[(usize, usize)],
1138 offset: usize,
1139) -> Option<Scope> {
1140 let index = match segments.binary_search_by(|segment| segment.span.start.cmp(&offset)) {
1143 Ok(exact) => exact,
1144 Err(0) => return None,
1145 Err(next) => next - 1,
1146 };
1147 if let Some((message_index, (start, _))) = messages
1148 .iter()
1149 .enumerate()
1150 .find(|(_, (start, end))| (*start..=*end).contains(&index))
1151 {
1152 return u32::try_from(index - start + 1)
1153 .ok()
1154 .map(|segment_position| Scope::Message {
1155 index: message_index,
1156 segment_position,
1157 });
1158 }
1159 groups
1160 .iter()
1161 .position(|(start, end)| (*start..=*end).contains(&index))
1162 .map(Scope::Group)
1163}
1164
1165#[cfg(test)]
1166mod tests {
1167 use super::*;
1168
1169 fn parse(input: &[u8]) -> Vec<OwnedSegment> {
1170 crate::from_bytes_owned(input)
1171 .collect::<Result<Vec<_>, _>>()
1172 .expect("parse")
1173 }
1174
1175 #[test]
1176 fn annex_a_permits_a_code_only_where_the_table_says() {
1177 assert!(SyntaxError::NotActualRecipient.permitted_at(ReportingLevel::Interchange));
1179 assert!(!SyntaxError::NotActualRecipient.permitted_at(ReportingLevel::Group));
1180 assert!(SyntaxError::TooManyGroupRepetitions.permitted_at(ReportingLevel::Segment));
1181 assert!(!SyntaxError::TooManyGroupRepetitions.permitted_at(ReportingLevel::DataElement));
1182 assert!(SyntaxError::LowerLevelEmpty.permitted_at(ReportingLevel::Group));
1183 assert!(!SyntaxError::LowerLevelEmpty.permitted_at(ReportingLevel::Message));
1184 assert!(SyntaxError::EnvelopeFunctionalityNotSupported.permitted_at(ReportingLevel::Group));
1185 assert!(
1186 !SyntaxError::EnvelopeFunctionalityNotSupported
1187 .permitted_at(ReportingLevel::Interchange)
1188 );
1189 for level in [
1191 ReportingLevel::Interchange,
1192 ReportingLevel::Group,
1193 ReportingLevel::Message,
1194 ReportingLevel::Segment,
1195 ReportingLevel::DataElement,
1196 ] {
1197 assert!(SyntaxError::InvalidValue.permitted_at(level), "{level:?}");
1198 }
1199 }
1200
1201 #[test]
1202 fn every_code_is_permitted_somewhere() {
1203 for error in ALL_ERRORS {
1206 assert!(
1207 [
1208 ReportingLevel::Interchange,
1209 ReportingLevel::Group,
1210 ReportingLevel::Message,
1211 ReportingLevel::Segment,
1212 ReportingLevel::DataElement,
1213 ]
1214 .iter()
1215 .any(|level| error.permitted_at(*level)),
1216 "{error:?} is permitted nowhere"
1217 );
1218 }
1219 }
1220
1221 #[test]
1222 fn codes_are_unique() {
1223 let mut codes: Vec<&str> = ALL_ERRORS.iter().map(|e| e.code()).collect();
1224 codes.sort_unstable();
1225 let before = codes.len();
1226 codes.dedup();
1227 assert_eq!(before, codes.len(), "duplicate DE 0085 code");
1228 }
1229
1230 const ALL_ERRORS: &[SyntaxError] = &[
1231 SyntaxError::SyntaxVersionNotSupported,
1232 SyntaxError::NotActualRecipient,
1233 SyntaxError::InvalidValue,
1234 SyntaxError::Missing,
1235 SyntaxError::ValueNotSupportedHere,
1236 SyntaxError::NotSupportedHere,
1237 SyntaxError::TooManyConstituents,
1238 SyntaxError::NoAgreement,
1239 SyntaxError::Unspecified,
1240 SyntaxError::InvalidAsServiceCharacter,
1241 SyntaxError::InvalidCharacters,
1242 SyntaxError::InvalidServiceCharacters,
1243 SyntaxError::UnknownSender,
1244 SyntaxError::TooOld,
1245 SyntaxError::TestIndicatorNotSupported,
1246 SyntaxError::DuplicateDetected,
1247 SyntaxError::ReferencesDoNotMatch,
1248 SyntaxError::ControlCountMismatch,
1249 SyntaxError::GroupsAndMessagesMixed,
1250 SyntaxError::LowerLevelEmpty,
1251 SyntaxError::InvalidOccurrenceOutsideMessage,
1252 SyntaxError::TooManyRepetitions,
1253 SyntaxError::TooManyGroupRepetitions,
1254 SyntaxError::InvalidCharacterType,
1255 SyntaxError::DataElementTooLong,
1256 SyntaxError::DataElementTooShort,
1257 SyntaxError::TrailingSeparator,
1258 SyntaxError::CharacterSetNotSupported,
1259 SyntaxError::EnvelopeFunctionalityNotSupported,
1260 ];
1261
1262 #[test]
1263 fn an_acknowledgement_validates_against_the_shipped_layouts() {
1264 let raw = b"UNB+UNOC:3+SENDER:14+RECEIVER:14+260101:0900+IC4711'\
1265 UNH+MSG1+ORDERS:D:96A:UN'BGM+220+PO-1+9'UNT+3+MSG1'\
1266 UNZ+1+IC4711'";
1267 let owned = parse(raw);
1268 let segments: Vec<_> = owned.iter().map(OwnedSegment::as_borrowed).collect();
1269 let validated = crate::validate_envelope(&segments).expect("valid subject");
1270
1271 let contrl = Contrl::acknowledgement(&validated).with_message_reference("ACK1");
1272 let wire = contrl.to_edifact_string().expect("render");
1273
1274 assert_eq!(
1275 wire,
1276 "UNH+ACK1+CONTRL:4:1:UN'UCI+IC4711+SENDER:14+RECEIVER:14+7'UNT+3+ACK1'"
1277 );
1278
1279 let reparsed = parse(wire.as_bytes());
1281 let borrowed: Vec<_> = reparsed.iter().map(OwnedSegment::as_borrowed).collect();
1282 let validator = crate::DirectoryValidator::new(
1283 "iso-9735-4",
1284 crate::service::lookup,
1285 |_, _| true,
1286 |_, _| None,
1287 |_, _| None,
1288 None,
1289 );
1290 let report = crate::ValidationContext::builder()
1291 .with_validator(crate::ValidationLayer::Structure, validator)
1292 .build()
1293 .validate_lenient(&borrowed);
1294 assert!(!report.has_errors(), "{:#?}", report.errors());
1295 }
1296
1297 #[test]
1298 fn a_receipt_carries_action_8_and_nothing_else() {
1299 let raw =
1300 b"UNB+UNOC:3+S+R+260101:0900+IC1'UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'UNZ+1+IC1'";
1301 let owned = parse(raw);
1302 let segments: Vec<_> = owned.iter().map(OwnedSegment::as_borrowed).collect();
1303 let validated = crate::validate_envelope(&segments).expect("valid subject");
1304
1305 let wire = Contrl::receipt(&validated.interchange)
1306 .to_edifact_string()
1307 .expect("render");
1308 assert!(wire.contains("UCI+IC1+S+R+8'"), "{wire}");
1309 assert_eq!(
1310 Contrl::receipt(&validated.interchange).action(),
1311 Action::Received
1312 );
1313 }
1314
1315 #[test]
1316 fn the_message_reference_defaults_to_the_subject_control_reference() {
1317 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC-42'UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'UNZ+1+IC-42'";
1318 let owned = parse(raw);
1319 let segments: Vec<_> = owned.iter().map(OwnedSegment::as_borrowed).collect();
1320 let validated = crate::validate_envelope(&segments).expect("valid subject");
1321 assert_eq!(
1322 Contrl::acknowledgement(&validated).message_reference(),
1323 "IC-42"
1324 );
1325 }
1326
1327 #[test]
1328 fn a_forbidden_interchange_level_code_is_not_recorded() {
1329 let raw =
1330 b"UNB+UNOC:3+S+R+260101:0900+IC1'UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'UNZ+1+IC1'";
1331 let owned = parse(raw);
1332 let segments: Vec<_> = owned.iter().map(OwnedSegment::as_borrowed).collect();
1333 let validated = crate::validate_envelope(&segments).expect("valid subject");
1334
1335 let wire = Contrl::acknowledgement(&validated)
1337 .with_interchange_error(SyntaxError::TooManyGroupRepetitions)
1338 .to_edifact_string()
1339 .expect("render");
1340 assert!(wire.contains("UCI+IC1+S+R+7'"), "{wire}");
1341 }
1342
1343 fn report_for(raw: &[u8]) -> Contrl {
1348 let owned = parse(raw);
1349 let segments: Vec<_> = owned.iter().map(OwnedSegment::as_borrowed).collect();
1350 let validated = crate::validate_envelope_lenient(&segments)
1351 .interchange
1352 .expect("subject must be structurally interpretable");
1353 let report = crate::ValidationContext::builder()
1354 .with_envelope_validation()
1355 .with_syntax_validation()
1356 .build()
1357 .validate_lenient(&segments);
1358 Contrl::from_report(&validated, &segments, &report)
1359 }
1360
1361 #[test]
1362 fn a_data_element_fault_is_reported_at_the_ucd_level() {
1363 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1367 UNH+M1+ORDERS:D:96A:UN'FTX+ 'UNT+3+M1'\
1368 UNZ+1+IC1'";
1369 let contrl = report_for(raw);
1370
1371 assert_eq!(contrl.action(), Action::Acknowledged);
1374 let wire = contrl.to_edifact_string().expect("render");
1375
1376 assert!(wire.contains("UCM+M1+ORDERS:D:96A:UN+7'"), "{wire}");
1379 assert!(wire.contains("UCS+2'"), "{wire}");
1380 assert!(wire.contains("UCD+12+2:1'"), "{wire}");
1381 }
1382
1383 #[test]
1384 fn a_rejected_message_is_named_by_its_ucm() {
1385 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1388 UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'\
1389 UNH+M1+ORDERS:D:96A:UN'BGM+221'UNT+3+M1'\
1390 UNZ+2+IC1'";
1391 let contrl = report_for(raw);
1392
1393 assert_eq!(contrl.action(), Action::Acknowledged);
1397 let wire = contrl.to_edifact_string().expect("render");
1398
1399 assert_eq!(
1404 wire,
1405 "UNH+IC1+CONTRL:4:1:UN'\
1406 UCI+IC1+S+R+7'\
1407 UCM+M1+ORDERS:D:96A:UN+4+26'\
1408 UNT+4+IC1'"
1409 );
1410 }
1411
1412 #[test]
1413 fn an_envelope_fault_rejects_the_interchange_and_emits_no_ucm() {
1414 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1418 UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'\
1419 UNZ+1+IC-OTHER'";
1420 let contrl = report_for(raw);
1421
1422 assert_eq!(contrl.action(), Action::Rejected);
1423 let wire = contrl.to_edifact_string().expect("render");
1424 assert_eq!(wire, "UNH+IC1+CONTRL:4:1:UN'UCI+IC1+S+R+4+28'UNT+3+IC1'");
1425 }
1426
1427 #[test]
1428 fn a_count_mismatch_is_reported_on_the_message_that_got_it_wrong() {
1429 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1432 UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+9+M1'\
1433 UNZ+1+IC1'";
1434 let contrl = report_for(raw);
1435
1436 assert_eq!(contrl.action(), Action::Acknowledged);
1437 let wire = contrl.to_edifact_string().expect("render");
1438 assert_eq!(
1441 wire,
1442 "UNH+IC1+CONTRL:4:1:UN'\
1443 UCI+IC1+S+R+7'\
1444 UCM+M1+ORDERS:D:96A:UN+4+29'\
1445 UNT+4+IC1'"
1446 );
1447 }
1448
1449 #[test]
1450 fn a_finding_lands_at_the_lowest_level_annex_a_permits() {
1451 let duplicate = Finding {
1454 scope: Scope::Message {
1455 index: 0,
1456 segment_position: 1,
1457 },
1458 element_position: Some(2),
1459 component_position: None,
1460 rejects: true,
1461 error: SyntaxError::DuplicateDetected,
1462 };
1463 assert_eq!(duplicate.level(), ReportingLevel::Message);
1464
1465 let invalid = Finding {
1467 error: SyntaxError::InvalidValue,
1468 ..duplicate.clone()
1469 };
1470 assert_eq!(invalid.level(), ReportingLevel::DataElement);
1471
1472 let segment_only = Finding {
1474 element_position: None,
1475 ..invalid.clone()
1476 };
1477 assert_eq!(segment_only.level(), ReportingLevel::Segment);
1478
1479 let group_repetitions = Finding {
1482 error: SyntaxError::TooManyGroupRepetitions,
1483 ..duplicate
1484 };
1485 assert_eq!(group_repetitions.level(), ReportingLevel::Segment);
1486 }
1487
1488 #[test]
1489 fn a_generated_contrl_reparses_and_counts_its_own_segments() {
1490 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1491 UNH+M1+ORDERS:D:96A:UN'FTX+ 'UNT+3+M1'\
1492 UNZ+1+IC1'";
1493 let contrl = report_for(raw);
1494
1495 let wire = contrl
1496 .to_interchange_string("UNOC", "3", "260101", "0930", "ACK-1")
1497 .expect("render");
1498
1499 let segments: Vec<_> = crate::from_bytes(wire.as_bytes())
1502 .collect::<Result<Vec<_>, _>>()
1503 .expect("generated CONTRL must reparse");
1504 let validated =
1505 crate::validate_envelope(&segments).expect("generated CONTRL must validate");
1506 assert_eq!(validated.messages.len(), 1);
1507 assert_eq!(validated.messages[0].message_type, "CONTRL");
1508 assert_eq!(validated.messages[0].version, "4");
1509 assert_eq!(validated.messages[0].release, "1");
1510 assert_eq!(
1511 validated.messages[0].declared_segment_count,
1512 validated.messages[0].actual_segment_count
1513 );
1514 }
1515
1516 #[test]
1517 fn a_grouped_interchange_is_reported_through_ucf() {
1518 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1521 UNG+ORDERS+SND:14+RCV:14+260101:0900+GRP1+UN+D:96A'\
1522 UNH+M1+ORDERS:D:96A:UN'FTX+ 'UNT+3+M1'\
1523 UNE+1+GRP1'\
1524 UNZ+1+IC1'";
1525 let contrl = report_for(raw);
1526
1527 assert_eq!(contrl.action(), Action::Acknowledged);
1528 let wire = contrl.to_edifact_string().expect("render");
1529
1530 assert_eq!(
1533 wire,
1534 "UNH+IC1+CONTRL:4:1:UN'\
1535 UCI+IC1+S+R+7'\
1536 UCF+GRP1+SND:14+RCV:14+7'\
1537 UCM+M1+ORDERS:D:96A:UN+7'\
1538 UCS+2'\
1539 UCD+12+2:1'\
1540 UNT+7+IC1'"
1541 );
1542 }
1543
1544 #[test]
1545 fn a_group_envelope_fault_rejects_only_that_group() {
1546 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1550 UNG+ORDERS+SND+RCV+260101:0900+GRP1+UN+D:96A'\
1551 UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'\
1552 UNE+1+GRP-OTHER'\
1553 UNZ+1+IC1'";
1554 let contrl = report_for(raw);
1555
1556 assert_eq!(contrl.action(), Action::Acknowledged);
1557 let wire = contrl.to_edifact_string().expect("render");
1558 assert_eq!(
1559 wire,
1560 "UNH+IC1+CONTRL:4:1:UN'\
1561 UCI+IC1+S+R+7'\
1562 UCF+GRP1+SND+RCV+4+28'\
1563 UNT+4+IC1'"
1564 );
1565 }
1566
1567 #[test]
1568 fn a_clean_grouped_interchange_needs_no_ucf() {
1569 let raw = b"UNB+UNOC:3+S+R+260101:0900+IC1'\
1572 UNG+ORDERS+SND+RCV+260101:0900+GRP1+UN+D:96A'\
1573 UNH+M1+ORDERS:D:96A:UN'BGM+220'UNT+3+M1'\
1574 UNE+1+GRP1'\
1575 UNZ+1+IC1'";
1576 let contrl = report_for(raw);
1577 assert_eq!(
1578 contrl.to_edifact_string().expect("render"),
1579 "UNH+IC1+CONTRL:4:1:UN'UCI+IC1+S+R+7'UNT+3+IC1'"
1580 );
1581 }
1582
1583 #[test]
1584 fn error_mapping_picks_the_narrowest_annex_a_code() {
1585 use EdifactError as E;
1586 let cases: [(EdifactError, SyntaxError); 6] = [
1587 (
1588 E::MessageCountMismatch {
1589 expected: 1,
1590 actual: 2,
1591 },
1592 SyntaxError::ControlCountMismatch,
1593 ),
1594 (
1595 E::DuplicateReference {
1596 tag: "UNH".to_owned(),
1597 reference: "1".to_owned(),
1598 span: crate::Span::new(0, 1),
1599 },
1600 SyntaxError::DuplicateDetected,
1601 ),
1602 (
1603 E::UnsupportedCharset {
1604 syntax_identifier: "UNOX".to_owned(),
1605 },
1606 SyntaxError::CharacterSetNotSupported,
1607 ),
1608 (E::InvalidUna, SyntaxError::InvalidServiceCharacters),
1609 (
1610 E::EmptyInterchange {
1611 control_ref: "IC1".to_owned(),
1612 },
1613 SyntaxError::LowerLevelEmpty,
1614 ),
1615 (
1616 E::PackageNotSupported {
1617 tag: "UNO".to_owned(),
1618 span: crate::Span::new(0, 1),
1619 },
1620 SyntaxError::EnvelopeFunctionalityNotSupported,
1621 ),
1622 ];
1623 for (error, expected) in cases {
1624 assert_eq!(SyntaxError::for_error(&error), expected, "{error:?}");
1625 }
1626 }
1627}