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}
32
33impl std::fmt::Display for StructureDiagnostic {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(
36            f,
37            "[{:?}] {} at position {}: {}",
38            self.kind, self.segment_id, self.position, self.message
39        )
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn display_format_includes_all_fields() {
49        let diag = StructureDiagnostic {
50            kind: StructureDiagnosticKind::UnexpectedSegment,
51            segment_id: "BGM".to_string(),
52            position: 3,
53            message: "BGM not expected after UNH".to_string(),
54        };
55        let display = diag.to_string();
56        assert!(display.contains("BGM"), "display should contain segment_id");
57        assert!(display.contains("3"), "display should contain position");
58        assert!(
59            display.contains("BGM not expected after UNH"),
60            "display should contain message"
61        );
62        assert!(
63            display.contains("UnexpectedSegment"),
64            "display should contain kind"
65        );
66    }
67
68    #[test]
69    fn display_format_missing_required() {
70        let diag = StructureDiagnostic {
71            kind: StructureDiagnosticKind::MissingRequiredSegment,
72            segment_id: "IDE".to_string(),
73            position: 5,
74            message: "mandatory IDE segment missing in SG4".to_string(),
75        };
76        let display = diag.to_string();
77        assert_eq!(
78            display,
79            "[MissingRequiredSegment] IDE at position 5: mandatory IDE segment missing in SG4"
80        );
81    }
82
83    #[test]
84    fn serialization_roundtrip() {
85        let diag = StructureDiagnostic {
86            kind: StructureDiagnosticKind::MaxRepetitionsExceeded,
87            segment_id: "RFF".to_string(),
88            position: 12,
89            message: "RFF exceeded max repetitions of 5".to_string(),
90        };
91        let json = serde_json::to_string(&diag).expect("serialize");
92        let roundtripped: StructureDiagnostic = serde_json::from_str(&json).expect("deserialize");
93        assert_eq!(
94            roundtripped.kind,
95            StructureDiagnosticKind::MaxRepetitionsExceeded
96        );
97        assert_eq!(roundtripped.segment_id, "RFF");
98        assert_eq!(roundtripped.position, 12);
99        assert_eq!(roundtripped.message, "RFF exceeded max repetitions of 5");
100    }
101
102    #[test]
103    fn serialization_roundtrip_unrecognized_qualifier() {
104        let diag = StructureDiagnostic {
105            kind: StructureDiagnosticKind::UnrecognizedQualifier,
106            segment_id: "LOC".to_string(),
107            position: 7,
108            message: "qualifier Z99 not recognized for LOC in SG5".to_string(),
109        };
110        let json = serde_json::to_string(&diag).expect("serialize");
111        let roundtripped: StructureDiagnostic = serde_json::from_str(&json).expect("deserialize");
112        assert_eq!(
113            roundtripped.kind,
114            StructureDiagnosticKind::UnrecognizedQualifier
115        );
116        assert_eq!(roundtripped.segment_id, "LOC");
117        assert_eq!(roundtripped.position, 7);
118        assert_eq!(
119            roundtripped.message,
120            "qualifier Z99 not recognized for LOC in SG5"
121        );
122    }
123}