pub struct ViolationCode(/* private fields */);Expand description
A stable violation code using the point-separated ASCII protocol.
The value is non-empty and consists of one or more dot-separated segments. Each segment starts with an ASCII letter and continues with ASCII letters, digits, or underscores. Empty segments, whitespace, non-ASCII letters, and hyphens are rejected. No trimming or case normalization is performed.
§Examples
use qubit_validator::ViolationCode;
use qubit_validator::ViolationCodeError;
let code = ViolationCode::try_new("text.length.minimum")?;
assert_eq!(code.as_str(), "text.length.minimum");
let error = ViolationCode::try_new("text..minimum").unwrap_err();
assert_eq!(error, ViolationCodeError::EmptySegment);
assert_eq!(
ViolationCode::try_new("text.min-length"),
Err(ViolationCodeError::InvalidSegment),
);Implementations§
Source§impl ViolationCode
impl ViolationCode
Sourcepub const fn new(value: &'static str) -> Self
pub const fn new(value: &'static str) -> Self
Creates a code from a valid static string.
§Parameters
value: A static string containing the complete protocol value.
§Returns
The validated violation code.
§Panics
Panics when value violates the syntax documented on ViolationCode.
Examples found in repository?
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}Sourcepub const fn try_new(value: &'static str) -> Result<Self, ViolationCodeError>
pub const fn try_new(value: &'static str) -> Result<Self, ViolationCodeError>
Validates and creates a code.
§Parameters
value: A static string containing the complete protocol value.
§Returns
The validated violation code when value follows the protocol.
§Errors
Returns ViolationCodeError::Empty for an empty string,
ViolationCodeError::EmptySegment for an empty dot-separated segment,
or ViolationCodeError::InvalidSegment for a segment with invalid
characters or an invalid first character.