qubit_validator/lib.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-safe validation traits and immutable validator registries.
10//!
11//! Use [`Validator`] for direct typed validation. Use a prepared adapter when
12//! a domain rule must cross an erased input boundary. For configured execution,
13//! declare [`ValidatorSignature`] values in a [`ValidatorDescriptor`], register
14//! the descriptor, then bind it into a reusable [`BoundValidator`]. Aggregate
15//! bound results explicitly with [`ValidationReport`] when a caller needs a
16//! report; this crate does not schedule rules.
17//!
18//! A prepared validator is a lower-level entry point. Direct callers must
19//! provide a context that satisfies its declared dependency contract; bound
20//! execution performs the input and dependency checks before invoking it.
21//!
22//! # Direct typed validation
23//!
24//! ```
25//! use std::fmt;
26//!
27//! use qubit_validator::Validator;
28//!
29//! struct Positive;
30//! #[derive(Debug)]
31//! struct NotPositive;
32//!
33//! impl fmt::Display for NotPositive {
34//! fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35//! formatter.write_str("value must be positive")
36//! }
37//! }
38//! impl std::error::Error for NotPositive {}
39//!
40//! impl Validator<i32> for Positive {
41//! type Error = NotPositive;
42//!
43//! fn validate(&self, value: &i32, _: &()) -> Result<(), Self::Error> {
44//! if *value > 0 { Ok(()) } else { Err(NotPositive) }
45//! }
46//! }
47//!
48//! Positive.validate(&7, &()).unwrap();
49//! assert!(Positive.validate(&0, &()).is_err());
50//! ```
51//!
52//! # Configured validation and reporting
53//!
54//! ```
55//! use std::sync::Arc;
56//!
57//! use qubit_validator::{
58//! ArgumentReader, BindError, BoundValidationContext, InputType,
59//! PreparedValidator, RegistrationSource, ValidationOutcome, ValidationPath,
60//! ValidationReport, ValidationValue, Validator, ValidatorDescriptor, ValidatorId,
61//! ValidatorRegistration, ValidatorRegistry, ValidatorSignature, ViolationCode,
62//! prepare_text_validator,
63//! };
64//!
65//! struct NonBlank;
66//! #[derive(Debug)]
67//! struct Blank;
68//! impl std::fmt::Display for Blank {
69//! fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70//! formatter.write_str("blank name")
71//! }
72//! }
73//! impl std::error::Error for Blank {}
74//!
75//! impl Validator<str> for NonBlank {
76//! type Error = Blank;
77//!
78//! fn validate(&self, value: &str, _: &()) -> Result<(), Self::Error> {
79//! if value.trim().is_empty() { Err(Blank) } else { Ok(()) }
80//! }
81//! }
82//!
83//! fn prepare(_reader: &mut ArgumentReader<'_>) -> Result<Arc<dyn PreparedValidator>, BindError> {
84//! Ok(prepare_text_validator(NonBlank, |_| {
85//! qubit_validator::ViolationDraft::new(qubit_validator::ViolationCode::new("text.blank"))
86//! }))
87//! }
88//!
89//! static SIGNATURES: &[ValidatorSignature] = &[ValidatorSignature::new(InputType::Text, &[], prepare)];
90//! static DESCRIPTOR: ValidatorDescriptor = ValidatorDescriptor::new(SIGNATURES);
91//! static REGISTRATION: ValidatorRegistration = ValidatorRegistration::new(
92//! ValidatorId::new("profile.name.non_blank"),
93//! &DESCRIPTOR,
94//! RegistrationSource::new("profile-rules", "profile", "rules.rs", 1),
95//! );
96//!
97//! let registry = ValidatorRegistry::from_registrations([®ISTRATION])?;
98//! let rule = registry.bind("profile.name.non_blank", InputType::Text, &[])?;
99//! let outcome = rule.validate(
100//! ValidationValue::Text("Ada"),
101//! &BoundValidationContext::new(&[]),
102//! )?;
103//! let mut report = ValidationReport::new();
104//! report.record_outcome(0, ValidationPath::root().with_field("name"), outcome)?;
105//! assert!(report.is_valid());
106//!
107//! let invalid = rule.validate(
108//! ValidationValue::Text(" "),
109//! &BoundValidationContext::new(&[]),
110//! )?;
111//! assert!(matches!(invalid, ValidationOutcome::Invalid(_)));
112//! report.record_outcome(
113//! 1,
114//! ValidationPath::root().with_field("name"),
115//! invalid,
116//! )?;
117//! assert!(!report.is_valid());
118//! assert_eq!(report.violations()[0].code(), ViolationCode::new("text.blank"));
119//! # Ok::<(), Box<dyn std::error::Error>>(())
120//! ```
121
122#![deny(missing_docs)]
123#![deny(unsafe_code)]
124
125mod argument;
126mod binding;
127mod internal;
128mod registry;
129mod report;
130mod validator;
131mod validator_id;
132mod validator_id_error;
133mod validator_registry_error;
134
135#[cfg(feature = "inventory")]
136#[doc(hidden)]
137pub mod __private {
138 pub use inventory;
139}
140
141pub use argument::NamedValidationArgument;
142pub use argument::ValidationArgument;
143pub use binding::ArgumentReader;
144pub use binding::BindError;
145pub use binding::BindErrorKind;
146pub use binding::BoundValidationContext;
147pub use binding::BoundValidator;
148pub use binding::DependencySpec;
149pub use binding::ExecutionError;
150pub use binding::ExecutionErrorKind;
151pub use binding::InputType;
152pub use binding::NamedValidationDependency;
153pub use binding::PrepareFn;
154pub use binding::PreparedOutcome;
155pub use binding::PreparedValidator;
156pub use binding::ValidationOutcome;
157pub use binding::ValidationValue;
158pub use binding::ViolationDraft;
159pub use binding::prepare_contextual_text_validator;
160pub use binding::prepare_contextual_typed_validator;
161pub use binding::prepare_text_domain_rule;
162pub use binding::prepare_text_validator;
163pub use binding::prepare_text_with_context;
164pub use binding::prepare_typed_domain_rule;
165pub use binding::prepare_typed_validator;
166pub use binding::prepare_typed_with_context;
167pub use registry::RegistrationSource;
168pub use registry::ValidatorDescriptor;
169pub use registry::ValidatorRegistration;
170#[cfg(feature = "inventory")]
171#[doc(hidden)]
172pub use registry::ValidatorRegistrationFactory;
173pub use registry::ValidatorRegistry;
174pub use registry::ValidatorSignature;
175pub use report::FailureId;
176pub use report::PathSegment;
177pub use report::RecordedOutcome;
178pub use report::SkipReason;
179pub use report::SkippedValidation;
180pub use report::ValidationLimits;
181pub use report::ValidationOutcomeError;
182pub use report::ValidationPath;
183pub use report::ValidationReport;
184pub use report::Violation;
185pub use report::ViolationCode;
186pub use report::ViolationCodeError;
187pub use report::ViolationParam;
188pub use validator::Validator;
189pub use validator_id::ValidatorId;
190pub use validator_id_error::ValidatorIdError;
191pub use validator_registry_error::ValidatorRegistryError;