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 unit context.

use super::super::BoundValidationContext;
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 text validator and the mapper for its domain errors.
///
/// # Type Parameters
///
/// - `V`: Validator for borrowed text with unit context.
/// - `M`: Mapper that converts the validator's error into a violation draft.
pub(in crate::binding) struct TextValidatorAdapter<V, M> {
    /// Typed validator invoked after the erased input is checked.
    validator: V,
    /// Mapper that creates a safe violation draft from the domain error.
    map_error: M,
}

impl<V, M> TextValidatorAdapter<V, M> {
    /// Creates an adapter from a validator and its domain error mapper.
    ///
    /// # Parameters
    ///
    /// - `validator`: Domain validator invoked for each accepted text value.
    /// - `map_error`: Mapper that converts a domain error into a safe draft.
    ///
    /// # Returns
    ///
    /// The adapter owning `validator` and `map_error`.
    #[must_use]
    #[inline]
    pub(in crate::binding) const fn new(validator: V, map_error: M) -> Self {
        Self { validator, map_error }
    }
}

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

    /// Returns that this unit-context adapter has no dependency slots.
    ///
    /// # Returns
    ///
    /// An empty slice because this adapter uses unit context.
    #[inline]
    fn dependency_specs(&self) -> &'static [super::super::DependencySpec] {
        &[]
    }

    /// Validates a text value and converts a domain error into one draft
    /// violation.
    ///
    /// A successful domain validation produces a valid outcome. A domain
    /// validation failure produces an invalid outcome containing the mapped
    /// violation.
    ///
    /// # Parameters
    ///
    /// - `value`: Erased input; only its text variant is accepted.
    /// - `_`: Context ignored because this adapter declares no dependencies.
    ///
    /// # Errors
    ///
    /// Returns [`ExecutionErrorKind::InputTypeMismatch`] when `value` is a
    /// typed or missing value instead of text.
    ///
    /// # Returns
    ///
    /// Returns the outcome of the domain validator, or an execution error if
    /// the erased input has the wrong shape.
    fn validate(
        &self,
        value: ValidationValue<'_>,
        _: &BoundValidationContext<'_>,
    ) -> Result<PreparedOutcome, ExecutionError> {
        let text = value
            .as_text()
            .ok_or_else(|| ExecutionError::new(ExecutionErrorKind::InputTypeMismatch))?;
        match self.validator.validate(text, &()) {
            Ok(()) => Ok(PreparedOutcome::valid()),
            Err(error) => Ok(PreparedOutcome::invalid(vec![(self.map_error)(error)])
                .expect("a domain error maps to one violation draft")),
        }
    }
}