Skip to main content

qubit_validator/binding/
prepared_validator.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! Type-erased prepared validator instances.
10
11use std::sync::Arc;
12
13use super::ArgumentReader;
14use super::BindError;
15use super::BoundValidationContext;
16use super::DependencySpec;
17use super::ExecutionError;
18use super::InputType;
19use super::PreparedOutcome;
20use super::ValidationValue;
21
22/// A configured, immutable validator instance safe to share between calls.
23///
24/// Direct calls are a lower-level execution path. The caller must supply a
25/// target matching [`Self::input_type`] and a context matching
26/// [`Self::dependency_specs`] in count, order, type, and optional-value
27/// representation. When dependency paths are provided, they must correspond
28/// to the value slots. Constructing a [`BoundValidationContext`] does not
29/// validate it against these declarations.
30///
31/// This layer does not interpret error paths or add a target prefix. When
32/// integrated with model validation, ordinary rule errors use target-relative
33/// paths; an error for a dependency should name a declared slot and use that
34/// dependency's coordinate system. Model navigation and final path handling
35/// belong to the integrating layer.
36///
37/// For configured execution, bind through a [`crate::ValidatorDescriptor`] or
38/// [`crate::ValidatorRegistry`] and call [`crate::BoundValidator::validate`].
39/// That boundary checks the declared shapes and outcome invariants and attaches
40/// the bound rule ID to violation drafts. A direct call returns a
41/// [`PreparedOutcome`] whose drafts do not yet carry the bound rule ID. Custom
42/// implementations must document any further preconditions rather than assuming
43/// the bound boundary has already run.
44///
45/// The direct-call example below uses a matching text target and no
46/// dependencies.
47///
48/// # Examples
49///
50/// ```
51/// use std::sync::Arc;
52///
53/// use qubit_validator::{
54///     BoundValidationContext, ExecutionError, PreparedOutcome, PreparedValidator,
55///     ValidationValue,
56/// };
57///
58/// struct Accept;
59/// impl PreparedValidator for Accept {
60///     fn input_type(&self) -> qubit_validator::InputType {
61///         qubit_validator::InputType::Text
62///     }
63///
64///     fn dependency_specs(&self) -> &'static [qubit_validator::DependencySpec] {
65///         &[]
66///     }
67///
68///     fn validate(
69///         &self,
70///         _: ValidationValue<'_>,
71///         _: &BoundValidationContext<'_>,
72///     ) -> Result<PreparedOutcome, ExecutionError> {
73///         Ok(PreparedOutcome::valid())
74///     }
75/// }
76///
77/// let prepared: Arc<dyn PreparedValidator> = Arc::new(Accept);
78/// let result = prepared.validate(
79///     ValidationValue::Text("ready"),
80///     &BoundValidationContext::new(&[]),
81/// );
82/// assert_eq!(result?, PreparedOutcome::valid());
83/// # Ok::<(), ExecutionError>(())
84/// ```
85pub trait PreparedValidator: Send + Sync {
86    /// Returns the exact erased target shape accepted by this implementation.
87    ///
88    /// # Returns
89    ///
90    /// The input shape accepted by this validator.
91    #[must_use]
92    fn input_type(&self) -> InputType;
93
94    /// Returns dependency slots accepted in execution order.
95    ///
96    /// # Returns
97    ///
98    /// A static slice of dependency declarations in the order expected by
99    /// [`Self::validate`].
100    #[must_use]
101    fn dependency_specs(&self) -> &'static [DependencySpec];
102
103    /// Validates one erased input with its ordered dependency context.
104    ///
105    /// Direct callers must satisfy the input and dependency declarations
106    /// described by this trait. Use [`crate::BoundValidator::validate`] for
107    /// the checked binding boundary; implementations are not required to
108    /// duplicate all of that boundary's checks.
109    ///
110    /// # Parameters
111    ///
112    /// - `value`: Borrowed target matching [`Self::input_type`].
113    /// - `context`: Borrowed dependency slots satisfying
114    ///   [`Self::dependency_specs`], with corresponding paths when paths are
115    ///   supplied.
116    ///
117    /// # Returns
118    ///
119    /// Returns the prepared valid or invalid outcome on successful execution.
120    /// Invalid drafts have no bound rule ID yet. Skipping is recorded
121    /// separately by the caller's orchestration policy as a
122    /// [`crate::ValidationOutcome`].
123    ///
124    /// # Errors
125    ///
126    /// Returns an execution error when this implementation cannot process the
127    /// input or context. A domain rejection is an invalid outcome rather than
128    /// an execution error. Error paths are returned unchanged by this layer.
129    /// An error return is not a guarantee that every direct-call precondition
130    /// has been checked.
131    fn validate(
132        &self,
133        value: ValidationValue<'_>,
134        context: &BoundValidationContext<'_>,
135    ) -> Result<PreparedOutcome, ExecutionError>;
136}
137
138/// Constructs an owned prepared validator from declaration parameters.
139///
140/// When binding through a descriptor or registry, the binding layer rejects
141/// duplicate parameter names before calling the factory and rejects unconsumed
142/// parameters after it returns. The factory's reader calls decide which
143/// parameters are required and reject invalid types or ranges. Calling the
144/// factory directly does not provide the binding layer's checks.
145///
146/// # Examples
147///
148/// ```
149/// use std::sync::Arc;
150///
151/// use qubit_validator::{
152///     ArgumentReader, BindError, BoundValidationContext, ExecutionError,
153///     PrepareFn, PreparedOutcome, PreparedValidator, ValidationValue,
154/// };
155///
156/// struct Accept;
157/// impl PreparedValidator for Accept {
158///     fn input_type(&self) -> qubit_validator::InputType {
159///         qubit_validator::InputType::Text
160///     }
161///
162///     fn dependency_specs(&self) -> &'static [qubit_validator::DependencySpec] {
163///         &[]
164///     }
165///
166///     fn validate(
167///         &self,
168///         _: ValidationValue<'_>,
169///         _: &BoundValidationContext<'_>,
170///     ) -> Result<PreparedOutcome, ExecutionError> {
171///         Ok(PreparedOutcome::valid())
172///     }
173/// }
174///
175/// fn prepare(_reader: &mut ArgumentReader<'_>) -> Result<Arc<dyn PreparedValidator>, BindError> {
176///     Ok(Arc::new(Accept))
177/// }
178///
179/// let factory: PrepareFn = prepare;
180/// let mut reader = ArgumentReader::new(&[])?;
181/// let prepared = factory(&mut reader)?;
182/// reader.finish()?;
183/// assert_eq!(prepared.validate(
184///     ValidationValue::Text("ready"),
185///     &BoundValidationContext::new(&[]),
186/// )?, PreparedOutcome::valid());
187/// # Ok::<(), Box<dyn std::error::Error>>(())
188/// ```
189pub type PrepareFn = for<'a> fn(&mut ArgumentReader<'a>) -> Result<Arc<dyn PreparedValidator>, BindError>;