use std::fmt;
use std::sync::Arc;
use super::BindError;
use super::BindErrorKind;
use super::BoundValidationContext;
use super::DependencySpec;
use super::ExecutionError;
use super::ExecutionErrorKind;
use super::InputType;
use super::PreparedValidator;
use super::ValidationOutcome;
use super::ValidationValue;
use super::ValidatorSignature;
use crate::NamedValidationDependency;
use crate::ValidationPath;
use crate::ValidatorId;
#[derive(Clone)]
pub struct BoundValidator {
prepared: Arc<dyn PreparedValidator>,
input: InputType,
dependencies: &'static [DependencySpec],
rule_id: ValidatorId,
}
impl BoundValidator {
pub fn try_from_prepared<T: 'static>(
rule_id: ValidatorId,
prepared: Arc<dyn PreparedValidator>,
) -> Result<Self, BindError> {
if prepared.input_type() != InputType::of::<T>() || !prepared.dependency_specs().is_empty() {
return Err(BindError::new(BindErrorKind::PreparedSignatureMismatch).with_rule(rule_id));
}
Ok(Self {
prepared,
input: InputType::of::<T>(),
dependencies: &[],
rule_id,
})
}
pub(crate) fn new(
prepared: Arc<dyn PreparedValidator>,
signature: ValidatorSignature,
rule_id: ValidatorId,
) -> Self {
Self {
prepared,
input: signature.input(),
dependencies: signature.dependencies(),
rule_id,
}
}
pub fn validate(
&self,
value: ValidationValue<'_>,
context: &BoundValidationContext<'_>,
) -> Result<ValidationOutcome, ExecutionError> {
self.check_input(value)?;
self.check_dependencies(context)?;
let outcome = self
.prepared
.validate(value, context)
.map_err(|error| error.with_rule(self.rule_id))?;
outcome
.into_bound(self.rule_id)
.map_err(|_| ExecutionError::new(ExecutionErrorKind::AdapterContractViolation).with_rule(self.rule_id))
}
pub fn validate_named(
&self,
value: ValidationValue<'_>,
dependencies: &[NamedValidationDependency<'_>],
) -> Result<ValidationOutcome, ExecutionError> {
if self.dependencies.len() <= 1 {
for (index, dependency) in dependencies.iter().enumerate() {
if dependencies[..index]
.iter()
.any(|previous| previous.name == dependency.name)
{
return Err(ExecutionError::new(ExecutionErrorKind::DuplicateDependencyBinding)
.with_rule(self.rule_id)
.with_dependency(dependency.name));
}
if !self.dependencies.iter().any(|spec| spec.name() == dependency.name) {
return Err(ExecutionError::new(ExecutionErrorKind::UnknownDependencyBinding)
.with_rule(self.rule_id)
.with_dependency(dependency.name));
}
}
}
if self.dependencies.is_empty() {
return self.validate(value, &BoundValidationContext::new(&[]));
}
if self.dependencies.len() == 1 {
let spec = self.dependencies[0];
let dependency = dependencies
.iter()
.find(|dependency| dependency.name == spec.name())
.ok_or_else(|| {
ExecutionError::new(ExecutionErrorKind::MissingDependencyBinding)
.with_rule(self.rule_id)
.with_dependency(spec.name())
})?;
let values = [dependency.value];
return match dependency.path {
Some(path) => {
let paths = [path.clone()];
let context = BoundValidationContext::new_with_paths(&values, &paths)
.map_err(|error| error.with_rule(self.rule_id))?;
self.validate(value, &context)
}
None => self.validate(value, &BoundValidationContext::new(&values)),
};
}
self.validate_named_many(value, dependencies)
}
#[must_use]
#[inline]
pub const fn input_type(&self) -> InputType {
self.input
}
#[must_use]
#[inline]
pub const fn dependency_specs(&self) -> &'static [super::DependencySpec] {
self.dependencies
}
#[must_use]
#[inline]
pub const fn rule_id(&self) -> ValidatorId {
self.rule_id
}
fn validate_named_many(
&self,
value: ValidationValue<'_>,
dependencies: &[NamedValidationDependency<'_>],
) -> Result<ValidationOutcome, ExecutionError> {
const INLINE_SLOTS: usize = 8;
let mut inline_slots: [Option<NamedValidationDependency<'_>>; INLINE_SLOTS] = [None; INLINE_SLOTS];
let mut heap_slots = Vec::new();
let slots: &mut [Option<NamedValidationDependency<'_>>] = if self.dependencies.len() <= INLINE_SLOTS {
&mut inline_slots[..self.dependencies.len()]
} else {
heap_slots.resize(self.dependencies.len(), None);
heap_slots.as_mut_slice()
};
let mut has_path = false;
for dependency in dependencies.iter().copied() {
let Some(index) = self.dependencies.iter().position(|spec| spec.name() == dependency.name) else {
return Err(ExecutionError::new(ExecutionErrorKind::UnknownDependencyBinding)
.with_rule(self.rule_id)
.with_dependency(dependency.name));
};
if slots[index].replace(dependency).is_some() {
return Err(ExecutionError::new(ExecutionErrorKind::DuplicateDependencyBinding)
.with_rule(self.rule_id)
.with_dependency(dependency.name));
}
has_path |= dependency.path.is_some();
}
for (index, slot) in slots.iter().enumerate() {
if slot.is_none() {
return Err(ExecutionError::new(ExecutionErrorKind::MissingDependencyBinding)
.with_rule(self.rule_id)
.with_dependency(self.dependencies[index].name()));
}
}
if self.dependencies.len() <= INLINE_SLOTS {
let mut values = [ValidationValue::Missing; INLINE_SLOTS];
for (index, slot) in slots.iter().enumerate() {
values[index] = slot.as_ref().expect("checked slot").value;
}
let values = &values[..self.dependencies.len()];
if !has_path {
return self.validate(value, &BoundValidationContext::new(values));
}
let mut paths: [ValidationPath; INLINE_SLOTS] = std::array::from_fn(|_| ValidationPath::root());
for (index, slot) in slots.iter().enumerate() {
if let Some(path) = slot.as_ref().expect("checked slot").path {
paths[index] = path.clone();
}
}
let context = BoundValidationContext::new_with_paths(values, &paths[..self.dependencies.len()])
.map_err(|error| error.with_rule(self.rule_id))?;
return self.validate(value, &context);
}
let values: Vec<_> = slots
.iter()
.map(|slot| slot.as_ref().expect("checked slot").value)
.collect();
if !has_path {
return self.validate(value, &BoundValidationContext::new(&values));
}
let paths: Vec<_> = slots
.iter()
.map(|slot| {
slot.as_ref()
.expect("checked slot")
.path
.cloned()
.unwrap_or_else(ValidationPath::root)
})
.collect();
let context =
BoundValidationContext::new_with_paths(&values, &paths).map_err(|error| error.with_rule(self.rule_id))?;
self.validate(value, &context)
}
fn check_input(&self, value: ValidationValue<'_>) -> Result<(), ExecutionError> {
if value.is_missing() || !self.input.accepts(value) {
return Err(ExecutionError::new(ExecutionErrorKind::InputTypeMismatch).with_rule(self.rule_id));
}
Ok(())
}
fn check_dependencies(&self, context: &BoundValidationContext<'_>) -> Result<(), ExecutionError> {
context
.check_specs(self.dependencies)
.map_err(|error| error.with_rule(self.rule_id))
}
}
impl fmt::Debug for BoundValidator {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("BoundValidator")
.field("rule_id", &self.rule_id)
.field("input", &self.input)
.finish_non_exhaustive()
}
}