Skip to main content

mig_types/schema/
mig.rs

1use serde::{Deserialize, Serialize};
2
3use super::common::{Cardinality, CodeDefinition};
4
5/// Complete MIG schema for a message type.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct MigSchema {
8    /// The EDIFACT message type (e.g., "UTILMD", "ORDERS").
9    pub message_type: String,
10    /// Optional variant (e.g., "Strom", "Gas").
11    pub variant: Option<String>,
12    /// Version number from the MIG (e.g., "S2.1", "1.4a").
13    pub version: String,
14    /// Publication date string.
15    pub publication_date: String,
16    /// Author (typically "BDEW").
17    pub author: String,
18    /// Format version directory (e.g., "FV2504").
19    pub format_version: String,
20    /// Path to the source XML file.
21    pub source_file: String,
22    /// Top-level segment definitions (not in groups).
23    pub segments: Vec<MigSegment>,
24    /// Segment group definitions (contain more segments).
25    pub segment_groups: Vec<MigSegmentGroup>,
26}
27
28impl MigSchema {
29    /// Whether this MIG includes the interchange envelope (UNA/UNB) as
30    /// top-level segments. UTILMD/MSCONS/INVOIC/REMADV do; UTILTS/PRICAT/
31    /// ORDERS/COMDIS start at UNH.
32    ///
33    /// Callers that assemble from raw EDIFACT need this to decide whether
34    /// to feed `MessageChunk::all_segments()` (envelope + UNH + body + UNT)
35    /// or `MessageChunk::message_segments()` (UNH + body + UNT) to the
36    /// assembler — feeding envelope segments to a UNH-start MIG aborts
37    /// assembly at the first segment.
38    pub fn includes_envelope(&self) -> bool {
39        self.segments
40            .iter()
41            .any(|s| s.id == "UNA" || s.id == "UNB")
42    }
43}
44
45/// A segment (S_*) definition from the MIG.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct MigSegment {
48    /// Segment identifier (e.g., "UNH", "BGM", "NAD").
49    pub id: String,
50    /// Human-readable name.
51    pub name: String,
52    /// Description of the segment.
53    pub description: Option<String>,
54    /// Position counter (e.g., "0010", "0020").
55    pub counter: Option<String>,
56    /// Nesting level (0=root, 1=first level, etc.).
57    pub level: i32,
58    /// Sequence number within the message.
59    pub number: Option<String>,
60    /// Standard maximum repetitions.
61    pub max_rep_std: i32,
62    /// Specification maximum repetitions.
63    pub max_rep_spec: i32,
64    /// Standard status (M=Mandatory, C=Conditional, etc.).
65    pub status_std: Option<String>,
66    /// Specification status (M, R, D, O, N).
67    pub status_spec: Option<String>,
68    /// Example EDIFACT string.
69    pub example: Option<String>,
70    /// Direct child data elements.
71    pub data_elements: Vec<MigDataElement>,
72    /// Child composite elements.
73    pub composites: Vec<MigComposite>,
74}
75
76impl MigSegment {
77    /// Returns the effective cardinality based on spec or std status.
78    pub fn cardinality(&self) -> Cardinality {
79        let status = self
80            .status_spec
81            .as_deref()
82            .or(self.status_std.as_deref())
83            .unwrap_or("C");
84        Cardinality::from_status(status)
85    }
86
87    /// Returns the effective max repetitions (spec overrides std).
88    pub fn max_rep(&self) -> i32 {
89        self.max_rep_spec.max(self.max_rep_std)
90    }
91}
92
93/// A segment group (G_SG*) definition from the MIG.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct MigSegmentGroup {
96    /// Group identifier (e.g., "SG1", "SG2", "SG10").
97    pub id: String,
98    /// Human-readable name.
99    pub name: String,
100    /// Description of the segment group.
101    pub description: Option<String>,
102    /// Position counter (e.g., "0070", "0500").
103    pub counter: Option<String>,
104    /// Nesting level.
105    pub level: i32,
106    /// Standard maximum repetitions.
107    pub max_rep_std: i32,
108    /// Specification maximum repetitions.
109    pub max_rep_spec: i32,
110    /// Standard status.
111    pub status_std: Option<String>,
112    /// Specification status.
113    pub status_spec: Option<String>,
114    /// Segments directly in this group.
115    pub segments: Vec<MigSegment>,
116    /// Nested segment groups.
117    pub nested_groups: Vec<MigSegmentGroup>,
118    /// Optional variant qualifier code for the entry segment.
119    /// When set, the assembler only matches segments whose entry qualifier
120    /// equals this code (e.g., "Z98" for SEQ+Z98, "ZD5" for SEQ+ZD5).
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub variant_code: Option<String>,
123    /// Position of the variant qualifier in the entry segment:
124    /// (element_index, component_index). Defaults to (0, 0) when absent.
125    /// Some segments have the qualifier in a composite at a non-zero position
126    /// (e.g., CCI with qualifier in C240/D7037 at element index 2).
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub variant_qualifier_position: Option<(usize, usize)>,
129    /// All allowed qualifier codes for this variant (assembler matches ANY).
130    #[serde(default)]
131    pub variant_codes: Vec<String>,
132    /// Number of MIG XML variants that were merged into this group definition.
133    /// When multiple SG2 definitions (MS, MR, DP, etc.) are merged into one,
134    /// this holds the count of merged variants. Used to compute the correct
135    /// max_reps for PID schema generation (variant count, not max of individual max_reps).
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub merged_variant_count: Option<u32>,
138}
139
140impl MigSegmentGroup {
141    /// Returns the effective cardinality.
142    pub fn cardinality(&self) -> Cardinality {
143        let status = self
144            .status_spec
145            .as_deref()
146            .or(self.status_std.as_deref())
147            .unwrap_or("C");
148        Cardinality::from_status(status)
149    }
150}
151
152/// A composite element (C_*) definition from the MIG.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct MigComposite {
155    /// Composite identifier (e.g., "S009", "C002").
156    pub id: String,
157    /// Human-readable name.
158    pub name: String,
159    /// Description.
160    pub description: Option<String>,
161    /// Standard status.
162    pub status_std: Option<String>,
163    /// Specification status.
164    pub status_spec: Option<String>,
165    /// Child data elements within this composite.
166    pub data_elements: Vec<MigDataElement>,
167    /// Position of this composite within its parent segment (0-based).
168    pub position: usize,
169}
170
171/// A data element (D_*) definition from the MIG.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct MigDataElement {
174    /// Element identifier (e.g., "0062", "3035").
175    pub id: String,
176    /// Human-readable name.
177    pub name: String,
178    /// Description.
179    pub description: Option<String>,
180    /// Standard status.
181    pub status_std: Option<String>,
182    /// Specification status.
183    pub status_spec: Option<String>,
184    /// Standard format (e.g., "an..14", "n13").
185    pub format_std: Option<String>,
186    /// Specification format.
187    pub format_spec: Option<String>,
188    /// Allowed code values, if restricted.
189    pub codes: Vec<CodeDefinition>,
190    /// Position within parent (0-based).
191    pub position: usize,
192}