edifact_rs/validator/context.rs
1//! Validation context: `ValidationContext`, `ValidationContextBuilder`, `LayeredValidator`.
2
3use super::pack::ProfileRulePack;
4use super::{EnvelopeValidator, ValidationLayer, ValidationRuleContext, Validator};
5use crate::{OwnedSegment, Segment, ValidationReport, ValidationSeverity};
6use std::any::Any;
7use std::sync::Arc;
8
9pub(super) struct LayeredValidator {
10 pub(super) layer: ValidationLayer,
11 pub(super) validator: Box<dyn Validator + Send + Sync>,
12}
13
14/// Runtime validation context for progressive layered validation.
15///
16/// # Architecture
17///
18/// `edifact-rs` validation is organized into **four independent layers**, each
19/// responsible for a distinct class of checks. All layers run against the same
20/// segment slice; their issues are collected into a single [`ValidationReport`].
21///
22/// | Layer | [`ValidationLayer`] variant | Default | Type |
23/// |---|---|---|---|
24/// | **Envelope** | `Envelope` | disabled | [`EnvelopeValidator`] |
25/// | **Structure** | `Structure` | enabled | external (e.g. `DirectoryValidator`) |
26/// | **Code-list** | `CodeList` | enabled | external |
27/// | **Profile** | `Profile` | enabled | [`ProfileRulePack`] / `Arc<ProfileRulePack>` |
28///
29/// Validators are run in registration order within each enabled layer. Layers
30/// themselves have no enforced ordering beyond the order in which they are added
31/// via the builder.
32///
33/// ## Envelope layer
34///
35/// Checks `UNB`/`UNH`/`UNT`/`UNZ` structural invariants: presence, message
36/// count, and segment count. Enabled by calling
37/// [`ValidationContextBuilder::with_envelope_validation`]. When enabled, the
38/// envelope segments (`UNB`, `UNZ`, `UNG`, `UNE`) are *excluded* from the slice
39/// passed to validators in subsequent layers.
40///
41/// ## Structure layer
42///
43/// Validates segment presence, order, and arity against an EDIFACT directory.
44/// Implemented by `DirectoryValidator` (registered as a `Structure`-layer
45/// validator via [`ValidationContextBuilder::with_validator`]).
46///
47/// ## Code-list layer
48///
49/// Validates DE values against EDIFACT code lists from the directory. Also
50/// implemented by `DirectoryValidator`.
51///
52/// ## Profile layer
53///
54/// Applies downstream business rules (BDEW AHB / MIG rules, custom constraints)
55/// via [`ProfileRulePack`]. A pack can be scoped to specific EDIFACT message
56/// types (`for_message_type`) and association-assigned codes (`for_release`).
57///
58/// ## Group-aware validation
59///
60/// Validators that implement [`Validator::validate_group_batch`] can additionally
61/// enforce rules scoped to specific segment groups (e.g. "DTM must appear in every
62/// SG5 occurrence"). Call [`validate_lenient_grouped`] with a pre-built
63/// [`SegmentGroupIndexed`] tree to activate both the flat and group passes.
64///
65/// [`SegmentGroupIndexed`]: crate::SegmentGroupIndexed
66/// [`validate_lenient_grouped`]: ValidationContext::validate_lenient_grouped
67///
68/// # Example — building a context
69///
70/// ```rust,ignore
71/// use std::sync::{Arc, LazyLock};
72/// use edifact_rs::{ProfileRulePack, ValidationContext, ValidationLayer};
73///
74/// static ORDERS_PACK: LazyLock<Arc<ProfileRulePack>> = LazyLock::new(|| {
75/// Arc::new(
76/// ProfileRulePack::new("ORDERS-MIG-5.5")
77/// .for_message_type("ORDERS")
78/// .require_segment("BGM", "MIG-BGM-M")
79/// .require_segment_in_group("SG2", "NAD", "SG2-NAD-M"),
80/// )
81/// });
82///
83/// let ctx = ValidationContext::builder()
84/// .with_envelope_validation()
85/// .with_profile_pack_arc(Arc::clone(&*ORDERS_PACK))
86/// .build();
87///
88/// let report = ctx.validate_lenient(&segments);
89/// ```
90pub struct ValidationContext {
91 pub(super) validators: Vec<LayeredValidator>,
92 pub(super) envelope_enabled: bool,
93 pub(super) structure_enabled: bool,
94 pub(super) code_list_enabled: bool,
95 pub(super) profile_enabled: bool,
96 /// Stop evaluating all remaining validators as soon as a `Critical`-severity
97 /// issue appears in the report.
98 pub(super) bail_on_first_critical: bool,
99 pub(super) message_type: Option<String>,
100 /// Injected into every emitted `ValidationIssue` when set.
101 pub(super) message_ref: Option<String>,
102 pub(super) metadata: Option<Arc<dyn Any + Send + Sync>>,
103}
104
105/// Builder for [`ValidationContext`].
106#[must_use = "call `.build()` to produce a `ValidationContext`"]
107pub struct ValidationContextBuilder {
108 pub(super) inner: ValidationContext,
109}
110
111impl Default for ValidationContextBuilder {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl ValidationContextBuilder {
118 /// Create a new context builder.
119 ///
120 /// Structure, code-list, and profile layers are enabled by default.
121 /// The envelope layer is **disabled** by default.
122 pub fn new() -> Self {
123 Self {
124 inner: ValidationContext {
125 validators: Vec::new(),
126 envelope_enabled: false,
127 structure_enabled: true,
128 code_list_enabled: true,
129 profile_enabled: true,
130 bail_on_first_critical: false,
131 message_type: None,
132 message_ref: None,
133 metadata: None,
134 },
135 }
136 }
137
138 /// Attach typed metadata accessible to context-aware profile rules.
139 pub fn with_metadata<T: Any + Send + Sync + 'static>(mut self, value: T) -> Self {
140 self.inner.metadata = Some(Arc::new(value));
141 self
142 }
143
144 /// Stamp every issue produced by this context with the given message reference.
145 pub fn with_message_ref(mut self, message_ref: impl Into<String>) -> Self {
146 self.inner.message_ref = Some(message_ref.into());
147 self
148 }
149
150 /// Set message type metadata for downstream validators.
151 pub fn with_message_type(mut self, message_type: impl Into<String>) -> Self {
152 self.inner.message_type = Some(message_type.into());
153 let configured = self.inner.message_type.as_deref();
154 for layered in &mut self.inner.validators {
155 layered.validator.set_message_type(configured);
156 }
157 self
158 }
159
160 /// Enable/disable structure validators.
161 pub fn structure(mut self, enabled: bool) -> Self {
162 self.inner.structure_enabled = enabled;
163 self
164 }
165
166 /// Enable/disable code-list validators.
167 pub fn code_list(mut self, enabled: bool) -> Self {
168 self.inner.code_list_enabled = enabled;
169 self
170 }
171
172 /// Enable/disable profile validators.
173 pub fn profile(mut self, enabled: bool) -> Self {
174 self.inner.profile_enabled = enabled;
175 self
176 }
177
178 /// Stop all validation as soon as the first `Critical`-severity issue is produced.
179 ///
180 /// When set, [`ValidationContext::validate_lenient`] returns immediately after the
181 /// first `Critical` issue from any validator, skipping all remaining packs and layers.
182 ///
183 /// Default: `false` (collect all issues across all layers).
184 pub fn bail_on_first_critical(mut self, bail: bool) -> Self {
185 self.inner.bail_on_first_critical = bail;
186 self
187 }
188
189 /// Enable/disable envelope layer validators.
190 ///
191 /// Off by default. Call [`with_envelope_validation`][Self::with_envelope_validation]
192 /// to add the built-in [`EnvelopeValidator`] and enable the layer in one step.
193 pub fn envelope(mut self, enabled: bool) -> Self {
194 self.inner.envelope_enabled = enabled;
195 self
196 }
197
198 /// Add the built-in [`EnvelopeValidator`] and enable the envelope layer.
199 pub fn with_envelope_validation(mut self) -> Self {
200 self.inner.envelope_enabled = true;
201 self.inner.validators.push(LayeredValidator {
202 layer: ValidationLayer::Envelope,
203 validator: Box::new(EnvelopeValidator),
204 });
205 self
206 }
207
208 /// Add a validator assigned to `layer`.
209 pub fn with_validator<V>(mut self, layer: ValidationLayer, mut validator: V) -> Self
210 where
211 V: Validator + 'static,
212 {
213 validator.set_message_type(self.inner.message_type.as_deref());
214 self.inner.validators.push(LayeredValidator {
215 layer,
216 validator: Box::new(validator),
217 });
218 self
219 }
220
221 /// Add a profile rule pack to the profile layer.
222 pub fn with_profile_pack(mut self, mut pack: ProfileRulePack) -> Self {
223 pack.set_message_type(self.inner.message_type.as_deref());
224 self.inner.validators.push(LayeredValidator {
225 layer: ValidationLayer::Profile,
226 validator: Box::new(pack),
227 });
228 self
229 }
230
231 /// Add a reference-counted profile rule pack to the profile layer.
232 ///
233 /// Unlike [`with_profile_pack`](Self::with_profile_pack), this method stores the pack
234 /// behind an [`Arc`] so context forking (via
235 /// [`ValidationContext::fork_with_message_ref`]) only increments the reference count
236 /// instead of deep-cloning the rule vec.
237 ///
238 /// This is the preferred API for downstream code that caches packs in static
239 /// storage (`LazyLock`, `OnceLock`) and reuses them across many validation calls.
240 ///
241 /// # Example
242 ///
243 /// ```rust,ignore
244 /// use std::sync::{Arc, LazyLock};
245 /// use edifact_rs::{ProfileRulePack, ValidationContext};
246 ///
247 /// static PACK: LazyLock<Arc<ProfileRulePack>> = LazyLock::new(|| {
248 /// Arc::new(ProfileRulePack::new("MIG").require_segment("BGM", "MIG-BGM-M"))
249 /// });
250 ///
251 /// let ctx = ValidationContext::builder()
252 /// .with_profile_pack_arc(Arc::clone(&*PACK))
253 /// .build();
254 /// ```
255 pub fn with_profile_pack_arc(mut self, pack: std::sync::Arc<ProfileRulePack>) -> Self {
256 self.inner.validators.push(LayeredValidator {
257 layer: ValidationLayer::Profile,
258 validator: Box::new(pack),
259 });
260 self
261 }
262
263 /// Finalize builder and create context.
264 #[must_use = "call `.validate_lenient()` or `.validate_strict()` on the resulting context"]
265 pub fn build(self) -> ValidationContext {
266 self.inner
267 }
268}
269
270impl ValidationContext {
271 /// Start building a validation context.
272 pub fn builder() -> ValidationContextBuilder {
273 ValidationContextBuilder::new()
274 }
275
276 /// Execute validators in lenient mode for enabled layers.
277 pub fn validate_lenient(&self, segments: &[Segment<'_>]) -> ValidationReport {
278 self.validate_with_context(segments, &self.build_rule_context())
279 }
280
281 /// Execute flat + group-aware validators in lenient mode.
282 ///
283 /// This method runs the full flat validation pass (same as
284 /// [`validate_lenient`](Self::validate_lenient)) **and** then runs the
285 /// group-aware pass by calling [`Validator::validate_group_batch`] on every
286 /// validator. Validators without group rules treat `validate_group_batch`
287 /// as a no-op, so this is safe to call for any context.
288 ///
289 /// # When to use
290 ///
291 /// Use this method when you have already grouped your segments with
292 /// [`group_segments_indexed`][crate::group_segments_indexed] or
293 /// [`group_owned_segments_indexed`][crate::group_owned_segments_indexed] and
294 /// want group-presence or cross-group rules (via
295 /// [`ProfileRulePack::with_scoped_group_rule_fn`][crate::ProfileRulePack::with_scoped_group_rule_fn])
296 /// to fire.
297 ///
298 /// # Example
299 ///
300 /// ```rust,ignore
301 /// use edifact_rs::{group_segments_indexed, ValidationContext};
302 /// use edifact_rs::group::GroupDef;
303 ///
304 /// static SCHEMA: &[GroupDef] = &[
305 /// GroupDef { name: "SG5", trigger: "LOC", children: &[] },
306 /// ];
307 ///
308 /// let tree = group_segments_indexed(&segments, SCHEMA, "ROOT");
309 /// let pack = ProfileRulePack::new("AHB")
310 /// .require_segment_in_group("SG5", "DTM", "SG5-DTM-M");
311 /// let ctx = ValidationContext::builder().with_profile_pack(pack).build();
312 ///
313 /// let report = ctx.validate_lenient_grouped(&tree, &segments);
314 /// ```
315 pub fn validate_lenient_grouped(
316 &self,
317 root: &crate::group::SegmentGroupIndexed,
318 segments: &[Segment<'_>],
319 ) -> ValidationReport {
320 let base_ctx = self.build_rule_context();
321 // Phase 1: flat validation.
322 let mut report = self.validate_with_context(segments, &base_ctx);
323 // Phase 2: group-aware validation with pre-extracted UNH message type.
324 let unh_mt = segments
325 .iter()
326 .find(|s| s.tag == "UNH")
327 .and_then(|s| s.get_element(1))
328 .and_then(|e| e.get_component(0));
329 let ctx_with_type;
330 let group_ctx: &ValidationRuleContext<'_> = if let Some(mt) = unh_mt {
331 ctx_with_type = ValidationRuleContext {
332 metadata: base_ctx.metadata,
333 message_ref: base_ctx.message_ref,
334 message_type: Some(mt),
335 };
336 &ctx_with_type
337 } else {
338 &base_ctx
339 };
340 self.run_group_pass(root, segments, &mut report, group_ctx);
341 report
342 }
343
344 /// Execute flat + group-aware validators in strict mode.
345 pub fn validate_strict_grouped(
346 &self,
347 root: &crate::group::SegmentGroupIndexed,
348 segments: &[Segment<'_>],
349 ) -> Result<ValidationReport, ValidationReport> {
350 self.validate_lenient_grouped(root, segments).result()
351 }
352
353 /// Execute flat + group-aware validators against owned segments in lenient mode.
354 pub fn validate_lenient_grouped_owned(
355 &self,
356 root: &crate::group::SegmentGroupIndexed,
357 segments: &[crate::OwnedSegment],
358 ) -> ValidationReport {
359 let base_ctx = self.build_rule_context();
360 // Phase 1: flat validation.
361 let mut report = self.validate_with_context_owned(segments, &base_ctx);
362 // Phase 2: group-aware validation — borrow owned segments.
363 let borrowed: Vec<Segment<'_>> = segments.iter().map(|s| s.as_borrowed()).collect();
364 let unh_mt = borrowed
365 .iter()
366 .find(|s| s.tag == "UNH")
367 .and_then(|s| s.get_element(1))
368 .and_then(|e| e.get_component(0));
369 let ctx_with_type;
370 let group_ctx: &ValidationRuleContext<'_> = if let Some(mt) = unh_mt {
371 ctx_with_type = ValidationRuleContext {
372 metadata: base_ctx.metadata,
373 message_ref: base_ctx.message_ref,
374 message_type: Some(mt),
375 };
376 &ctx_with_type
377 } else {
378 &base_ctx
379 };
380 self.run_group_pass(root, &borrowed, &mut report, group_ctx);
381 report
382 }
383
384 /// Execute flat + group-aware validators against owned segments in strict mode.
385 pub fn validate_strict_grouped_owned(
386 &self,
387 root: &crate::group::SegmentGroupIndexed,
388 segments: &[crate::OwnedSegment],
389 ) -> Result<ValidationReport, ValidationReport> {
390 self.validate_lenient_grouped_owned(root, segments).result()
391 }
392
393 /// Phase-2 group pass: call `validate_group_batch` on each enabled validator.
394 fn run_group_pass(
395 &self,
396 root: &crate::group::SegmentGroupIndexed,
397 segments: &[Segment<'_>],
398 report: &mut ValidationReport,
399 context: &ValidationRuleContext<'_>,
400 ) {
401 for lv in &self.validators {
402 if !self.layer_enabled(lv.layer) {
403 continue;
404 }
405 lv.validator
406 .validate_group_batch(root, segments, report, context);
407 if self.bail_on_first_critical
408 && report
409 .errors
410 .iter()
411 .any(|i| i.severity == ValidationSeverity::Critical)
412 {
413 break;
414 }
415 }
416 }
417
418 /// Execute validators with per-call typed metadata.
419 pub fn validate_lenient_with<T: Any + Send + Sync>(
420 &self,
421 segments: &[Segment<'_>],
422 value: &T,
423 ) -> ValidationReport {
424 let ctx = ValidationRuleContext {
425 metadata: Some(value as &(dyn Any + Send + Sync)),
426 message_ref: self.message_ref.as_deref(),
427 message_type: None,
428 };
429 self.validate_with_context(segments, &ctx)
430 }
431
432 /// Execute validators in strict mode for enabled layers.
433 pub fn validate_strict(
434 &self,
435 segments: &[Segment<'_>],
436 ) -> Result<ValidationReport, ValidationReport> {
437 self.validate_lenient(segments).result()
438 }
439
440 /// Execute validators in strict mode with per-call typed metadata.
441 pub fn validate_strict_with<T: Any + Send + Sync>(
442 &self,
443 segments: &[Segment<'_>],
444 value: &T,
445 ) -> Result<ValidationReport, ValidationReport> {
446 self.validate_lenient_with(segments, value).result()
447 }
448
449 /// Execute validators in lenient mode against an owned-segment slice.
450 pub fn validate_lenient_owned(&self, segments: &[OwnedSegment]) -> ValidationReport {
451 if self.validators.is_empty()
452 && !self.envelope_enabled
453 && !self.structure_enabled
454 && !self.code_list_enabled
455 && !self.profile_enabled
456 {
457 return ValidationReport::default();
458 }
459 self.validate_with_context_owned(segments, &self.build_rule_context())
460 }
461
462 fn build_rule_context(&self) -> ValidationRuleContext<'_> {
463 self.metadata
464 .as_ref()
465 .map(|arc| ValidationRuleContext {
466 metadata: Some(arc.as_ref() as &(dyn Any + Send + Sync)),
467 message_ref: self.message_ref.as_deref(),
468 message_type: None,
469 })
470 .unwrap_or_else(|| ValidationRuleContext {
471 metadata: None,
472 message_ref: self.message_ref.as_deref(),
473 message_type: None,
474 })
475 }
476
477 fn validate_with_context_owned(
478 &self,
479 segments: &[OwnedSegment],
480 context: &ValidationRuleContext<'_>,
481 ) -> ValidationReport {
482 let mut report = ValidationReport::default();
483 // Pre-extract UNH message type once (F-017).
484 let unh_message_type: Option<String> = segments
485 .iter()
486 .find(|s| s.tag == "UNH")
487 .and_then(|s| s.component_str(1, 0))
488 .map(str::to_owned);
489 let ctx_with_type;
490 let effective_ctx: &ValidationRuleContext<'_> = if let Some(ref mt) = unh_message_type {
491 ctx_with_type = ValidationRuleContext {
492 metadata: context.metadata,
493 message_ref: context.message_ref,
494 message_type: Some(mt.as_str()),
495 };
496 &ctx_with_type
497 } else {
498 context
499 };
500 let mut full_borrowed: Option<Vec<Segment<'_>>> = None;
501 let mut filtered_borrowed: Option<Vec<Segment<'_>>> = None;
502 let mut envelope_ran = false;
503
504 for lv in &self.validators {
505 if !self.layer_enabled(lv.layer) {
506 continue;
507 }
508 if lv.layer == ValidationLayer::Envelope {
509 let borrowed: Vec<Segment<'_>> = segments.iter().map(|s| s.as_borrowed()).collect();
510 lv.validator
511 .validate_batch(&borrowed, &mut report, effective_ctx);
512 envelope_ran = true;
513 } else if envelope_ran {
514 let active = filtered_borrowed.get_or_insert_with(|| {
515 segments
516 .iter()
517 .filter(|s| !matches!(s.tag.as_str(), "UNB" | "UNZ" | "UNG" | "UNE"))
518 .map(|s| s.as_borrowed())
519 .collect()
520 });
521 lv.validator
522 .validate_batch(active, &mut report, effective_ctx);
523 } else {
524 let active = full_borrowed
525 .get_or_insert_with(|| segments.iter().map(|s| s.as_borrowed()).collect());
526 lv.validator
527 .validate_batch(active, &mut report, effective_ctx);
528 }
529 if self.bail_on_first_critical
530 && report
531 .errors
532 .iter()
533 .any(|i| i.severity == ValidationSeverity::Critical)
534 {
535 break;
536 }
537 }
538
539 if let Some(ref msg_ref) = self.message_ref {
540 for issue in report
541 .errors
542 .iter_mut()
543 .chain(report.warnings.iter_mut())
544 .chain(report.infos.iter_mut())
545 {
546 if issue.message_ref.is_none() {
547 issue.message_ref = Some(msg_ref.clone());
548 }
549 }
550 }
551 report
552 }
553
554 /// Execute validators in strict mode against an owned-segment slice.
555 pub fn validate_strict_owned(
556 &self,
557 segments: &[OwnedSegment],
558 ) -> Result<ValidationReport, ValidationReport> {
559 self.validate_lenient_owned(segments).result()
560 }
561
562 fn validate_with_context(
563 &self,
564 segments: &[Segment<'_>],
565 context: &ValidationRuleContext<'_>,
566 ) -> ValidationReport {
567 let mut report = ValidationReport::default();
568 // Pre-extract UNH message type once (F-017).
569 let unh_message_type = segments
570 .iter()
571 .find(|s| s.tag == "UNH")
572 .and_then(|s| s.get_element(1))
573 .and_then(|e| e.get_component(0));
574 let ctx_with_type;
575 let effective_ctx: &ValidationRuleContext<'_> = if let Some(mt) = unh_message_type {
576 ctx_with_type = ValidationRuleContext {
577 metadata: context.metadata,
578 message_ref: context.message_ref,
579 message_type: Some(mt),
580 };
581 &ctx_with_type
582 } else {
583 context
584 };
585 let mut filtered: Option<Vec<Segment<'_>>> = None;
586 let mut envelope_ran = false;
587
588 for lv in &self.validators {
589 if !self.layer_enabled(lv.layer) {
590 continue;
591 }
592 if lv.layer == ValidationLayer::Envelope {
593 lv.validator
594 .validate_batch(segments, &mut report, effective_ctx);
595 envelope_ran = true;
596 } else {
597 let active: &[Segment<'_>] = if envelope_ran {
598 filtered.get_or_insert_with(|| {
599 segments
600 .iter()
601 .filter(|s| !matches!(s.tag, "UNB" | "UNZ" | "UNG" | "UNE"))
602 .cloned()
603 .collect()
604 })
605 } else {
606 segments
607 };
608 lv.validator
609 .validate_batch(active, &mut report, effective_ctx);
610 }
611 if self.bail_on_first_critical
612 && report
613 .errors
614 .iter()
615 .any(|i| i.severity == ValidationSeverity::Critical)
616 {
617 break;
618 }
619 }
620
621 if let Some(ref msg_ref) = self.message_ref {
622 for issue in report
623 .errors
624 .iter_mut()
625 .chain(report.warnings.iter_mut())
626 .chain(report.infos.iter_mut())
627 {
628 if issue.message_ref.is_none() {
629 issue.message_ref = Some(msg_ref.clone());
630 }
631 }
632 }
633 report
634 }
635
636 /// Message type metadata associated with this context, if provided.
637 pub fn message_type(&self) -> Option<&str> {
638 self.message_type.as_deref()
639 }
640
641 /// Message reference (`UNH` element 0) associated with this context, if provided.
642 pub fn message_ref(&self) -> Option<&str> {
643 self.message_ref.as_deref()
644 }
645
646 /// Create a child context that inherits all rules and configuration from `self`
647 /// but is scoped to a specific message reference (UNH DE 0062).
648 ///
649 /// Issues produced by the child context are automatically stamped with
650 /// `message_ref`, making it easy to correlate findings in a multi-message
651 /// interchange back to the originating `UNH`/`UNT` envelope.
652 ///
653 /// # Example
654 ///
655 /// ```rust,ignore
656 /// let base_ctx = ValidationContext::builder()
657 /// .with_profile_pack(mig_pack)
658 /// .build();
659 ///
660 /// for (ref_no, message_segments) in messages {
661 /// let child = base_ctx.fork_with_message_ref(&ref_no);
662 /// let report = child.validate_lenient(&message_segments);
663 /// }
664 /// ```
665 pub fn fork_with_message_ref(&self, message_ref: impl Into<String>) -> Self {
666 Self {
667 validators: self
668 .validators
669 .iter()
670 .map(|lv| LayeredValidator {
671 layer: lv.layer,
672 validator: lv.validator.fork(),
673 })
674 .collect(),
675 envelope_enabled: self.envelope_enabled,
676 structure_enabled: self.structure_enabled,
677 code_list_enabled: self.code_list_enabled,
678 profile_enabled: self.profile_enabled,
679 bail_on_first_critical: self.bail_on_first_critical,
680 message_type: self.message_type.clone(),
681 message_ref: Some(message_ref.into()),
682 metadata: self.metadata.clone(),
683 }
684 }
685
686 fn layer_enabled(&self, layer: ValidationLayer) -> bool {
687 match layer {
688 ValidationLayer::Envelope => self.envelope_enabled,
689 ValidationLayer::Structure => self.structure_enabled,
690 ValidationLayer::CodeList => self.code_list_enabled,
691 ValidationLayer::Profile => self.profile_enabled,
692 }
693 }
694}