Skip to main content

prost_protovalidate_types/
lib.rs

1//! Generated Rust types for the [`buf.validate`](https://github.com/bufbuild/protovalidate)
2//! protobuf schema, built with `prost` and `prost-reflect`.
3//!
4//! This crate provides:
5//!
6//! - All message and enum types from `buf/validate/validate.proto`
7//!   (e.g. [`FieldRules`], [`MessageRules`], [`OneofRules`]).
8//! - A shared [`DESCRIPTOR_POOL`] containing the file descriptor set for
9//!   runtime reflection.
10//! - Extension traits for extracting constraint annotations from descriptors:
11//!   - [`FieldConstraintsExt`] — `buf.validate.field` rules on a
12//!     [`FieldDescriptor`].
13//!   - [`MessageConstraintsExt`] — `buf.validate.message` rules on a
14//!     [`MessageDescriptor`].
15//!   - [`OneofConstraintsExt`] — `buf.validate.oneof` rules on a
16//!     [`OneofDescriptor`].
17//!   - [`PredefinedConstraintsExt`] — `buf.validate.predefined` rules.
18//!   - [`FieldConstraintsDynExt`] / [`MessageConstraintsDynExt`] — raw
19//!     [`DynamicMessage`] access for the
20//!     runtime validator.
21//! - Typed helper functions for extension extraction with concrete error types:
22//!   [`field_constraints_typed`], [`message_constraints_typed`],
23//!   [`oneof_constraints_typed`], [`predefined_constraints_typed`].
24//!
25//! # Usage
26//!
27//! Most users do not need this crate directly — the
28//! [`prost-protovalidate`](https://crates.io/crates/prost-protovalidate) crate re-exports
29//! everything required for validation via its `types` module. Use this crate when you only need the
30//! generated types or descriptor pool without the evaluation engine.
31
32#![warn(missing_docs)]
33
34#[allow(
35    missing_docs,
36    clippy::len_without_is_empty,
37    clippy::doc_lazy_continuation,
38    clippy::doc_markdown,
39    clippy::must_use_candidate
40)]
41mod proto;
42
43use std::sync::LazyLock;
44
45use prost::Message;
46use prost_reflect::{
47    DynamicMessage, ExtensionDescriptor, FieldDescriptor, MessageDescriptor, OneofDescriptor,
48};
49
50pub use proto::*;
51
52// buf.validate extensions use field number 1159
53static BUF_VALIDATE_MESSAGE: LazyLock<Option<ExtensionDescriptor>> =
54    LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.message"));
55
56static BUF_VALIDATE_ONEOF: LazyLock<Option<ExtensionDescriptor>> =
57    LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.oneof"));
58
59static BUF_VALIDATE_FIELD: LazyLock<Option<ExtensionDescriptor>> =
60    LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.field"));
61
62static BUF_VALIDATE_PREDEFINED: LazyLock<Option<ExtensionDescriptor>> =
63    LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.predefined"));
64
65/// Error returned while decoding `buf.validate` descriptor extensions.
66#[derive(Debug, thiserror::Error)]
67pub enum ConstraintDecodeError {
68    /// The generated descriptor pool could not be decoded.
69    #[error("descriptor pool initialization failed: {0}")]
70    DescriptorPoolInitialization(String),
71
72    /// The expected extension descriptor is missing from the pool.
73    #[error("missing extension descriptor `{0}`")]
74    MissingExtension(&'static str),
75
76    /// The extension payload could not be decoded into the typed rule.
77    #[error(transparent)]
78    Decode(#[from] prost::DecodeError),
79}
80
81/// Typed decode result for descriptor extension constraints.
82pub type ConstraintDecodeResult<T> = Result<Option<T>, ConstraintDecodeError>;
83
84fn decode_extension_constraints<T>(
85    options: &DynamicMessage,
86    extension_name: &'static str,
87    extension: &'static LazyLock<Option<ExtensionDescriptor>>,
88) -> ConstraintDecodeResult<T>
89where
90    T: Message + Default,
91{
92    let extension = resolve_extension_descriptor(extension_name, extension)?;
93    if !options.has_extension(extension) {
94        return Ok(None);
95    }
96    match options.get_extension(extension).as_message() {
97        Some(message) => message.transcode_to::<T>().map(Some).map_err(Into::into),
98        None => Ok(None),
99    }
100}
101
102fn resolve_extension_descriptor(
103    extension_name: &'static str,
104    extension: &'static LazyLock<Option<ExtensionDescriptor>>,
105) -> Result<&'static ExtensionDescriptor, ConstraintDecodeError> {
106    if let Some(err) = descriptor_pool_decode_error() {
107        return Err(ConstraintDecodeError::DescriptorPoolInitialization(
108            err.to_string(),
109        ));
110    }
111
112    extension
113        .as_ref()
114        .ok_or(ConstraintDecodeError::MissingExtension(extension_name))
115}
116
117/// Typed helper for extracting `buf.validate.field` rules from a field descriptor.
118///
119/// Prefer this API when you need a concrete, non-erased error type.
120///
121/// # Errors
122///
123/// Returns an error if the extension value cannot be transcoded to `FieldRules`.
124pub fn field_constraints_typed(field: &FieldDescriptor) -> ConstraintDecodeResult<FieldRules> {
125    let options = field.options();
126    decode_extension_constraints(&options, "buf.validate.field", &BUF_VALIDATE_FIELD)
127}
128
129/// Typed helper for extracting `buf.validate.oneof` rules from a oneof descriptor.
130///
131/// # Errors
132///
133/// Returns an error if the extension value cannot be transcoded to `OneofRules`.
134pub fn oneof_constraints_typed(oneof: &OneofDescriptor) -> ConstraintDecodeResult<OneofRules> {
135    let options = oneof.options();
136    decode_extension_constraints(&options, "buf.validate.oneof", &BUF_VALIDATE_ONEOF)
137}
138
139/// Typed helper for extracting `buf.validate.message` rules from a message descriptor.
140///
141/// Prefer this API when you need a concrete, non-erased error type.
142///
143/// # Errors
144///
145/// Returns an error if the extension value cannot be transcoded to `MessageRules`.
146pub fn message_constraints_typed(
147    message: &MessageDescriptor,
148) -> ConstraintDecodeResult<MessageRules> {
149    let options = message.options();
150    decode_extension_constraints(&options, "buf.validate.message", &BUF_VALIDATE_MESSAGE)
151}
152
153/// Typed helper for extracting `buf.validate.predefined` rules from a field descriptor.
154///
155/// Prefer this API when you need a concrete, non-erased error type.
156///
157/// # Errors
158///
159/// Returns an error if the extension value cannot be transcoded to `PredefinedRules`.
160pub fn predefined_constraints_typed(
161    field: &FieldDescriptor,
162) -> ConstraintDecodeResult<PredefinedRules> {
163    let options = field.options();
164    decode_extension_constraints(
165        &options,
166        "buf.validate.predefined",
167        &BUF_VALIDATE_PREDEFINED,
168    )
169}
170
171/// Extension trait for extracting `buf.validate.field` rules from a field descriptor.
172pub trait FieldConstraintsExt {
173    /// Returns the `FieldRules` for this field, if any.
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if the extension value cannot be transcoded to `FieldRules`.
178    fn field_constraints(&self) -> ConstraintDecodeResult<FieldRules>;
179
180    /// Returns the real (non-synthetic) oneof containing this field, if any.
181    fn real_oneof(&self) -> Option<OneofDescriptor>;
182
183    /// Returns true if this field is proto3 optional (synthetic oneof).
184    fn is_optional(&self) -> bool;
185}
186
187impl FieldConstraintsExt for FieldDescriptor {
188    fn field_constraints(&self) -> ConstraintDecodeResult<FieldRules> {
189        field_constraints_typed(self)
190    }
191
192    fn real_oneof(&self) -> Option<OneofDescriptor> {
193        self.containing_oneof().filter(|o| !o.is_synthetic())
194    }
195
196    fn is_optional(&self) -> bool {
197        self.containing_oneof().is_some_and(|d| d.is_synthetic())
198    }
199}
200
201/// Extension trait for extracting `buf.validate.oneof` rules from a oneof descriptor.
202pub trait OneofConstraintsExt {
203    /// Returns the `OneofRules` for this oneof, if any.
204    ///
205    /// # Errors
206    ///
207    /// Returns an error if the extension value cannot be transcoded to `OneofRules`.
208    fn oneof_constraints(&self) -> ConstraintDecodeResult<OneofRules>;
209
210    /// Returns true if this oneof requires exactly one field to be set.
211    ///
212    /// # Errors
213    ///
214    /// Returns an error if the extension value cannot be transcoded to `OneofRules`.
215    fn try_is_required(&self) -> Result<bool, ConstraintDecodeError> {
216        Ok(self
217            .oneof_constraints()?
218            .is_some_and(|rules| rules.required.unwrap_or(false)))
219    }
220}
221
222impl OneofConstraintsExt for OneofDescriptor {
223    fn oneof_constraints(&self) -> ConstraintDecodeResult<OneofRules> {
224        oneof_constraints_typed(self)
225    }
226}
227
228/// Extension trait for extracting `buf.validate.message` rules from a message descriptor.
229pub trait MessageConstraintsExt {
230    /// Returns the `MessageRules` for this message, if any.
231    ///
232    /// # Errors
233    ///
234    /// Returns an error if the extension value cannot be transcoded to `MessageRules`.
235    fn message_constraints(&self) -> ConstraintDecodeResult<MessageRules>;
236}
237
238impl MessageConstraintsExt for MessageDescriptor {
239    fn message_constraints(&self) -> ConstraintDecodeResult<MessageRules> {
240        message_constraints_typed(self)
241    }
242}
243
244/// Extension trait for extracting `buf.validate.predefined` rules from a field descriptor.
245pub trait PredefinedConstraintsExt {
246    /// Returns the `PredefinedRules` for this field, if any.
247    ///
248    /// # Errors
249    ///
250    /// Returns an error if the extension value cannot be transcoded to `PredefinedRules`.
251    fn predefined_constraints(&self) -> ConstraintDecodeResult<PredefinedRules>;
252}
253
254impl PredefinedConstraintsExt for FieldDescriptor {
255    fn predefined_constraints(&self) -> ConstraintDecodeResult<PredefinedRules> {
256        predefined_constraints_typed(self)
257    }
258}
259
260/// Extension trait for extracting the `DynamicMessage` form of field constraints.
261/// This is useful for the runtime validator which needs to read rule fields dynamically.
262pub trait FieldConstraintsDynExt {
263    /// Returns the raw `DynamicMessage` for the `buf.validate.field` extension.
264    fn field_constraints_dynamic(&self) -> Option<DynamicMessage>;
265}
266
267impl FieldConstraintsDynExt for FieldDescriptor {
268    fn field_constraints_dynamic(&self) -> Option<DynamicMessage> {
269        let options = self.options();
270        let Ok(extension) = resolve_extension_descriptor("buf.validate.field", &BUF_VALIDATE_FIELD)
271        else {
272            return None;
273        };
274        if !options.has_extension(extension) {
275            return None;
276        }
277        options.get_extension(extension).as_message().cloned()
278    }
279}
280
281/// Extension trait for extracting the `DynamicMessage` form of message constraints.
282pub trait MessageConstraintsDynExt {
283    /// Returns the raw `DynamicMessage` for the `buf.validate.message` extension.
284    fn message_constraints_dynamic(&self) -> Option<DynamicMessage>;
285}
286
287impl MessageConstraintsDynExt for MessageDescriptor {
288    fn message_constraints_dynamic(&self) -> Option<DynamicMessage> {
289        let options = self.options();
290        let Ok(extension) =
291            resolve_extension_descriptor("buf.validate.message", &BUF_VALIDATE_MESSAGE)
292        else {
293            return None;
294        };
295        if !options.has_extension(extension) {
296            return None;
297        }
298        options.get_extension(extension).as_message().cloned()
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use pretty_assertions::assert_eq;
305
306    use super::*;
307
308    fn descriptor_field(message: &str, field: &str) -> FieldDescriptor {
309        DESCRIPTOR_POOL
310            .get_message_by_name(message)
311            .and_then(|message| message.get_field_by_name(field))
312            .expect("descriptor field must exist")
313    }
314
315    #[test]
316    fn typed_helpers_return_none_when_extension_is_absent() {
317        let field = descriptor_field("buf.validate.FieldRules", "required");
318        let message = DESCRIPTOR_POOL
319            .get_message_by_name("buf.validate.FieldRules")
320            .expect("message must exist");
321        let oneof = message
322            .oneofs()
323            .find(|oneof| oneof.name() == "type")
324            .expect("oneof must exist");
325
326        assert_eq!(field_constraints_typed(&field).ok().flatten(), None);
327        assert_eq!(message_constraints_typed(&message).ok().flatten(), None);
328        assert_eq!(oneof_constraints_typed(&oneof).ok().flatten(), None);
329    }
330
331    #[test]
332    fn typed_predefined_helper_decodes_known_extension() {
333        let field = descriptor_field("buf.validate.RepeatedRules", "min_items");
334        let rules = predefined_constraints_typed(&field)
335            .expect("predefined extension should decode")
336            .expect("predefined extension should be present");
337        assert!(!rules.cel.is_empty());
338    }
339}