Skip to main content

prost_protovalidate/
config.rs

1use prost_reflect::{
2    DynamicMessage, FieldDescriptor, MessageDescriptor, OneofDescriptor, ReflectMessage,
3};
4use prost_types::Timestamp;
5use std::sync::Arc;
6
7/// Options for configuring the `Validator` at construction time.
8#[non_exhaustive]
9pub enum ValidatorOption {
10    /// Stop validation on the first violation instead of collecting all.
11    FailFast,
12
13    /// Disable lazy compilation: all known message types must be
14    /// pre-registered, and unknown types will produce a compilation error.
15    DisableLazy,
16
17    /// Override the function used to populate `now` in timestamp-based rules/CEL.
18    NowFn(Arc<dyn Fn() -> Timestamp + Send + Sync>),
19
20    /// Additional encoded `FileDescriptorSet` payloads used to resolve
21    /// custom/proprietary rule extensions at runtime.
22    AdditionalDescriptorSetBytes(Vec<u8>),
23
24    /// Preload evaluators for these descriptors at validator construction time.
25    /// Useful with `DisableLazy` to allow a fixed set of message types.
26    MessageDescriptors(Vec<MessageDescriptor>),
27
28    /// Allow unknown fields in constraint messages instead of producing a
29    /// compilation error. Useful when working with newer constraint protos
30    /// that contain fields not yet recognized by this library.
31    AllowUnknownFields,
32}
33
34/// Options for configuring a single `Validator::validate_with` call.
35#[non_exhaustive]
36pub enum ValidationOption {
37    /// Stop validation on the first violation instead of collecting all.
38    FailFast,
39    /// Override the filter for this validation call.
40    Filter(Arc<dyn Filter>),
41    /// Override the function used to populate `now` in timestamp-based rules/CEL.
42    NowFn(Arc<dyn Fn() -> Timestamp + Send + Sync>),
43}
44
45/// Controls which fields/messages are validated.
46pub trait Filter: Send + Sync {
47    /// Returns true if the given message should be validated.
48    fn should_validate(&self, message: &DynamicMessage, descriptor: &MessageDescriptor) -> bool;
49
50    /// Returns true if the given field should be validated.
51    /// Defaults to message-level filtering for compatibility.
52    fn should_validate_field(&self, message: &DynamicMessage, _field: &FieldDescriptor) -> bool {
53        let descriptor = message.descriptor();
54        self.should_validate(message, &descriptor)
55    }
56
57    /// Returns true if the given oneof should be validated.
58    /// Defaults to message-level filtering for compatibility.
59    fn should_validate_oneof(&self, message: &DynamicMessage, _oneof: &OneofDescriptor) -> bool {
60        let descriptor = message.descriptor();
61        self.should_validate(message, &descriptor)
62    }
63}
64
65/// A filter that always validates everything.
66pub(crate) struct NopFilter;
67
68impl Filter for NopFilter {
69    fn should_validate(&self, _message: &DynamicMessage, _descriptor: &MessageDescriptor) -> bool {
70        true
71    }
72}
73
74/// Runtime configuration passed to evaluators during validation.
75pub(crate) struct ValidationConfig {
76    pub fail_fast: bool,
77    pub filter: Arc<dyn Filter>,
78    pub now_fn: Arc<dyn Fn() -> Timestamp + Send + Sync>,
79}
80
81impl Default for ValidationConfig {
82    fn default() -> Self {
83        Self {
84            fail_fast: false,
85            filter: Arc::new(NopFilter),
86            now_fn: Arc::new(|| {
87                let now = std::time::SystemTime::now()
88                    .duration_since(std::time::UNIX_EPOCH)
89                    .unwrap_or_default();
90                // Both casts are semantically safe:
91                // - as_secs() since UNIX_EPOCH fits in i64 for billions of years
92                // - subsec_nanos() is always < 1_000_000_000 which fits in i32
93                #[allow(clippy::cast_possible_wrap)]
94                Timestamp {
95                    seconds: now.as_secs() as i64,
96                    nanos: now.subsec_nanos() as i32,
97                }
98            }),
99        }
100    }
101}