Skip to main content

mig_assembly/
diagnostic.rs

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