1use crate::{DiagnosticCode, InvalidDiagnosticCode, Severity};
4
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7#[repr(u32)]
8#[non_exhaustive]
9#[allow(
10 missing_docs,
11 reason = "each variant is documented by its public catalogue definition"
12)]
13pub enum RuleId {
14 ComposeModelInvalid = 1_000_001,
15 ComposeProfileRequired = 1_000_002,
16 ComposeProfileMismatch = 1_000_003,
17 ComposeIntentUnsupported = 1_000_004,
18 ComposeValueInvalid = 1_000_005,
19 ComposeTargetInvalid = 1_000_006,
20 ComposeOutputUnsupported = 1_000_007,
21 ComposeGenerationFailed = 1_000_008,
22 ComposeCompatibilityConstraint = 1_000_009,
23 ComposeUnsetVariable = 1_000_101,
24 ComposeRequiredVariable = 1_000_102,
25 ComposeInterpolationInvalid = 1_000_103,
26 ComposeInterpolationNestingLimit = 1_000_104,
27 ComposeUnresolvedVariable = 1_000_105,
28 ComposeNativeError = 1_000_197,
29 ComposeNativeWarning = 1_000_198,
30 ComposeNativeNote = 1_000_199,
31 OrchestrationFailed = 3_001_000,
32 InputReadFailed = 3_001_001,
33 ComposeProjectRootInvalid = 3_001_002,
34 PodmanTargetSelectionInvalid = 3_001_003,
35 InterpolationInputInvalid = 3_001_004,
36 ComposeLoadFailed = 3_001_005,
37 ConversionFailed = 3_001_006,
38 QuadletParseFailed = 3_001_007,
39 OutputDirectoryNotEmpty = 3_002_001,
40 OutputPathInvalid = 3_002_002,
41 OutputWriteFailed = 3_002_003,
42 ReportWriteFailed = 3_003_001,
43 SupportBundleWriteFailed = 3_003_002,
44 PodmanSourceInvalid = 4_000_001,
45 PodmanInputUnmodelled = 4_000_002,
46 PodmanPromotionRequired = 4_000_003,
47 PodmanIdentityConflict = 4_000_004,
48 PodmanSecretGrantIncomplete = 4_000_005,
49 PodmanTargetInvalid = 4_000_006,
50 PodmanOutputUnsupported = 4_000_007,
51 PodmanPlanningFailed = 4_000_008,
52 QuadletTargetInvalid = 5_000_001,
53 QuadletOpenEndedTarget = 5_000_002,
54 QuadletOutputUnsupported = 5_000_003,
55 QuadletValueInvalid = 5_000_004,
56 QuadletGenerationFailed = 5_000_005,
57 QuadletCapabilityUnavailable = 5_000_006,
58 QuadletGroupingApproximation = 5_000_007,
59 QuadletDependencyUnsupported = 5_000_008,
60 QuadletRestartApproximation = 5_000_009,
61 QuadletEnvironmentFileApproximation = 5_000_010,
62 QuadletGroupingInvalid = 5_000_011,
63 QuadletDependencyInvalid = 5_000_012,
64 QuadletCapabilityDeprecated = 5_000_013,
65 QuadletUnresolvedVariable = 5_000_014,
66 QuadletSourceInvalid = 5_001_001,
67 QuadletModelInvalid = 5_001_002,
68 QuadletInputUnsupported = 5_001_003,
69 QuadletInputApproximation = 5_001_004,
70 QuadletNativeSyntax = 5_001_101,
71 QuadletNativeModel = 5_001_102,
72 QuadletNativeDocumentSet = 5_001_103,
73 QuadletNativeFailure = 5_001_104,
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub struct DiagnosticRule {
79 id: RuleId,
80 code: &'static str,
81 name: &'static str,
82 default_severity: Severity,
83 description: &'static str,
84 help: &'static str,
85}
86
87impl DiagnosticRule {
88 #[must_use]
90 pub const fn id(self) -> RuleId {
91 self.id
92 }
93
94 #[must_use]
96 pub const fn code(self) -> &'static str {
97 self.code
98 }
99
100 #[must_use]
102 pub const fn name(self) -> &'static str {
103 self.name
104 }
105
106 #[must_use]
108 pub const fn default_severity(self) -> Severity {
109 self.default_severity
110 }
111
112 #[must_use]
114 pub fn owner(self) -> &'static str {
115 match self.code.as_bytes().get(..3) {
116 Some(b"BFC") => "Compose adapter",
117 Some(b"BFO") => "BoxFerry orchestration",
118 Some(b"BFP") => "Podman adapter",
119 Some(b"BFQ") => "Quadlet adapter",
120 _ => "BoxFerry engine",
121 }
122 }
123
124 #[must_use]
126 pub const fn description(self) -> &'static str {
127 self.description
128 }
129
130 #[must_use]
132 pub const fn help(self) -> &'static str {
133 self.help
134 }
135
136 pub fn diagnostic_code(self) -> Result<DiagnosticCode, InvalidDiagnosticCode> {
142 DiagnosticCode::new(self.code)
143 }
144}
145
146macro_rules! rule {
147 ($id:ident, $code:literal, $name:literal, $severity:ident, $description:literal, $help:literal) => {
148 DiagnosticRule {
149 id: RuleId::$id,
150 code: $code,
151 name: $name,
152 default_severity: Severity::$severity,
153 description: $description,
154 help: $help,
155 }
156 };
157}
158
159pub const RULES: &[DiagnosticRule] = &[
161 rule!(
162 ComposeModelInvalid,
163 "BFC0001",
164 "compose-model-invalid",
165 Error,
166 "Compose intent could not be represented by the neutral application model.",
167 "Correct the reported Compose value or remove the conflicting declaration."
168 ),
169 rule!(
170 ComposeProfileRequired,
171 "BFC0002",
172 "compose-profile-selection-required",
173 Error,
174 "Profiled Compose services require an explicit profile selection.",
175 "Select profiles explicitly with --profile, or use --all-profiles only after reviewing mutually exclusive services."
176 ),
177 rule!(
178 ComposeProfileMismatch,
179 "BFC0003",
180 "compose-profile-selection-mismatch",
181 Error,
182 "The Compose profile selection is invalid or belongs to another merged project.",
183 "Recreate the profile selection from the same merged Compose project."
184 ),
185 rule!(
186 ComposeIntentUnsupported,
187 "BFC0004",
188 "compose-intent-unsupported",
189 Warning,
190 "Compose intent is not represented by the current neutral-model subset.",
191 "Review the named feature; use --loss-policy partial only when omitting that intent is acceptable."
192 ),
193 rule!(
194 ComposeValueInvalid,
195 "BFC0005",
196 "compose-value-invalid",
197 Error,
198 "A Compose value cannot be represented safely in the neutral model.",
199 "Correct the reported value before converting again."
200 ),
201 rule!(
202 ComposeTargetInvalid,
203 "BFC0006",
204 "compose-target-invalid",
205 Error,
206 "The requested Compose output target is invalid.",
207 "Select a supported Compose target profile and compatible version inputs."
208 ),
209 rule!(
210 ComposeOutputUnsupported,
211 "BFC0007",
212 "compose-output-unsupported",
213 Warning,
214 "Neutral application intent is not represented in generated Compose output.",
215 "Review the named subject; use --loss-policy partial only when the omitted intent is acceptable."
216 ),
217 rule!(
218 ComposeGenerationFailed,
219 "BFC0008",
220 "compose-generation-failed",
221 Error,
222 "ComposeLens rejected generated Compose output.",
223 "Correct the reported neutral value or target choice and retry."
224 ),
225 rule!(
226 ComposeCompatibilityConstraint,
227 "BFC0009",
228 "compose-compatibility-constraint",
229 Warning,
230 "Generated Compose syntax has a target-specific compatibility constraint.",
231 "Review the target-specific classification before authorizing non-exact output."
232 ),
233 rule!(
234 ComposeUnsetVariable,
235 "BFC0101",
236 "compose-unset-variable",
237 Warning,
238 "A Compose interpolation variable is not set.",
239 "Provide the missing value with --env-file FILE or --env NAME=VALUE."
240 ),
241 rule!(
242 ComposeRequiredVariable,
243 "BFC0102",
244 "compose-required-variable",
245 Error,
246 "A required Compose interpolation variable is not set.",
247 "Provide the required value with --env-file FILE or --env NAME=VALUE."
248 ),
249 rule!(
250 ComposeInterpolationInvalid,
251 "BFC0103",
252 "compose-interpolation-invalid",
253 Error,
254 "A Compose interpolation expression is invalid.",
255 "Correct the interpolation expression and retry."
256 ),
257 rule!(
258 ComposeInterpolationNestingLimit,
259 "BFC0104",
260 "compose-interpolation-nesting-limit",
261 Error,
262 "A Compose interpolation expression exceeds the supported nesting limit.",
263 "Simplify the interpolation expression before converting."
264 ),
265 rule!(
266 ComposeUnresolvedVariable,
267 "BFC0105",
268 "compose-unresolved-variable",
269 Warning,
270 "A Compose variable expression remains unresolved at the adapter boundary.",
271 "Use --interpolate and provide missing values with --env-file FILE or --env NAME=VALUE; partial authorization applies only when the affected intent can be omitted."
272 ),
273 rule!(
274 ComposeNativeError,
275 "BFC0197",
276 "compose-native-error",
277 Error,
278 "ComposeLens reported a native Compose error.",
279 "Use the retained source code and source span to correct the Compose document."
280 ),
281 rule!(
282 ComposeNativeWarning,
283 "BFC0198",
284 "compose-native-warning",
285 Warning,
286 "ComposeLens reported a native Compose warning.",
287 "Review the retained source code and source span before accepting the document."
288 ),
289 rule!(
290 ComposeNativeNote,
291 "BFC0199",
292 "compose-native-note",
293 Note,
294 "ComposeLens reported native Compose information.",
295 "Review the retained source code when more context is needed."
296 ),
297 rule!(
298 OrchestrationFailed,
299 "BFO1000",
300 "orchestration-failed",
301 Error,
302 "BoxFerry could not complete the requested orchestration stage.",
303 "Review the reason and correct the command inputs before retrying."
304 ),
305 rule!(
306 InputReadFailed,
307 "BFO1001",
308 "input-read-failed",
309 Error,
310 "An explicitly selected input could not be read.",
311 "Check that the input exists, is a readable regular file, and is not a symlink."
312 ),
313 rule!(
314 ComposeProjectRootInvalid,
315 "BFO1002",
316 "compose-project-root-invalid",
317 Error,
318 "The Compose project root could not be resolved.",
319 "Provide an existing absolute --project-directory or correct the input location."
320 ),
321 rule!(
322 PodmanTargetSelectionInvalid,
323 "BFO1003",
324 "podman-target-selection-invalid",
325 Error,
326 "The requested Podman target range could not be resolved.",
327 "Use major.minor or major.minor.patch values within the reviewed range."
328 ),
329 rule!(
330 InterpolationInputInvalid,
331 "BFO1004",
332 "interpolation-input-invalid",
333 Error,
334 "Compose interpolation inputs could not be resolved safely.",
335 "Correct the named --env-file or --env input and retry."
336 ),
337 rule!(
338 ComposeLoadFailed,
339 "BFO1005",
340 "compose-load-failed",
341 Error,
342 "Compose input could not be loaded or merged.",
343 "Correct the reported Compose source diagnostic before retrying."
344 ),
345 rule!(
346 ConversionFailed,
347 "BFO1006",
348 "conversion-failed",
349 Error,
350 "Source import or target conversion failed.",
351 "Correct the reported rule occurrences before retrying."
352 ),
353 rule!(
354 QuadletParseFailed,
355 "BFO1007",
356 "quadlet-parse-failed",
357 Error,
358 "Quadlet input could not be parsed into the native document boundary.",
359 "Correct the retained native diagnostics before retrying."
360 ),
361 rule!(
362 OutputDirectoryNotEmpty,
363 "BFO2001",
364 "output-directory-not-empty",
365 Error,
366 "The selected output directory is not empty.",
367 "Empty the selected --output-directory or choose a new path."
368 ),
369 rule!(
370 OutputPathInvalid,
371 "BFO2002",
372 "output-path-invalid",
373 Error,
374 "The selected output path is not a usable non-symlink directory.",
375 "Choose an absent path or an existing empty, non-symlink directory."
376 ),
377 rule!(
378 OutputWriteFailed,
379 "BFO2003",
380 "output-write-failed",
381 Error,
382 "Generated output could not be written safely.",
383 "Check parent-directory existence, permissions, free space, and path conflicts."
384 ),
385 rule!(
386 ReportWriteFailed,
387 "BFO3001",
388 "report-write-failed",
389 Error,
390 "The structured report file could not be written safely.",
391 "Choose a new writable --report-file path."
392 ),
393 rule!(
394 SupportBundleWriteFailed,
395 "BFO3002",
396 "support-bundle-write-failed",
397 Error,
398 "The diagnostic support bundle could not be written safely.",
399 "Choose a writable --error-report-directory with space for a new archive."
400 ),
401 rule!(
402 PodmanSourceInvalid,
403 "BFP0001",
404 "podman-source-invalid",
405 Error,
406 "Inspected Podman evidence is malformed, incomplete, or unavailable for a required mapping.",
407 "Correct the selected Podman resources or selectors, then acquire a complete read-only inventory again."
408 ),
409 rule!(
410 PodmanInputUnmodelled,
411 "BFP0002",
412 "podman-input-unmodelled",
413 Warning,
414 "Inspected Podman intent is not represented by the current neutral application model.",
415 "Review the named field; use --loss-policy partial only when omitting that intent is acceptable."
416 ),
417 rule!(
418 PodmanPromotionRequired,
419 "BFP0003",
420 "podman-promotion-required",
421 Warning,
422 "Effective or locally resolved Podman evidence requires an explicit promotion decision.",
423 "Choose an explicit promotion policy only after confirming the observed value is portable desired state."
424 ),
425 rule!(
426 PodmanIdentityConflict,
427 "BFP0004",
428 "podman-identity-conflict",
429 Error,
430 "Podman resource identities or references conflict in the neutral namespace.",
431 "Narrow the selectors or rename the conflicting resources before importing again."
432 ),
433 rule!(
434 PodmanSecretGrantIncomplete,
435 "BFP0005",
436 "podman-secret-grant-incomplete",
437 Warning,
438 "Podman inspection lacks authored secret-delivery information required for a portable grant.",
439 "Author the secret delivery form and target separately; inspection metadata alone is insufficient."
440 ),
441 rule!(
442 PodmanTargetInvalid,
443 "BFP0006",
444 "podman-target-invalid",
445 Error,
446 "The requested Podman output target is outside the reviewed exact version and execution-context catalogue.",
447 "Select a reviewed --podman-max-version and state the target rootful, rootless, or unknown context explicitly."
448 ),
449 rule!(
450 PodmanOutputUnsupported,
451 "BFP0007",
452 "podman-output-unsupported",
453 Warning,
454 "Neutral application intent is not represented by the current Podman deployment subset.",
455 "Review the named subject; use --loss-policy partial only when the omitted intent is acceptable."
456 ),
457 rule!(
458 PodmanPlanningFailed,
459 "BFP0008",
460 "podman-planning-failed",
461 Error,
462 "PodmanLens could not produce or render a complete inert deployment plan.",
463 "Review retained PodmanLens findings, correct the neutral intent or target, and retry."
464 ),
465 rule!(
466 QuadletTargetInvalid,
467 "BFQ0001",
468 "quadlet-target-invalid",
469 Error,
470 "The requested Quadlet target or Podman range is invalid.",
471 "Select the podman target and a version range covered by QuadletLens."
472 ),
473 rule!(
474 QuadletOpenEndedTarget,
475 "BFQ0002",
476 "quadlet-open-ended-target",
477 Note,
478 "An omitted Podman maximum extends beyond verified compatibility evidence.",
479 "Set --podman-maximum-version for a finite compatibility claim."
480 ),
481 rule!(
482 QuadletOutputUnsupported,
483 "BFQ0003",
484 "quadlet-output-unsupported",
485 Warning,
486 "Neutral application intent is not represented by the current Quadlet subset.",
487 "Review the named subject; use --loss-policy partial only when omission is acceptable."
488 ),
489 rule!(
490 QuadletValueInvalid,
491 "BFQ0004",
492 "quadlet-value-invalid",
493 Error,
494 "A neutral value cannot be emitted safely as native Quadlet syntax.",
495 "Correct or explicitly map the reported value before converting again."
496 ),
497 rule!(
498 QuadletGenerationFailed,
499 "BFQ0005",
500 "quadlet-generation-failed",
501 Error,
502 "QuadletLens rejected generated native output.",
503 "Correct the reported value or dependency and retry."
504 ),
505 rule!(
506 QuadletCapabilityUnavailable,
507 "BFQ0006",
508 "quadlet-capability-unavailable",
509 Warning,
510 "A required Quadlet capability is unavailable for part of the target range.",
511 "Narrow the Podman target range or accept the documented partial result."
512 ),
513 rule!(
514 QuadletGroupingApproximation,
515 "BFQ0007",
516 "quadlet-grouping-approximation",
517 Warning,
518 "Generated Quadlet grouping changes source service isolation.",
519 "Use --quadlet-grouping pod only when the shared-pod semantics are acceptable."
520 ),
521 rule!(
522 QuadletDependencyUnsupported,
523 "BFQ0008",
524 "quadlet-dependency-unsupported",
525 Warning,
526 "A service dependency cannot be represented exactly by Quadlet and systemd.",
527 "Review the dependency condition and complete unsupported behavior manually."
528 ),
529 rule!(
530 QuadletRestartApproximation,
531 "BFQ0009",
532 "quadlet-restart-policy-approximation",
533 Warning,
534 "Container restart behavior is approximated by the systemd service manager.",
535 "Use --loss-policy approximate only after accepting the documented systemd behavior difference."
536 ),
537 rule!(
538 QuadletEnvironmentFileApproximation,
539 "BFQ0010",
540 "quadlet-environment-file-approximation",
541 Warning,
542 "Environment-file parsing is delegated to Podman.",
543 "Verify the environment file with the target Podman version before deployment."
544 ),
545 rule!(
546 QuadletGroupingInvalid,
547 "BFQ0011",
548 "quadlet-grouping-invalid",
549 Error,
550 "The requested Quadlet grouping cannot preserve the application topology.",
551 "Keep separate containers or correct the reported grouping conflict."
552 ),
553 rule!(
554 QuadletDependencyInvalid,
555 "BFQ0012",
556 "quadlet-dependency-invalid",
557 Error,
558 "The service or image-artifact dependency graph is invalid.",
559 "Correct missing or cyclic dependencies before converting."
560 ),
561 rule!(
562 QuadletCapabilityDeprecated,
563 "BFQ0013",
564 "quadlet-capability-deprecated",
565 Note,
566 "A required Quadlet capability is deprecated for part of the target range.",
567 "Review the target range and plan migration away from the deprecated capability."
568 ),
569 rule!(
570 QuadletUnresolvedVariable,
571 "BFQ0014",
572 "quadlet-unresolved-source-variable",
573 Error,
574 "A source variable expression cannot be emitted as a Quadlet value.",
575 "Resolve source variables before conversion; for Compose input, use --interpolate and provide missing values with --env-file FILE or --env NAME=VALUE."
576 ),
577 rule!(
578 QuadletSourceInvalid,
579 "BFQ1001",
580 "quadlet-source-invalid",
581 Error,
582 "The Quadlet document set is invalid.",
583 "Correct native Quadlet syntax and document-set errors before converting."
584 ),
585 rule!(
586 QuadletModelInvalid,
587 "BFQ1002",
588 "quadlet-model-invalid",
589 Error,
590 "Quadlet intent cannot be represented by the neutral application model.",
591 "Correct the reported native value or relationship."
592 ),
593 rule!(
594 QuadletInputUnsupported,
595 "BFQ1003",
596 "quadlet-input-unsupported",
597 Warning,
598 "Quadlet intent is outside the current neutral-model importer subset.",
599 "Review the named key; use --loss-policy partial only when omission is acceptable."
600 ),
601 rule!(
602 QuadletInputApproximation,
603 "BFQ1004",
604 "quadlet-input-approximation",
605 Warning,
606 "Quadlet intent is approximated in the neutral model.",
607 "Review the documented semantic difference before authorizing approximate output."
608 ),
609 rule!(
610 QuadletNativeSyntax,
611 "BFQ1101",
612 "quadlet-native-syntax",
613 Error,
614 "QuadletLens reported a native syntax diagnostic.",
615 "Use the retained source code and source span to correct the Quadlet unit."
616 ),
617 rule!(
618 QuadletNativeModel,
619 "BFQ1102",
620 "quadlet-native-model",
621 Error,
622 "QuadletLens reported a native model diagnostic.",
623 "Use the retained source code and source span to correct the Quadlet value."
624 ),
625 rule!(
626 QuadletNativeDocumentSet,
627 "BFQ1103",
628 "quadlet-native-document-set",
629 Error,
630 "QuadletLens reported a native document-set diagnostic.",
631 "Correct missing, ambiguous, or invalid cross-document relationships."
632 ),
633 rule!(
634 QuadletNativeFailure,
635 "BFQ1104",
636 "quadlet-native-failure",
637 Error,
638 "QuadletLens could not construct the native input boundary.",
639 "Correct the reported input and native stage before retrying."
640 ),
641];
642
643impl RuleId {
644 #[must_use]
646 pub const fn definition(self) -> &'static DiagnosticRule {
647 match self {
648 Self::ComposeModelInvalid => &RULES[0],
649 Self::ComposeProfileRequired => &RULES[1],
650 Self::ComposeProfileMismatch => &RULES[2],
651 Self::ComposeIntentUnsupported => &RULES[3],
652 Self::ComposeValueInvalid => &RULES[4],
653 Self::ComposeTargetInvalid => &RULES[5],
654 Self::ComposeOutputUnsupported => &RULES[6],
655 Self::ComposeGenerationFailed => &RULES[7],
656 Self::ComposeCompatibilityConstraint => &RULES[8],
657 Self::ComposeUnsetVariable => &RULES[9],
658 Self::ComposeRequiredVariable => &RULES[10],
659 Self::ComposeInterpolationInvalid => &RULES[11],
660 Self::ComposeInterpolationNestingLimit => &RULES[12],
661 Self::ComposeUnresolvedVariable => &RULES[13],
662 Self::ComposeNativeError => &RULES[14],
663 Self::ComposeNativeWarning => &RULES[15],
664 Self::ComposeNativeNote => &RULES[16],
665 Self::OrchestrationFailed => &RULES[17],
666 Self::InputReadFailed => &RULES[18],
667 Self::ComposeProjectRootInvalid => &RULES[19],
668 Self::PodmanTargetSelectionInvalid => &RULES[20],
669 Self::InterpolationInputInvalid => &RULES[21],
670 Self::ComposeLoadFailed => &RULES[22],
671 Self::ConversionFailed => &RULES[23],
672 Self::QuadletParseFailed => &RULES[24],
673 Self::OutputDirectoryNotEmpty => &RULES[25],
674 Self::OutputPathInvalid => &RULES[26],
675 Self::OutputWriteFailed => &RULES[27],
676 Self::ReportWriteFailed => &RULES[28],
677 Self::SupportBundleWriteFailed => &RULES[29],
678 Self::PodmanSourceInvalid => &RULES[30],
679 Self::PodmanInputUnmodelled => &RULES[31],
680 Self::PodmanPromotionRequired => &RULES[32],
681 Self::PodmanIdentityConflict => &RULES[33],
682 Self::PodmanSecretGrantIncomplete => &RULES[34],
683 Self::PodmanTargetInvalid => &RULES[35],
684 Self::PodmanOutputUnsupported => &RULES[36],
685 Self::PodmanPlanningFailed => &RULES[37],
686 Self::QuadletTargetInvalid => &RULES[38],
687 Self::QuadletOpenEndedTarget => &RULES[39],
688 Self::QuadletOutputUnsupported => &RULES[40],
689 Self::QuadletValueInvalid => &RULES[41],
690 Self::QuadletGenerationFailed => &RULES[42],
691 Self::QuadletCapabilityUnavailable => &RULES[43],
692 Self::QuadletGroupingApproximation => &RULES[44],
693 Self::QuadletDependencyUnsupported => &RULES[45],
694 Self::QuadletRestartApproximation => &RULES[46],
695 Self::QuadletEnvironmentFileApproximation => &RULES[47],
696 Self::QuadletGroupingInvalid => &RULES[48],
697 Self::QuadletDependencyInvalid => &RULES[49],
698 Self::QuadletCapabilityDeprecated => &RULES[50],
699 Self::QuadletUnresolvedVariable => &RULES[51],
700 Self::QuadletSourceInvalid => &RULES[52],
701 Self::QuadletModelInvalid => &RULES[53],
702 Self::QuadletInputUnsupported => &RULES[54],
703 Self::QuadletInputApproximation => &RULES[55],
704 Self::QuadletNativeSyntax => &RULES[56],
705 Self::QuadletNativeModel => &RULES[57],
706 Self::QuadletNativeDocumentSet => &RULES[58],
707 Self::QuadletNativeFailure => &RULES[59],
708 }
709 }
710}
711
712#[must_use]
714pub fn find_rule(value: &str) -> Option<&'static DiagnosticRule> {
715 RULES
716 .iter()
717 .find(|rule| rule.code.eq_ignore_ascii_case(value) || rule.name.eq_ignore_ascii_case(value))
718}
719
720#[cfg(test)]
721mod tests {
722 use std::collections::BTreeSet;
723
724 use super::{RULES, RuleId, find_rule};
725
726 #[test]
727 fn catalogue_codes_names_and_typed_indices_are_unique_and_valid() -> Result<(), String> {
728 let mut codes = BTreeSet::new();
729 let mut names = BTreeSet::new();
730 let mut ids = BTreeSet::new();
731 for rule in RULES {
732 rule.diagnostic_code().map_err(|error| error.to_string())?;
733 assert!(ids.insert(rule.id()), "duplicate typed id {:?}", rule.id());
734 assert_eq!(rule.id().definition(), rule);
735 assert!(codes.insert(rule.code()), "duplicate code {}", rule.code());
736 assert!(names.insert(rule.name()), "duplicate name {}", rule.name());
737 assert!(!rule.description().is_empty());
738 assert!(!rule.help().is_empty());
739 assert_ne!(
740 rule.owner(),
741 "BoxFerry engine",
742 "uncatalogued namespace for {}",
743 rule.code()
744 );
745 }
746 assert!(RULES.windows(2).all(|pair| pair[0].code() < pair[1].code()));
747 assert_eq!(RuleId::OutputWriteFailed.definition().code(), "BFO2003");
748 assert_eq!(RuleId::PodmanPromotionRequired.definition().code(), "BFP0003");
749 assert_eq!(
750 find_rule("podman-output-unsupported").map(|rule| rule.owner()),
751 Some("Podman adapter")
752 );
753 assert_eq!(find_rule("quadlet-restart-policy-approximation"), find_rule("BFQ0009"));
754 Ok(())
755 }
756}