1use mig_assembly::StructureDiagnosticKind;
7use serde::{Deserialize, Serialize};
8
9use super::issue::ValidationCategory;
10
11#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
15pub struct UnresolvedConditions {
16 pub external: Vec<u32>,
18 pub undetermined: Vec<u32>,
20 pub missing: Vec<u32>,
22}
23
24impl UnresolvedConditions {
25 pub fn is_empty(&self) -> bool {
27 self.external.is_empty() && self.undetermined.is_empty() && self.missing.is_empty()
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(tag = "type", rename_all = "camelCase")]
38pub enum IssueKind {
39 MissingRequiredField { field_name: String },
41 GroupNotAllowed { group: String },
44 SegmentNotAllowed { segment: String },
48 FieldConditionNotSatisfied {
53 field_name: String,
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
55 failed: Vec<u32>,
56 },
57 ConditionUnknown {
59 field_name: String,
60 unresolved: UnresolvedConditions,
61 },
62 PackageCardinality {
64 package_id: String,
65 present: usize,
66 min: usize,
67 max: usize,
68 codes: Vec<String>,
69 },
70 RepeatedInTransaction { path: String, count: usize },
74 RepetitionCount { path: String, count: usize },
81 CodeNotAllowedForPid { value: String, allowed: Vec<String> },
83 AnswerNotInList {
88 answer: String,
89 list: String,
90 allowed: Vec<String>,
91 },
92 ValueTooLong {
94 field_name: String,
95 length: usize,
96 max: usize,
97 },
98 ValueNotNumeric { field_name: String },
101 UntSegmentCountMismatch { declared: usize, actual: usize },
103 UntCountNotVerifiable { unh_count: usize },
105 StructureDiagnostic {
112 kind: StructureDiagnosticKind,
113 segment_id: String,
114 position: usize,
115 detail: String,
116 },
117}
118
119impl IssueKind {
120 pub fn code(&self) -> &'static str {
122 match self {
123 Self::MissingRequiredField { .. } => "AHB001",
124 Self::GroupNotAllowed { .. } | Self::SegmentNotAllowed { .. } => "AHB002",
125 Self::FieldConditionNotSatisfied { .. } => "AHB003",
126 Self::ConditionUnknown { .. } => "AHB005",
127 Self::PackageCardinality { .. } => "AHB006",
128 Self::RepeatedInTransaction { .. } => "AHB007",
129 Self::RepetitionCount { .. } => "AHB008",
130 Self::CodeNotAllowedForPid { .. } => "COD002",
131 Self::AnswerNotInList { .. } => "COD003",
132 Self::ValueTooLong { .. } => "FMT001",
133 Self::ValueNotNumeric { .. } => "FMT002",
134 Self::UntSegmentCountMismatch { .. } | Self::UntCountNotVerifiable { .. } => "STR007",
135 Self::StructureDiagnostic { kind, .. } => match kind {
140 StructureDiagnosticKind::MissingRequiredSegment => "STR001",
141 StructureDiagnosticKind::MaxRepetitionsExceeded => "STR002",
142 StructureDiagnosticKind::UnexpectedSegment
143 | StructureDiagnosticKind::UnrecognizedQualifier => "STR003",
144 StructureDiagnosticKind::MissingRequiredGroup => "STR005",
145 StructureDiagnosticKind::GroupMaxRepetitionsExceeded => "STR006",
146 StructureDiagnosticKind::SkippedUnknownSegment
147 | StructureDiagnosticKind::OrphanedGroupSegment => "STR008",
148 },
149 }
150 }
151
152 pub fn category(&self) -> ValidationCategory {
154 match self {
155 Self::MissingRequiredField { .. }
156 | Self::GroupNotAllowed { .. }
157 | Self::SegmentNotAllowed { .. }
158 | Self::FieldConditionNotSatisfied { .. }
159 | Self::ConditionUnknown { .. }
160 | Self::PackageCardinality { .. }
161 | Self::RepeatedInTransaction { .. }
162 | Self::RepetitionCount { .. } => ValidationCategory::Ahb,
163 Self::CodeNotAllowedForPid { .. } | Self::AnswerNotInList { .. } => {
164 ValidationCategory::Code
165 }
166 Self::UntSegmentCountMismatch { .. }
167 | Self::UntCountNotVerifiable { .. }
168 | Self::StructureDiagnostic { .. } => ValidationCategory::Structure,
169 Self::ValueTooLong { .. } | Self::ValueNotNumeric { .. } => ValidationCategory::Format,
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use mig_assembly::StructureDiagnosticKind;
178
179 #[test]
180 fn code_matches_the_legacy_error_code_for_every_kind() {
181 let cases: Vec<(IssueKind, &str)> = vec![
182 (
183 IssueKind::MissingRequiredField {
184 field_name: "f".into(),
185 },
186 "AHB001",
187 ),
188 (
189 IssueKind::GroupNotAllowed {
190 group: "SG4/SG5".into(),
191 },
192 "AHB002",
193 ),
194 (
195 IssueKind::SegmentNotAllowed {
196 segment: "SG4/SG8/SG10/CAV".into(),
197 },
198 "AHB002",
199 ),
200 (
201 IssueKind::FieldConditionNotSatisfied {
202 field_name: "f".into(),
203 failed: vec![],
204 },
205 "AHB003",
206 ),
207 (
208 IssueKind::ConditionUnknown {
209 field_name: "f".into(),
210 unresolved: UnresolvedConditions::default(),
211 },
212 "AHB005",
213 ),
214 (
215 IssueKind::PackageCardinality {
216 package_id: "1P0..1".into(),
217 present: 2,
218 min: 0,
219 max: 1,
220 codes: vec![],
221 },
222 "AHB006",
223 ),
224 (
225 IssueKind::RepetitionCount {
226 path: "SG29/SG34".into(),
227 count: 2,
228 },
229 "AHB008",
230 ),
231 (
232 IssueKind::CodeNotAllowedForPid {
233 value: "X".into(),
234 allowed: vec![],
235 },
236 "COD002",
237 ),
238 (
239 IssueKind::AnswerNotInList {
240 answer: "ZB5".into(),
241 list: "G_0059".into(),
242 allowed: vec![],
243 },
244 "COD003",
245 ),
246 (
247 IssueKind::ValueTooLong {
248 field_name: "f".into(),
249 length: 600,
250 max: 512,
251 },
252 "FMT001",
253 ),
254 (
255 IssueKind::UntSegmentCountMismatch {
256 declared: 30,
257 actual: 31,
258 },
259 "STR007",
260 ),
261 (IssueKind::UntCountNotVerifiable { unh_count: 2 }, "STR007"),
262 (
263 IssueKind::StructureDiagnostic {
264 kind: StructureDiagnosticKind::SkippedUnknownSegment,
265 segment_id: "CCI".into(),
266 position: 15,
267 detail: "irrelevant to the code".into(),
268 },
269 "STR008",
270 ),
271 (
272 IssueKind::StructureDiagnostic {
273 kind: StructureDiagnosticKind::UnexpectedSegment,
274 segment_id: "CCI".into(),
275 position: 15,
276 detail: "irrelevant to the code".into(),
277 },
278 "STR003",
279 ),
280 ];
281 for (kind, expected) in cases {
282 assert_eq!(kind.code(), expected, "wrong code for {kind:?}");
283 }
284 }
285
286 #[test]
289 fn each_structure_kind_has_its_own_code() {
290 use StructureDiagnosticKind as K;
291 for (kind, expected) in [
292 (K::MissingRequiredSegment, "STR001"),
293 (K::MaxRepetitionsExceeded, "STR002"),
294 (K::UnexpectedSegment, "STR003"),
295 (K::UnrecognizedQualifier, "STR003"),
296 (K::MissingRequiredGroup, "STR005"),
297 (K::GroupMaxRepetitionsExceeded, "STR006"),
298 (K::SkippedUnknownSegment, "STR008"),
299 (K::OrphanedGroupSegment, "STR008"),
300 ] {
301 let issue = IssueKind::StructureDiagnostic {
302 kind,
303 segment_id: "RFF".into(),
304 position: 1,
305 detail: String::new(),
306 };
307 assert_eq!(issue.code(), expected, "{kind:?}");
308 }
309 }
310
311 #[test]
312 fn category_is_derived_from_the_kind() {
313 assert_eq!(
314 IssueKind::MissingRequiredField {
315 field_name: "f".into()
316 }
317 .category(),
318 ValidationCategory::Ahb
319 );
320 assert_eq!(
321 IssueKind::CodeNotAllowedForPid {
322 value: "X".into(),
323 allowed: vec![]
324 }
325 .category(),
326 ValidationCategory::Code
327 );
328 assert_eq!(
329 IssueKind::UntSegmentCountMismatch {
330 declared: 1,
331 actual: 2
332 }
333 .category(),
334 ValidationCategory::Structure
335 );
336 }
337}