prost_protovalidate_types/
constraints.rs1use std::sync::LazyLock;
7
8use prost::Message;
9use prost_reflect::{
10 DynamicMessage, ExtensionDescriptor, FieldDescriptor, MessageDescriptor, OneofDescriptor,
11};
12
13use crate::proto::{DESCRIPTOR_POOL, descriptor_pool_decode_error};
14use crate::{
15 ConstraintDecodeError, ConstraintDecodeResult, FieldRules, MessageRules, OneofRules,
16 PredefinedRules,
17};
18
19static BUF_VALIDATE_MESSAGE: LazyLock<Option<ExtensionDescriptor>> =
21 LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.message"));
22
23static BUF_VALIDATE_ONEOF: LazyLock<Option<ExtensionDescriptor>> =
24 LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.oneof"));
25
26static BUF_VALIDATE_FIELD: LazyLock<Option<ExtensionDescriptor>> =
27 LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.field"));
28
29static BUF_VALIDATE_PREDEFINED: LazyLock<Option<ExtensionDescriptor>> =
30 LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.predefined"));
31
32fn decode_extension_constraints<T>(
33 options: &DynamicMessage,
34 extension_name: &'static str,
35 extension: &'static LazyLock<Option<ExtensionDescriptor>>,
36) -> ConstraintDecodeResult<T>
37where
38 T: Message + Default,
39{
40 let extension = resolve_extension_descriptor(extension_name, extension)?;
41 if !options.has_extension(extension) {
42 return Ok(None);
43 }
44 match options.get_extension(extension).as_message() {
45 Some(message) => message.transcode_to::<T>().map(Some).map_err(Into::into),
46 None => Ok(None),
47 }
48}
49
50fn resolve_extension_descriptor(
51 extension_name: &'static str,
52 extension: &'static LazyLock<Option<ExtensionDescriptor>>,
53) -> Result<&'static ExtensionDescriptor, ConstraintDecodeError> {
54 if let Some(err) = descriptor_pool_decode_error() {
55 return Err(ConstraintDecodeError::DescriptorPoolInitialization(
56 err.to_string(),
57 ));
58 }
59
60 extension
61 .as_ref()
62 .ok_or(ConstraintDecodeError::MissingExtension(extension_name))
63}
64
65pub fn field_constraints_typed(field: &FieldDescriptor) -> ConstraintDecodeResult<FieldRules> {
73 let options = field.options();
74 decode_extension_constraints(&options, "buf.validate.field", &BUF_VALIDATE_FIELD)
75}
76
77pub fn oneof_constraints_typed(oneof: &OneofDescriptor) -> ConstraintDecodeResult<OneofRules> {
83 let options = oneof.options();
84 decode_extension_constraints(&options, "buf.validate.oneof", &BUF_VALIDATE_ONEOF)
85}
86
87pub fn message_constraints_typed(
95 message: &MessageDescriptor,
96) -> ConstraintDecodeResult<MessageRules> {
97 let options = message.options();
98 decode_extension_constraints(&options, "buf.validate.message", &BUF_VALIDATE_MESSAGE)
99}
100
101pub fn predefined_constraints_typed(
109 field: &FieldDescriptor,
110) -> ConstraintDecodeResult<PredefinedRules> {
111 let options = field.options();
112 decode_extension_constraints(
113 &options,
114 "buf.validate.predefined",
115 &BUF_VALIDATE_PREDEFINED,
116 )
117}
118
119pub trait FieldConstraintsExt {
121 fn field_constraints(&self) -> ConstraintDecodeResult<FieldRules>;
127
128 fn real_oneof(&self) -> Option<OneofDescriptor>;
130
131 fn is_optional(&self) -> bool;
133}
134
135impl FieldConstraintsExt for FieldDescriptor {
136 fn field_constraints(&self) -> ConstraintDecodeResult<FieldRules> {
137 field_constraints_typed(self)
138 }
139
140 fn real_oneof(&self) -> Option<OneofDescriptor> {
141 self.containing_oneof().filter(|o| !o.is_synthetic())
142 }
143
144 fn is_optional(&self) -> bool {
145 self.containing_oneof().is_some_and(|d| d.is_synthetic())
146 }
147}
148
149pub trait OneofConstraintsExt {
151 fn oneof_constraints(&self) -> ConstraintDecodeResult<OneofRules>;
157
158 fn try_is_required(&self) -> Result<bool, ConstraintDecodeError> {
164 Ok(self
165 .oneof_constraints()?
166 .is_some_and(|rules| rules.required.unwrap_or(false)))
167 }
168}
169
170impl OneofConstraintsExt for OneofDescriptor {
171 fn oneof_constraints(&self) -> ConstraintDecodeResult<OneofRules> {
172 oneof_constraints_typed(self)
173 }
174}
175
176pub trait MessageConstraintsExt {
178 fn message_constraints(&self) -> ConstraintDecodeResult<MessageRules>;
184}
185
186impl MessageConstraintsExt for MessageDescriptor {
187 fn message_constraints(&self) -> ConstraintDecodeResult<MessageRules> {
188 message_constraints_typed(self)
189 }
190}
191
192pub trait PredefinedConstraintsExt {
194 fn predefined_constraints(&self) -> ConstraintDecodeResult<PredefinedRules>;
200}
201
202impl PredefinedConstraintsExt for FieldDescriptor {
203 fn predefined_constraints(&self) -> ConstraintDecodeResult<PredefinedRules> {
204 predefined_constraints_typed(self)
205 }
206}
207
208pub trait FieldConstraintsDynExt {
211 fn field_constraints_dynamic(&self) -> Option<DynamicMessage>;
213}
214
215impl FieldConstraintsDynExt for FieldDescriptor {
216 fn field_constraints_dynamic(&self) -> Option<DynamicMessage> {
217 let options = self.options();
218 let Ok(extension) = resolve_extension_descriptor("buf.validate.field", &BUF_VALIDATE_FIELD)
219 else {
220 return None;
221 };
222 if !options.has_extension(extension) {
223 return None;
224 }
225 options.get_extension(extension).as_message().cloned()
226 }
227}
228
229pub trait MessageConstraintsDynExt {
231 fn message_constraints_dynamic(&self) -> Option<DynamicMessage>;
233}
234
235impl MessageConstraintsDynExt for MessageDescriptor {
236 fn message_constraints_dynamic(&self) -> Option<DynamicMessage> {
237 let options = self.options();
238 let Ok(extension) =
239 resolve_extension_descriptor("buf.validate.message", &BUF_VALIDATE_MESSAGE)
240 else {
241 return None;
242 };
243 if !options.has_extension(extension) {
244 return None;
245 }
246 options.get_extension(extension).as_message().cloned()
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use pretty_assertions::assert_eq;
253
254 use super::*;
255
256 fn descriptor_field(message: &str, field: &str) -> FieldDescriptor {
257 DESCRIPTOR_POOL
258 .get_message_by_name(message)
259 .and_then(|message| message.get_field_by_name(field))
260 .expect("descriptor field must exist")
261 }
262
263 #[test]
264 fn typed_helpers_return_none_when_extension_is_absent() {
265 let field = descriptor_field("buf.validate.FieldRules", "required");
266 let message = DESCRIPTOR_POOL
267 .get_message_by_name("buf.validate.FieldRules")
268 .expect("message must exist");
269 let oneof = message
270 .oneofs()
271 .find(|oneof| oneof.name() == "type")
272 .expect("oneof must exist");
273
274 assert_eq!(field_constraints_typed(&field).ok().flatten(), None);
275 assert_eq!(message_constraints_typed(&message).ok().flatten(), None);
276 assert_eq!(oneof_constraints_typed(&oneof).ok().flatten(), None);
277 }
278
279 #[test]
280 fn typed_predefined_helper_decodes_known_extension() {
281 let field = descriptor_field("buf.validate.RepeatedRules", "min_items");
282 let rules = predefined_constraints_typed(&field)
283 .expect("predefined extension should decode")
284 .expect("predefined extension should be present");
285 assert!(!rules.cel.is_empty());
286 }
287}