local_registry/
local_registry.rs1use std::error::Error;
14use std::fmt;
15use std::sync::Arc;
16
17use qubit_validator::ArgumentReader;
18use qubit_validator::BindError;
19use qubit_validator::BindErrorKind;
20use qubit_validator::BoundValidationContext;
21use qubit_validator::DependencySpec;
22use qubit_validator::ExecutionError;
23use qubit_validator::InputType;
24use qubit_validator::NamedValidationArgument;
25use qubit_validator::NamedValidationDependency;
26use qubit_validator::PreparedOutcome;
27use qubit_validator::PreparedValidator;
28use qubit_validator::RegistrationSource;
29use qubit_validator::ValidationArgument;
30use qubit_validator::ValidationOutcome;
31use qubit_validator::ValidationValue;
32use qubit_validator::Validator;
33use qubit_validator::ValidatorDescriptor;
34use qubit_validator::ValidatorId;
35use qubit_validator::ValidatorRegistration;
36use qubit_validator::ValidatorRegistry;
37use qubit_validator::ValidatorSignature;
38use qubit_validator::ViolationCode;
39use qubit_validator::ViolationDraft;
40use qubit_validator::prepare_text_validator;
41#[cfg(feature = "inventory")]
42use qubit_validator::register_validator;
43
44#[derive(Debug)]
45struct BlankText;
46
47impl fmt::Display for BlankText {
48 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
49 formatter.write_str("text must not be blank")
50 }
51}
52
53impl Error for BlankText {}
54
55struct NonBlank;
56
57impl Validator<str> for NonBlank {
58 type Error = BlankText;
59
60 fn validate(&self, value: &str, _context: &()) -> Result<(), Self::Error> {
61 if value.trim().is_empty() {
62 Err(BlankText)
63 } else {
64 Ok(())
65 }
66 }
67}
68
69fn prepare_non_blank(_reader: &mut ArgumentReader<'_>) -> Result<Arc<dyn PreparedValidator>, BindError> {
70 Ok(prepare_text_validator(NonBlank, |_| {
71 ViolationDraft::new(ViolationCode::new("text.blank"))
72 }))
73}
74
75static SIGNATURES: &[ValidatorSignature] = &[ValidatorSignature::new(InputType::Text, &[], prepare_non_blank)];
76static DESCRIPTOR: ValidatorDescriptor = ValidatorDescriptor::new(SIGNATURES);
77
78#[cfg(feature = "inventory")]
79register_validator!(id = "text.non_blank.global", descriptor = &DESCRIPTOR);
80
81static DEPENDENCIES: &[DependencySpec] = &[
82 DependencySpec::new("minimum", InputType::Text, false),
83 DependencySpec::new("maximum", InputType::Text, false),
84];
85
86struct CheckDependencySlots;
87
88impl PreparedValidator for CheckDependencySlots {
89 fn input_type(&self) -> InputType {
90 InputType::Text
91 }
92 fn dependency_specs(&self) -> &'static [DependencySpec] {
93 DEPENDENCIES
94 }
95
96 fn validate(
97 &self,
98 _: ValidationValue<'_>,
99 context: &BoundValidationContext<'_>,
100 ) -> Result<PreparedOutcome, ExecutionError> {
101 let valid = context.value(0)?.as_text() == Some("minimum") && context.value(1)?.as_text() == Some("maximum");
102 Ok(if valid {
103 PreparedOutcome::Valid
104 } else {
105 PreparedOutcome::Invalid(vec![ViolationDraft::new(ViolationCode::new(
106 "text.dependencies_mismatch",
107 ))])
108 })
109 }
110}
111
112fn prepare_dependency_slots(_reader: &mut ArgumentReader<'_>) -> Result<Arc<dyn PreparedValidator>, BindError> {
113 Ok(Arc::new(CheckDependencySlots))
114}
115
116static DEPENDENCY_SIGNATURES: &[ValidatorSignature] = &[ValidatorSignature::new(
117 InputType::Text,
118 DEPENDENCIES,
119 prepare_dependency_slots,
120)];
121static DEPENDENCY_DESCRIPTOR: ValidatorDescriptor = ValidatorDescriptor::new(DEPENDENCY_SIGNATURES);
122
123fn assert_dependency_specs_come_from_signature() -> Result<(), ExecutionError> {
124 let bound = DEPENDENCY_DESCRIPTOR
125 .bind(ValidatorId::new("text.dependent"), 0, &[])
126 .expect("signature binds");
127 assert_eq!(bound.dependency_specs(), DEPENDENCIES);
128 let dependencies = [
129 NamedValidationDependency::new("maximum", ValidationValue::Text("maximum")),
130 NamedValidationDependency::new("minimum", ValidationValue::Text("minimum")),
131 ];
132 assert_eq!(
133 bound.validate_named(ValidationValue::Text("target"), &dependencies)?,
134 ValidationOutcome::Valid,
135 );
136 Ok(())
137}
138
139fn assert_parameters_are_consumed_once() -> Result<(), BindError> {
140 let args = [NamedValidationArgument::new("limit", ValidationArgument::Unsigned(10))];
141 let mut reader = ArgumentReader::new(&args)?;
142 assert_eq!(reader.required_u32("limit")?, 10);
143 let error = reader
144 .required_u32("limit")
145 .expect_err("a parameter cannot be read twice");
146 assert_eq!(error.kind(), BindErrorKind::ParameterAlreadyConsumed);
147 Ok(())
148}
149
150fn main() -> Result<(), Box<dyn Error>> {
151 let registration = ValidatorRegistration::new(
152 ValidatorId::new("text.non_blank"),
153 &DESCRIPTOR,
154 RegistrationSource::new(env!("CARGO_PKG_NAME"), module_path!(), file!(), line!()),
155 );
156 let registry = ValidatorRegistry::from_registrations([registration])?;
157
158 assert!(registry.get("text.non_blank").is_some());
159 let validator = registry.bind("text.non_blank", InputType::Text, &[])?;
160 let context = BoundValidationContext::new(&[]);
161
162 let valid = validator.validate(ValidationValue::Text("Ada"), &context)?;
163 assert_eq!(valid, ValidationOutcome::Valid);
164
165 let invalid = validator.validate(ValidationValue::Text(" "), &context)?;
166 let ValidationOutcome::Invalid(violations) = invalid else {
167 panic!("blank text must be invalid");
168 };
169 assert_eq!(violations.len(), 1);
170 assert_eq!(violations[0].code(), ViolationCode::new("text.blank"));
171
172 assert_dependency_specs_come_from_signature()?;
173 assert_parameters_are_consumed_once()?;
174
175 #[cfg(feature = "inventory")]
176 {
177 let global = ValidatorRegistry::try_global()?;
178 assert!(global.get("text.non_blank.global").is_some());
179 }
180
181 Ok(())
182}
183
184#[cfg(test)]
185mod tests {
186 use super::main;
187
188 #[test]
191 fn test_example_completes_successfully() {
192 main().expect("the documented example should complete");
193 }
194}