prost_protovalidate/
validator.rs1use std::sync::LazyLock;
2
3use prost_reflect::ReflectMessage;
4
5use crate::config::{NopFilter, ValidationConfig, ValidationOption, ValidatorOption};
6use crate::error::Error;
7
8mod builder;
9mod descriptor_set;
10pub(crate) mod editions;
11mod evaluator;
12
13mod lookups;
14pub(crate) mod rules;
15mod wire;
16
17use builder::Builder;
18use evaluator::MessageEvaluator;
19
20pub struct Validator {
25 builder: Builder,
26 config: ValidationConfig,
27}
28
29impl Validator {
30 #[must_use]
32 pub fn new() -> Self {
33 Self {
34 builder: Builder::new(),
35 config: ValidationConfig::default(),
36 }
37 }
38
39 #[must_use]
41 pub fn with_options(options: &[ValidatorOption]) -> Self {
42 let mut fail_fast = false;
43 let mut disable_lazy = false;
44 let mut allow_unknown_fields = false;
45 let mut additional_descriptor_sets = Vec::new();
46 let mut message_descriptors = Vec::new();
47 let mut now_fn = crate::config::default_now_fn();
48
49 for opt in options {
50 match opt {
51 ValidatorOption::FailFast => fail_fast = true,
52 ValidatorOption::DisableLazy => disable_lazy = true,
53 ValidatorOption::AllowUnknownFields => allow_unknown_fields = true,
54 ValidatorOption::NowFn(f) => now_fn = std::sync::Arc::clone(f),
55 ValidatorOption::AdditionalDescriptorSetBytes(bytes) => {
56 additional_descriptor_sets.push(bytes.clone());
57 }
58 ValidatorOption::MessageDescriptors(descriptors) => {
59 message_descriptors.extend(descriptors.iter().cloned());
60 }
61 }
62 }
63
64 let builder = Builder::with_config(
65 !disable_lazy,
66 allow_unknown_fields,
67 &additional_descriptor_sets,
68 );
69 for descriptor in &message_descriptors {
70 builder.preload(descriptor);
71 }
72
73 Self {
74 builder,
75 config: ValidationConfig {
76 fail_fast,
77 filter: std::sync::Arc::new(NopFilter),
78 now_fn,
79 },
80 }
81 }
82
83 pub fn validate<M: ReflectMessage>(&self, msg: &M) -> Result<(), Error> {
90 self.validate_with(msg, &[])
91 }
92
93 pub fn validate_with<M: ReflectMessage>(
100 &self,
101 msg: &M,
102 options: &[ValidationOption],
103 ) -> Result<(), Error> {
104 let dynamic = msg.transcode_to_dynamic();
105 let descriptor = dynamic.descriptor();
106 let eval = self.builder.load_or_build(&descriptor);
107 let cfg = effective_config(&self.config, options);
108 eval.evaluate_message(&dynamic, &cfg)
109 }
110}
111
112fn effective_config(base: &ValidationConfig, options: &[ValidationOption]) -> ValidationConfig {
113 let mut cfg = ValidationConfig {
114 fail_fast: base.fail_fast,
115 filter: std::sync::Arc::clone(&base.filter),
116 now_fn: std::sync::Arc::clone(&base.now_fn),
117 };
118
119 for option in options {
120 match option {
121 ValidationOption::FailFast => cfg.fail_fast = true,
122 ValidationOption::Filter(filter) => cfg.filter = std::sync::Arc::clone(filter),
123 ValidationOption::NowFn(now_fn) => cfg.now_fn = std::sync::Arc::clone(now_fn),
124 }
125 }
126
127 cfg
128}
129
130impl Default for Validator {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136static GLOBAL_VALIDATOR: LazyLock<Validator> = LazyLock::new(Validator::new);
137
138pub fn validate<M: ReflectMessage>(msg: &M) -> Result<(), Error> {
149 GLOBAL_VALIDATOR.validate(msg)
150}
151
152#[cfg(test)]
153mod tests {
154 use std::sync::Arc;
155 use std::sync::atomic::{AtomicBool, Ordering};
156
157 use pretty_assertions::assert_eq;
158 use prost_reflect::{DynamicMessage, MessageDescriptor, ReflectMessage};
159
160 use super::*;
161 use crate::config::Filter;
162
163 struct DenyFilter;
164
165 impl Filter for DenyFilter {
166 fn should_validate(
167 &self,
168 _message: &DynamicMessage,
169 _descriptor: &MessageDescriptor,
170 ) -> bool {
171 false
172 }
173 }
174
175 struct RuntimeFilter {
176 seen_required_true: Arc<AtomicBool>,
177 }
178
179 impl Filter for RuntimeFilter {
180 fn should_validate(
181 &self,
182 message: &DynamicMessage,
183 _descriptor: &MessageDescriptor,
184 ) -> bool {
185 let Some(required) = message.descriptor().get_field_by_name("required") else {
186 return true;
187 };
188
189 let required_is_true = message.get_field(&required).as_bool() == Some(true);
190 if required_is_true {
191 self.seen_required_true.store(true, Ordering::Relaxed);
192 }
193 !required_is_true
194 }
195 }
196
197 #[test]
198 fn validation_options_override_call_config_only() {
199 let base = ValidationConfig::default();
200 let now_fn: Arc<dyn Fn() -> prost_types::Timestamp + Send + Sync> =
201 Arc::new(|| prost_types::Timestamp {
202 seconds: 123,
203 nanos: 456,
204 });
205 let options = vec![
206 ValidationOption::FailFast,
207 ValidationOption::Filter(Arc::new(DenyFilter)),
208 ValidationOption::NowFn(Arc::clone(&now_fn)),
209 ];
210
211 let effective = effective_config(&base, &options);
212 let descriptor = prost_protovalidate_types::DESCRIPTOR_POOL
213 .get_message_by_name("buf.validate.FieldRules")
214 .expect("message descriptor exists");
215 let dynamic = prost_reflect::DynamicMessage::new(descriptor.clone());
216
217 assert!(effective.fail_fast);
218 assert_eq!((effective.now_fn)().seconds, 123);
219 assert!(!effective.filter.should_validate(&dynamic, &descriptor));
220
221 assert!(!base.fail_fast);
222 }
223
224 #[test]
225 fn validate_with_keeps_existing_validate_behavior() {
226 let validator = Validator::new();
227 let msg = prost_protovalidate_types::BoolRules::default();
228
229 assert!(validator.validate(&msg).is_ok());
230 assert!(
231 validator
232 .validate_with(&msg, &[ValidationOption::FailFast])
233 .is_ok()
234 );
235 }
236
237 #[test]
238 fn invalid_additional_descriptor_set_surfaces_compilation_error() {
239 let validator =
240 Validator::with_options(&[ValidatorOption::AdditionalDescriptorSetBytes(vec![
241 0x01, 0x02, 0x03,
242 ])]);
243 let msg = prost_protovalidate_types::BoolRules::default();
244
245 match validator.validate(&msg) {
246 Ok(()) => panic!("invalid descriptor set bytes must fail validator initialization"),
247 Err(Error::Compilation(err)) => {
248 assert!(
249 err.cause
250 .contains("failed to decode additional descriptor set at index 0")
251 );
252 }
253 Err(other) => panic!("unexpected error type: {other}"),
254 }
255 }
256
257 #[test]
258 fn invalid_additional_descriptor_set_never_panics() {
259 let result = std::panic::catch_unwind(|| {
260 let validator = Validator::with_options(&[
261 ValidatorOption::AdditionalDescriptorSetBytes(vec![0x01, 0x02, 0x03]),
262 ]);
263 let msg = prost_protovalidate_types::BoolRules::default();
264 validator.validate(&msg)
265 });
266
267 let validation_result = result.expect("invalid descriptor sets must not panic");
268 match validation_result {
269 Ok(()) => panic!("invalid descriptor set bytes must fail validator initialization"),
270 Err(Error::Compilation(err)) => {
271 assert!(
272 err.cause
273 .contains("failed to decode additional descriptor set at index 0")
274 );
275 }
276 Err(other) => panic!("unexpected error type: {other}"),
277 }
278 }
279
280 #[test]
281 fn valid_additional_descriptor_set_keeps_validator_operational() {
282 let descriptor_bytes = Vec::new();
283 let validator = Validator::with_options(&[ValidatorOption::AdditionalDescriptorSetBytes(
284 descriptor_bytes,
285 )]);
286 let msg = prost_protovalidate_types::BoolRules::default();
287
288 assert!(validator.validate(&msg).is_ok());
289 }
290
291 #[test]
292 fn message_descriptor_preload_supports_disable_lazy_with_known_messages() {
293 let descriptor = prost_protovalidate_types::BoolRules::default().descriptor();
294 let validator = Validator::with_options(&[
295 ValidatorOption::MessageDescriptors(vec![descriptor]),
296 ValidatorOption::DisableLazy,
297 ]);
298 let msg = prost_protovalidate_types::BoolRules::default();
299
300 assert!(validator.validate(&msg).is_ok());
302 }
303
304 #[test]
305 fn runtime_filter_can_skip_based_on_message_content() {
306 let validator = Validator::new();
307 let descriptor = prost_protovalidate_types::FieldRules::default().descriptor();
308 let mut msg = prost_reflect::DynamicMessage::new(descriptor.clone());
309 let seen_required_true = Arc::new(AtomicBool::new(false));
310 let required = descriptor
311 .get_field_by_name("required")
312 .expect("required field exists");
313 msg.set_field(&required, prost_reflect::Value::Bool(true));
314
315 assert!(
316 validator
317 .validate_with(
318 &msg,
319 &[ValidationOption::Filter(Arc::new(RuntimeFilter {
320 seen_required_true: Arc::clone(&seen_required_true),
321 }))],
322 )
323 .is_ok()
324 );
325 assert!(seen_required_true.load(Ordering::Relaxed));
326 }
327}