qubit-validator 0.1.2

Type-safe validation traits and registries for Qubit applications
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

//! Private adapter for text validators with checked dependency context.

use std::error::Error;

use super::super::BoundValidationContext;
use super::super::DependencySpec;
use super::super::ExecutionError;
use super::super::ExecutionErrorKind;
use super::super::InputType;
use super::super::PreparedOutcome;
use super::super::PreparedValidator;
use super::super::ValidationValue;
use crate::Validator;
use crate::ViolationDraft;

/// Owns a contextual text validator and the mapper for its domain errors.
///
/// # Type Parameters
///
/// - `V`: Validator invoked with checked text and dependency context; for the
///   [`PreparedValidator`] implementation it must accept `str` with a
///   [`BoundValidationContext`] of any borrow lifetime and satisfy the `Send +
///   Sync + 'static` bounds.
/// - `M`: Mapper from the validator's domain error to a safe violation draft;
///   for the [`PreparedValidator`] implementation it must satisfy the `Send +
///   Sync + 'static` bounds.
pub(in crate::binding) struct ContextualTextValidatorAdapter<V, M> {
    /// Validator invoked with a checked text value and dependency context.
    validator: V,
    /// Mapper that creates a safe violation draft from the domain error.
    map_error: M,
    /// Declared dependency slots consumed by the validator.
    dependencies: &'static [DependencySpec],
}

impl<V, M> ContextualTextValidatorAdapter<V, M> {
    /// Creates an adapter from a validator, its domain error mapper, and its
    /// declared dependency slots.
    ///
    /// The dependency list is borrowed for the adapter's entire lifetime so
    /// the binding layer can verify and provide the values before validation.
    ///
    /// # Parameters
    ///
    /// - `validator`: Context-aware validator invoked for checked text input.
    /// - `map_error`: Converts a domain error into a violation draft.
    /// - `dependencies`: Static dependency slots required by the validator.
    ///
    /// # Returns
    ///
    /// An adapter that uses the supplied validator, mapper, and dependency
    /// declarations.
    #[must_use]
    #[inline]
    pub(in crate::binding) const fn new(validator: V, map_error: M, dependencies: &'static [DependencySpec]) -> Self {
        Self {
            validator,
            map_error,
            dependencies,
        }
    }
}

impl<V, E, M> PreparedValidator for ContextualTextValidatorAdapter<V, M>
where
    V: for<'a> Validator<str, BoundValidationContext<'a>, Error = E> + Send + Sync + 'static,
    E: Error + 'static,
    M: Fn(E) -> ViolationDraft + Send + Sync + 'static,
{
    /// Returns the text shape accepted by this validator.
    ///
    /// # Returns
    ///
    /// The input shape is always text.
    #[inline]
    fn input_type(&self) -> InputType {
        InputType::Text
    }

    /// Returns the dependency slots declared for this validator.
    ///
    /// # Returns
    ///
    /// The static dependency declarations in the order expected by the
    /// validator.
    #[inline]
    fn dependency_specs(&self) -> &'static [DependencySpec] {
        self.dependencies
    }

    /// Validates checked text with its dependency context.
    ///
    /// A validator domain error becomes an invalid outcome containing one
    /// mapped violation. An execution error is returned when `value` is not
    /// text; this indicates the binding supplied a value with the wrong shape.
    ///
    /// # Errors
    ///
    /// Returns an execution error when `value` is not text. Domain errors from
    /// the validator are represented as invalid outcomes instead.
    ///
    /// # Returns
    ///
    /// A valid outcome when the validator accepts the text, or an invalid
    /// outcome containing the mapped violation when it rejects the text.
    ///
    /// # Parameters
    ///
    /// - `value`: Erased input that must contain text.
    /// - `context`: Dependency values matching this adapter's declared slots.
    fn validate(
        &self,
        value: ValidationValue<'_>,
        context: &BoundValidationContext<'_>,
    ) -> Result<PreparedOutcome, ExecutionError> {
        let text = value
            .as_text()
            .ok_or_else(|| ExecutionError::new(ExecutionErrorKind::InputTypeMismatch))?;
        match self.validator.validate(text, context) {
            Ok(()) => Ok(PreparedOutcome::valid()),
            Err(error) => Ok(PreparedOutcome::invalid(vec![(self.map_error)(error)])
                .expect("a domain error maps to one violation draft")),
        }
    }
}