use super::pack::ProfileRulePack;
use super::{EnvelopeValidator, ValidationLayer, ValidationRuleContext, Validator};
use crate::{OwnedSegment, Segment, ValidationReport, ValidationSeverity};
use std::any::Any;
use std::sync::Arc;
pub(super) struct LayeredValidator {
pub(super) layer: ValidationLayer,
pub(super) validator: Box<dyn Validator + Send + Sync>,
}
pub struct ValidationContext {
pub(super) validators: Vec<LayeredValidator>,
pub(super) envelope_enabled: bool,
pub(super) structure_enabled: bool,
pub(super) code_list_enabled: bool,
pub(super) profile_enabled: bool,
pub(super) bail_on_first_critical: bool,
pub(super) message_type: Option<String>,
pub(super) message_ref: Option<String>,
pub(super) metadata: Option<Arc<dyn Any + Send + Sync>>,
}
#[must_use = "call `.build()` to produce a `ValidationContext`"]
pub struct ValidationContextBuilder {
pub(super) inner: ValidationContext,
}
impl Default for ValidationContextBuilder {
fn default() -> Self {
Self::new()
}
}
impl ValidationContextBuilder {
pub fn new() -> Self {
Self {
inner: ValidationContext {
validators: Vec::new(),
envelope_enabled: false,
structure_enabled: true,
code_list_enabled: true,
profile_enabled: true,
bail_on_first_critical: false,
message_type: None,
message_ref: None,
metadata: None,
},
}
}
pub fn with_metadata<T: Any + Send + Sync + 'static>(mut self, value: T) -> Self {
self.inner.metadata = Some(Arc::new(value));
self
}
pub fn with_message_ref(mut self, message_ref: impl Into<String>) -> Self {
self.inner.message_ref = Some(message_ref.into());
self
}
pub fn with_message_type(mut self, message_type: impl Into<String>) -> Self {
self.inner.message_type = Some(message_type.into());
let configured = self.inner.message_type.as_deref();
for layered in &mut self.inner.validators {
layered.validator.set_message_type(configured);
}
self
}
pub fn structure(mut self, enabled: bool) -> Self {
self.inner.structure_enabled = enabled;
self
}
pub fn code_list(mut self, enabled: bool) -> Self {
self.inner.code_list_enabled = enabled;
self
}
pub fn profile(mut self, enabled: bool) -> Self {
self.inner.profile_enabled = enabled;
self
}
pub fn bail_on_first_critical(mut self, bail: bool) -> Self {
self.inner.bail_on_first_critical = bail;
self
}
pub fn envelope(mut self, enabled: bool) -> Self {
self.inner.envelope_enabled = enabled;
self
}
pub fn with_envelope_validation(mut self) -> Self {
self.inner.envelope_enabled = true;
self.inner.validators.push(LayeredValidator {
layer: ValidationLayer::Envelope,
validator: Box::new(EnvelopeValidator),
});
self
}
pub fn with_validator<V>(mut self, layer: ValidationLayer, mut validator: V) -> Self
where
V: Validator + 'static,
{
validator.set_message_type(self.inner.message_type.as_deref());
self.inner.validators.push(LayeredValidator {
layer,
validator: Box::new(validator),
});
self
}
pub fn with_profile_pack(mut self, mut pack: ProfileRulePack) -> Self {
pack.set_message_type(self.inner.message_type.as_deref());
self.inner.validators.push(LayeredValidator {
layer: ValidationLayer::Profile,
validator: Box::new(pack),
});
self
}
pub fn with_profile_pack_arc(mut self, pack: std::sync::Arc<ProfileRulePack>) -> Self {
self.inner.validators.push(LayeredValidator {
layer: ValidationLayer::Profile,
validator: Box::new(pack),
});
self
}
#[must_use = "call `.validate_lenient()` or `.validate_strict()` on the resulting context"]
pub fn build(self) -> ValidationContext {
self.inner
}
}
impl ValidationContext {
pub fn builder() -> ValidationContextBuilder {
ValidationContextBuilder::new()
}
pub fn validate_lenient(&self, segments: &[Segment<'_>]) -> ValidationReport {
self.validate_with_context(segments, &self.build_rule_context())
}
pub fn validate_lenient_grouped(
&self,
root: &crate::group::SegmentGroupIndexed,
segments: &[Segment<'_>],
) -> ValidationReport {
let base_ctx = self.build_rule_context();
let mut report = self.validate_with_context(segments, &base_ctx);
let unh_mt = segments
.iter()
.find(|s| s.tag == "UNH")
.and_then(|s| s.get_element(1))
.and_then(|e| e.get_component(0));
let ctx_with_type;
let group_ctx: &ValidationRuleContext<'_> = if let Some(mt) = unh_mt {
ctx_with_type = ValidationRuleContext {
metadata: base_ctx.metadata,
message_ref: base_ctx.message_ref,
message_type: Some(mt),
};
&ctx_with_type
} else {
&base_ctx
};
self.run_group_pass(root, segments, &mut report, group_ctx);
report
}
pub fn validate_strict_grouped(
&self,
root: &crate::group::SegmentGroupIndexed,
segments: &[Segment<'_>],
) -> Result<ValidationReport, ValidationReport> {
self.validate_lenient_grouped(root, segments).result()
}
pub fn validate_lenient_grouped_owned(
&self,
root: &crate::group::SegmentGroupIndexed,
segments: &[crate::OwnedSegment],
) -> ValidationReport {
let base_ctx = self.build_rule_context();
let mut report = self.validate_with_context_owned(segments, &base_ctx);
let borrowed: Vec<Segment<'_>> = segments.iter().map(|s| s.as_borrowed()).collect();
let unh_mt = borrowed
.iter()
.find(|s| s.tag == "UNH")
.and_then(|s| s.get_element(1))
.and_then(|e| e.get_component(0));
let ctx_with_type;
let group_ctx: &ValidationRuleContext<'_> = if let Some(mt) = unh_mt {
ctx_with_type = ValidationRuleContext {
metadata: base_ctx.metadata,
message_ref: base_ctx.message_ref,
message_type: Some(mt),
};
&ctx_with_type
} else {
&base_ctx
};
self.run_group_pass(root, &borrowed, &mut report, group_ctx);
report
}
pub fn validate_strict_grouped_owned(
&self,
root: &crate::group::SegmentGroupIndexed,
segments: &[crate::OwnedSegment],
) -> Result<ValidationReport, ValidationReport> {
self.validate_lenient_grouped_owned(root, segments).result()
}
fn run_group_pass(
&self,
root: &crate::group::SegmentGroupIndexed,
segments: &[Segment<'_>],
report: &mut ValidationReport,
context: &ValidationRuleContext<'_>,
) {
for lv in &self.validators {
if !self.layer_enabled(lv.layer) {
continue;
}
lv.validator
.validate_group_batch(root, segments, report, context);
if self.bail_on_first_critical
&& report
.errors
.iter()
.any(|i| i.severity == ValidationSeverity::Critical)
{
break;
}
}
}
pub fn validate_lenient_with<T: Any + Send + Sync>(
&self,
segments: &[Segment<'_>],
value: &T,
) -> ValidationReport {
let ctx = ValidationRuleContext {
metadata: Some(value as &(dyn Any + Send + Sync)),
message_ref: self.message_ref.as_deref(),
message_type: None,
};
self.validate_with_context(segments, &ctx)
}
pub fn validate_strict(
&self,
segments: &[Segment<'_>],
) -> Result<ValidationReport, ValidationReport> {
self.validate_lenient(segments).result()
}
pub fn validate_strict_with<T: Any + Send + Sync>(
&self,
segments: &[Segment<'_>],
value: &T,
) -> Result<ValidationReport, ValidationReport> {
self.validate_lenient_with(segments, value).result()
}
pub fn validate_lenient_owned(&self, segments: &[OwnedSegment]) -> ValidationReport {
if self.validators.is_empty()
&& !self.envelope_enabled
&& !self.structure_enabled
&& !self.code_list_enabled
&& !self.profile_enabled
{
return ValidationReport::default();
}
self.validate_with_context_owned(segments, &self.build_rule_context())
}
fn build_rule_context(&self) -> ValidationRuleContext<'_> {
self.metadata
.as_ref()
.map(|arc| ValidationRuleContext {
metadata: Some(arc.as_ref() as &(dyn Any + Send + Sync)),
message_ref: self.message_ref.as_deref(),
message_type: None,
})
.unwrap_or_else(|| ValidationRuleContext {
metadata: None,
message_ref: self.message_ref.as_deref(),
message_type: None,
})
}
fn validate_with_context_owned(
&self,
segments: &[OwnedSegment],
context: &ValidationRuleContext<'_>,
) -> ValidationReport {
let mut report = ValidationReport::default();
let unh_message_type: Option<String> = segments
.iter()
.find(|s| s.tag == "UNH")
.and_then(|s| s.component_str(1, 0))
.map(str::to_owned);
let ctx_with_type;
let effective_ctx: &ValidationRuleContext<'_> = if let Some(ref mt) = unh_message_type {
ctx_with_type = ValidationRuleContext {
metadata: context.metadata,
message_ref: context.message_ref,
message_type: Some(mt.as_str()),
};
&ctx_with_type
} else {
context
};
let mut full_borrowed: Option<Vec<Segment<'_>>> = None;
let mut filtered_borrowed: Option<Vec<Segment<'_>>> = None;
let mut envelope_ran = false;
for lv in &self.validators {
if !self.layer_enabled(lv.layer) {
continue;
}
if lv.layer == ValidationLayer::Envelope {
let borrowed: Vec<Segment<'_>> = segments.iter().map(|s| s.as_borrowed()).collect();
lv.validator
.validate_batch(&borrowed, &mut report, effective_ctx);
envelope_ran = true;
} else if envelope_ran {
let active = filtered_borrowed.get_or_insert_with(|| {
segments
.iter()
.filter(|s| !matches!(s.tag.as_str(), "UNB" | "UNZ" | "UNG" | "UNE"))
.map(|s| s.as_borrowed())
.collect()
});
lv.validator
.validate_batch(active, &mut report, effective_ctx);
} else {
let active = full_borrowed
.get_or_insert_with(|| segments.iter().map(|s| s.as_borrowed()).collect());
lv.validator
.validate_batch(active, &mut report, effective_ctx);
}
if self.bail_on_first_critical
&& report
.errors
.iter()
.any(|i| i.severity == ValidationSeverity::Critical)
{
break;
}
}
if let Some(ref msg_ref) = self.message_ref {
for issue in report
.errors
.iter_mut()
.chain(report.warnings.iter_mut())
.chain(report.infos.iter_mut())
{
if issue.message_ref.is_none() {
issue.message_ref = Some(msg_ref.clone());
}
}
}
report
}
pub fn validate_strict_owned(
&self,
segments: &[OwnedSegment],
) -> Result<ValidationReport, ValidationReport> {
self.validate_lenient_owned(segments).result()
}
fn validate_with_context(
&self,
segments: &[Segment<'_>],
context: &ValidationRuleContext<'_>,
) -> ValidationReport {
let mut report = ValidationReport::default();
let unh_message_type = segments
.iter()
.find(|s| s.tag == "UNH")
.and_then(|s| s.get_element(1))
.and_then(|e| e.get_component(0));
let ctx_with_type;
let effective_ctx: &ValidationRuleContext<'_> = if let Some(mt) = unh_message_type {
ctx_with_type = ValidationRuleContext {
metadata: context.metadata,
message_ref: context.message_ref,
message_type: Some(mt),
};
&ctx_with_type
} else {
context
};
let mut filtered: Option<Vec<Segment<'_>>> = None;
let mut envelope_ran = false;
for lv in &self.validators {
if !self.layer_enabled(lv.layer) {
continue;
}
if lv.layer == ValidationLayer::Envelope {
lv.validator
.validate_batch(segments, &mut report, effective_ctx);
envelope_ran = true;
} else {
let active: &[Segment<'_>] = if envelope_ran {
filtered.get_or_insert_with(|| {
segments
.iter()
.filter(|s| !matches!(s.tag, "UNB" | "UNZ" | "UNG" | "UNE"))
.cloned()
.collect()
})
} else {
segments
};
lv.validator
.validate_batch(active, &mut report, effective_ctx);
}
if self.bail_on_first_critical
&& report
.errors
.iter()
.any(|i| i.severity == ValidationSeverity::Critical)
{
break;
}
}
if let Some(ref msg_ref) = self.message_ref {
for issue in report
.errors
.iter_mut()
.chain(report.warnings.iter_mut())
.chain(report.infos.iter_mut())
{
if issue.message_ref.is_none() {
issue.message_ref = Some(msg_ref.clone());
}
}
}
report
}
pub fn message_type(&self) -> Option<&str> {
self.message_type.as_deref()
}
pub fn message_ref(&self) -> Option<&str> {
self.message_ref.as_deref()
}
pub fn fork_with_message_ref(&self, message_ref: impl Into<String>) -> Self {
Self {
validators: self
.validators
.iter()
.map(|lv| LayeredValidator {
layer: lv.layer,
validator: lv.validator.fork(),
})
.collect(),
envelope_enabled: self.envelope_enabled,
structure_enabled: self.structure_enabled,
code_list_enabled: self.code_list_enabled,
profile_enabled: self.profile_enabled,
bail_on_first_critical: self.bail_on_first_critical,
message_type: self.message_type.clone(),
message_ref: Some(message_ref.into()),
metadata: self.metadata.clone(),
}
}
fn layer_enabled(&self, layer: ValidationLayer) -> bool {
match layer {
ValidationLayer::Envelope => self.envelope_enabled,
ValidationLayer::Structure => self.structure_enabled,
ValidationLayer::CodeList => self.code_list_enabled,
ValidationLayer::Profile => self.profile_enabled,
}
}
}