Skip to main content

prost_protovalidate/
validator.rs

1use 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
20/// Thread-safe validator for Protocol Buffer messages.
21///
22/// Validates messages against `buf.validate` rules extracted from proto descriptors.
23/// Evaluators are compiled lazily and cached for reuse.
24pub struct Validator {
25    builder: Builder,
26    config: ValidationConfig,
27}
28
29impl Validator {
30    /// Create a new `Validator` with default options.
31    #[must_use]
32    pub fn new() -> Self {
33        Self {
34            builder: Builder::new(),
35            config: ValidationConfig::default(),
36        }
37    }
38
39    /// Create a new `Validator` with the given options.
40    #[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    /// Validate a message against its `buf.validate` rules.
84    ///
85    /// # Errors
86    ///
87    /// Returns an `Error` containing all constraint violations found, or a
88    /// compilation/runtime error if rule evaluation fails.
89    pub fn validate<M: ReflectMessage>(&self, msg: &M) -> Result<(), Error> {
90        self.validate_with(msg, &[])
91    }
92
93    /// Validate a message with per-call validation options.
94    ///
95    /// # Errors
96    ///
97    /// Returns an `Error` containing all constraint violations found, or a
98    /// compilation/runtime error if rule evaluation fails.
99    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
138/// Validate a message using a global `Validator` instance.
139///
140/// This is a convenience function that uses a shared, lazily-initialized
141/// validator. For lower memory usage, prefer using a single `Validator`
142/// instance rather than creating multiple instances.
143///
144/// # Errors
145///
146/// Returns an `Error` containing all constraint violations found, or a
147/// compilation/runtime error if rule evaluation fails.
148pub 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        // BoolRules has no constraints on itself, so validation should pass
301        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}