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