1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Structure diagnostics emitted during MIG-guided assembly.
use serde::{Deserialize, Serialize};
/// A structure-level issue found during MIG-guided assembly.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StructureDiagnostic {
pub kind: StructureDiagnosticKind,
pub segment_id: String,
/// The input counter of the segment the finding is reported at (the
/// parser's `segment_number`, interchange-relative, UNB = 1). A missing
/// segment or group is reported at its predecessor.
pub position: usize,
pub message: String,
}
/// Classification of structure-level diagnostic issues.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StructureDiagnosticKind {
/// A segment appeared where it was not expected by the MIG schema.
UnexpectedSegment,
/// A segment the MIG marks mandatory (`M` / `R`) is absent from a
/// message, or from a group repetition of the variant that requires it.
MissingRequiredSegment,
/// A segment repeats more often than the MIG allows at its place.
MaxRepetitionsExceeded,
/// A segment's qualifier is none of the codes the MIG allows for that
/// segment in this group, so no slot could hold it; the assembler
/// advanced past it.
UnrecognizedQualifier,
/// A group variant the MIG marks mandatory (`M` / `R`) is absent.
MissingRequiredGroup,
/// A group variant repeats more often than the MIG allows.
GroupMaxRepetitionsExceeded,
/// A segment was present in the EDIFACT input but the PID-filtered MIG
/// has no slot for it, and the assembler ran with `skip_unknown_segments`
/// on. The segment was advanced past (preserved on `skipped_segments`)
/// so subsequent legitimate content still assembles, but the caller
/// should surface it — the message contains data outside the AHB.
SkippedUnknownSegment,
/// A segment the MIG defines as a non-entry segment of a group (e.g. `CAV`
/// in SG10) appeared without that group's entry segment (`CCI`), so no
/// group repetition could hold it. Like [`SkippedUnknownSegment`] the
/// assembler advanced past it and its content is absent from the result,
/// but the defect is a missing entry segment, not foreign content.
///
/// [`SkippedUnknownSegment`]: Self::SkippedUnknownSegment
OrphanedGroupSegment,
}
impl std::fmt::Display for StructureDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{:?}] {} at position {}: {}",
self.kind, self.segment_id, self.position, self.message
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_format_includes_all_fields() {
let diag = StructureDiagnostic {
kind: StructureDiagnosticKind::UnexpectedSegment,
segment_id: "BGM".to_string(),
position: 3,
message: "BGM not expected after UNH".to_string(),
};
let display = diag.to_string();
assert!(display.contains("BGM"), "display should contain segment_id");
assert!(display.contains("3"), "display should contain position");
assert!(
display.contains("BGM not expected after UNH"),
"display should contain message"
);
assert!(
display.contains("UnexpectedSegment"),
"display should contain kind"
);
}
#[test]
fn display_format_missing_required() {
let diag = StructureDiagnostic {
kind: StructureDiagnosticKind::MissingRequiredSegment,
segment_id: "IDE".to_string(),
position: 5,
message: "mandatory IDE segment missing in SG4".to_string(),
};
let display = diag.to_string();
assert_eq!(
display,
"[MissingRequiredSegment] IDE at position 5: mandatory IDE segment missing in SG4"
);
}
#[test]
fn serialization_roundtrip() {
let diag = StructureDiagnostic {
kind: StructureDiagnosticKind::MaxRepetitionsExceeded,
segment_id: "RFF".to_string(),
position: 12,
message: "RFF exceeded max repetitions of 5".to_string(),
};
let json = serde_json::to_string(&diag).expect("serialize");
let roundtripped: StructureDiagnostic = serde_json::from_str(&json).expect("deserialize");
assert_eq!(
roundtripped.kind,
StructureDiagnosticKind::MaxRepetitionsExceeded
);
assert_eq!(roundtripped.segment_id, "RFF");
assert_eq!(roundtripped.position, 12);
assert_eq!(roundtripped.message, "RFF exceeded max repetitions of 5");
}
#[test]
fn serialization_roundtrip_unrecognized_qualifier() {
let diag = StructureDiagnostic {
kind: StructureDiagnosticKind::UnrecognizedQualifier,
segment_id: "LOC".to_string(),
position: 7,
message: "qualifier Z99 not recognized for LOC in SG5".to_string(),
};
let json = serde_json::to_string(&diag).expect("serialize");
let roundtripped: StructureDiagnostic = serde_json::from_str(&json).expect("deserialize");
assert_eq!(
roundtripped.kind,
StructureDiagnosticKind::UnrecognizedQualifier
);
assert_eq!(roundtripped.segment_id, "LOC");
assert_eq!(roundtripped.position, 7);
assert_eq!(
roundtripped.message,
"qualifier Z99 not recognized for LOC in SG5"
);
}
}