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>;