Skip to main content

protify/validators/
message.rs

1mod builder;
2pub use builder::MessageValidatorBuilder;
3
4use super::*;
5
6/// Trait that executes validation on a message, if the latter had validators assigned to it via the attributes in [`proto_message`].
7pub trait ValidatedMessage: ProtoValidation + Default + Clone {
8	/// Executes validation on this message, triggering the validators that have been assigned to it
9	/// via macro attributes, if there are any.
10	///
11	/// Unlike the [`validate`](ValidatedMessage::validate) method, it sets `fail_fast` to `false`, so that
12	/// validation will continue even if a violation has already been found.
13	#[inline]
14	fn validate_all(&self) -> Result<(), ValidationErrors> {
15		if !Self::HAS_DEFAULT_VALIDATOR {
16			return Ok(());
17		}
18
19		let mut ctx = ValidationCtx {
20			field_context: None,
21			parent_elements: vec![],
22			violations: ValidationErrors::new(),
23			fail_fast: false,
24		};
25
26		let _ = self.validate_with_ctx(&mut ctx);
27
28		if ctx.violations.is_empty() {
29			Ok(())
30		} else {
31			Err(ctx.violations)
32		}
33	}
34
35	/// Executes validation on this message, triggering the validators that have been assigned to it
36	/// via macro attributes, if there are any.
37	///
38	/// Uses the default values for [`ValidationCtx`], including `fail_fast: true`.
39	#[inline]
40	fn validate(&self) -> Result<(), ValidationErrors> {
41		if !Self::HAS_DEFAULT_VALIDATOR {
42			return Ok(());
43		}
44
45		let mut ctx = ValidationCtx::default();
46
47		let _ = self.validate_with_ctx(&mut ctx);
48
49		if ctx.violations.is_empty() {
50			Ok(())
51		} else {
52			Err(ctx.violations)
53		}
54	}
55
56	/// Executes validation on this message, triggering the validators that have been assigned to it
57	/// via macro attributes, and returns `true` if the validation was successful.
58	///
59	/// Uses the default values for [`ValidationCtx`], including `fail_fast: true`.
60	#[inline]
61	fn is_valid(&self) -> bool {
62		if Self::HAS_DEFAULT_VALIDATOR {
63			self.validate().is_ok()
64		} else {
65			true
66		}
67	}
68
69	/// Executes validation on this message, triggering the validators that have been assigned to it
70	/// via macro attributes, if there are any, and returns the value if validation was successful.
71	///
72	/// Uses the default values for [`ValidationCtx`], including `fail_fast: true`.
73	#[inline]
74	fn validated(self) -> Result<Self, ValidationErrors> {
75		if !Self::HAS_DEFAULT_VALIDATOR {
76			return Ok(self);
77		}
78
79		match self.validate() {
80			Ok(()) => Ok(self),
81			Err(e) => Err(e),
82		}
83	}
84
85	/// Executes validation on this message, triggering the validators that have been assigned to it
86	/// via macro attributes, if there are any.
87	fn validate_with_ctx(&self, ctx: &mut ValidationCtx) -> ValidationResult;
88}
89
90/// Defalt validator for messages used as fields of other messages.
91#[non_exhaustive]
92#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94pub struct MessageValidator {
95	/// Adds custom validation using one or more [`CelRule`]s to this field.
96	pub cel: Vec<CelProgram>,
97
98	/// The conditions upon which this validator should be skipped.
99	pub ignore: Ignore,
100
101	/// Specifies that the field must be set in order to be valid.
102	pub required: bool,
103
104	/// A custom error message to display for the `required` violation.
105	pub required_error_message: Option<FixedStr>,
106}
107
108impl<T, S: builder::State> ValidatorBuilderFor<T> for MessageValidatorBuilder<S>
109where
110	T: ValidatedMessage + PartialEq + TryIntoCel,
111{
112	type Validator = MessageValidator;
113
114	#[inline]
115	fn build_validator(self) -> Self::Validator {
116		self.build()
117	}
118}
119
120impl<T> Validator<T> for MessageValidator
121where
122	T: ValidatedMessage + PartialEq + TryIntoCel,
123{
124	type Target = T;
125
126	#[cfg(feature = "cel")]
127	#[inline(never)]
128	#[cold]
129	fn check_cel_programs_with(&self, val: Self::Target) -> Result<(), Vec<CelError>> {
130		if self.cel.is_empty() {
131			Ok(())
132		} else {
133			test_programs(&self.cel, val)
134		}
135	}
136
137	#[cfg(feature = "cel")]
138	#[inline(never)]
139	#[cold]
140	#[doc(hidden)]
141	fn __check_cel_programs(&self) -> Result<(), Vec<CelError>> {
142		<Self as Validator<T>>::check_cel_programs_with(self, Self::Target::default())
143	}
144
145	#[doc(hidden)]
146	#[inline(never)]
147	#[cold]
148	fn __cel_rules(&self) -> Vec<CelRule> {
149		self.cel
150			.iter()
151			.map(|p| p.rule().clone())
152			.collect()
153	}
154
155	#[inline(never)]
156	#[cold]
157	fn check_consistency(&self) -> Result<(), Vec<ConsistencyError>> {
158		let mut errors = Vec::new();
159
160		#[cfg(feature = "cel")]
161		if let Err(e) = <Self as Validator<T>>::__check_cel_programs(self) {
162			errors.extend(e.into_iter().map(ConsistencyError::from));
163		}
164
165		if errors.is_empty() {
166			Ok(())
167		} else {
168			Err(errors)
169		}
170	}
171
172	fn execute_validation(
173		&self,
174		ctx: &mut ValidationCtx,
175		val: Option<&Self::Target>,
176	) -> ValidationResult {
177		handle_ignore_always!(&self.ignore);
178		handle_ignore_if_zero_value!(&self.ignore, val.is_none());
179
180		let mut is_valid = IsValid::Yes;
181
182		if let Some(val) = val {
183			if T::HAS_DEFAULT_VALIDATOR {
184				if let Some(field_context) = &mut ctx.field_context {
185					ctx.parent_elements
186						.push(field_context.as_path_element());
187				}
188
189				is_valid &= val.validate_with_ctx(ctx)?;
190
191				if ctx.field_context.is_some() {
192					ctx.parent_elements.pop();
193				}
194			}
195
196			#[cfg(feature = "cel")]
197			if !self.cel.is_empty() {
198				let cel_ctx = ProgramsExecutionCtx {
199					programs: &self.cel,
200					value: val.clone(),
201					ctx,
202				};
203
204				is_valid &= cel_ctx.execute_programs()?;
205			}
206		} else if self.required {
207			is_valid &= ctx.add_required_violation(
208				self.required_error_message
209					.as_ref()
210					.map(|e| e.to_string()),
211			)?;
212		}
213
214		Ok(is_valid)
215	}
216
217	#[inline(never)]
218	#[cold]
219	fn schema(&self) -> Option<ValidatorSchema> {
220		Some(ValidatorSchema {
221			schema: self.clone().into(),
222			cel_rules: <Self as Validator<T>>::__cel_rules(self),
223			imports: vec!["buf/validate/validate.proto".into()],
224		})
225	}
226}
227
228impl From<MessageValidator> for ProtoOption {
229	#[inline(never)]
230	#[cold]
231	fn from(validator: MessageValidator) -> Self {
232		let mut rules = OptionMessageBuilder::new();
233
234		rules
235			.add_cel_options(validator.cel)
236			.set_required(validator.required)
237			.set_ignore(validator.ignore);
238
239		Self {
240			name: "(buf.validate.field)".into(),
241			value: OptionValue::Message(rules.into()),
242		}
243	}
244}
245
246/// Default validator for messages when validated in their entirety (and not as a nested field).
247///
248/// Contains rules that are equivalent to `(buf.validate.message).cel` options in protobuf.
249#[non_exhaustive]
250#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
251#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
252pub struct CelValidator {
253	pub programs: Vec<CelProgram>,
254}
255
256impl CelValidator {
257	/// Adds a new program to this validator.
258	#[must_use]
259	#[inline]
260	pub fn cel(mut self, program: CelProgram) -> Self {
261		self.programs.push(program);
262		self
263	}
264}
265
266impl<T> Validator<T> for CelValidator
267where
268	T: ValidatedMessage + PartialEq + TryIntoCel + Default + Clone,
269{
270	type Target = T;
271
272	#[cfg(feature = "cel")]
273	#[inline(never)]
274	#[cold]
275	fn check_cel_programs_with(&self, val: Self::Target) -> Result<(), Vec<CelError>> {
276		if self.programs.is_empty() {
277			Ok(())
278		} else {
279			test_programs(&self.programs, val)
280		}
281	}
282
283	#[cfg(feature = "cel")]
284	#[inline(never)]
285	#[cold]
286	#[doc(hidden)]
287	fn __check_cel_programs(&self) -> Result<(), Vec<CelError>> {
288		<Self as Validator<T>>::check_cel_programs_with(self, Self::Target::default())
289	}
290
291	#[doc(hidden)]
292	#[inline(never)]
293	#[cold]
294	fn __cel_rules(&self) -> Vec<CelRule> {
295		self.programs
296			.iter()
297			.map(|p| p.rule().clone())
298			.collect()
299	}
300
301	#[inline(never)]
302	#[cold]
303	fn check_consistency(&self) -> Result<(), Vec<ConsistencyError>> {
304		let mut errors = Vec::new();
305
306		#[cfg(feature = "cel")]
307		if let Err(e) = <Self as Validator<T>>::__check_cel_programs(self) {
308			errors.extend(e.into_iter().map(ConsistencyError::from));
309		}
310
311		if errors.is_empty() {
312			Ok(())
313		} else {
314			Err(errors)
315		}
316	}
317
318	fn execute_validation(
319		&self,
320		ctx: &mut ValidationCtx,
321		val: Option<&Self::Target>,
322	) -> ValidationResult {
323		let mut is_valid = IsValid::Yes;
324
325		if let Some(val) = val {
326			#[cfg(feature = "cel")]
327			if !self.programs.is_empty() {
328				let cel_ctx = ProgramsExecutionCtx {
329					programs: &self.programs,
330					value: val.clone(),
331					ctx,
332				};
333
334				is_valid &= cel_ctx.execute_programs()?;
335			}
336		}
337
338		Ok(is_valid)
339	}
340
341	#[inline(never)]
342	#[cold]
343	fn schema(&self) -> Option<ValidatorSchema> {
344		Some(ValidatorSchema {
345			schema: self.clone().into(),
346			cel_rules: <Self as Validator<T>>::__cel_rules(self),
347			imports: vec!["buf/validate/validate.proto".into()],
348		})
349	}
350}
351
352impl From<CelValidator> for ProtoOption {
353	#[inline(never)]
354	#[cold]
355	fn from(value: CelValidator) -> Self {
356		let mut rules = OptionMessageBuilder::new();
357
358		rules.add_cel_options(value.programs);
359
360		Self {
361			name: "(buf.validate.message)".into(),
362			value: OptionValue::Message(rules.into()),
363		}
364	}
365}