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//!
22//! # Usage
23//!
24//! Most users do not need this crate directly — the
25//! [`prost-protovalidate`](https://crates.io/crates/prost-protovalidate) crate re-exports
26//! everything required for validation via its `types` module. Use this crate when you only need the
27//! generated types or descriptor pool without the evaluation engine.
28
29#![warn(missing_docs)]
30
31#[allow(
32    missing_docs,
33    clippy::len_without_is_empty,
34    clippy::doc_lazy_continuation,
35    clippy::doc_markdown,
36    clippy::must_use_candidate
37)]
38mod proto;
39
40use anyhow::anyhow;
41use prost_reflect::{
42    DynamicMessage, ExtensionDescriptor, FieldDescriptor, MessageDescriptor, OneofDescriptor,
43};
44use std::sync::LazyLock;
45
46pub use proto::*;
47
48// buf.validate extensions use field number 1159
49#[allow(clippy::unwrap_used)]
50static BUF_VALIDATE_MESSAGE: LazyLock<ExtensionDescriptor> = LazyLock::new(|| {
51    DESCRIPTOR_POOL
52        .get_extension_by_name("buf.validate.message")
53        .ok_or(anyhow!("buf.validate.message extension not found"))
54        .unwrap()
55});
56
57#[allow(clippy::unwrap_used)]
58static BUF_VALIDATE_ONEOF: LazyLock<ExtensionDescriptor> = LazyLock::new(|| {
59    DESCRIPTOR_POOL
60        .get_extension_by_name("buf.validate.oneof")
61        .ok_or(anyhow!("buf.validate.oneof extension not found"))
62        .unwrap()
63});
64
65#[allow(clippy::unwrap_used)]
66static BUF_VALIDATE_FIELD: LazyLock<ExtensionDescriptor> = LazyLock::new(|| {
67    DESCRIPTOR_POOL
68        .get_extension_by_name("buf.validate.field")
69        .ok_or(anyhow!("buf.validate.field extension not found"))
70        .unwrap()
71});
72
73#[allow(clippy::unwrap_used)]
74static BUF_VALIDATE_PREDEFINED: LazyLock<ExtensionDescriptor> = LazyLock::new(|| {
75    DESCRIPTOR_POOL
76        .get_extension_by_name("buf.validate.predefined")
77        .ok_or(anyhow!("buf.validate.predefined extension not found"))
78        .unwrap()
79});
80
81/// Extension trait for extracting `buf.validate.field` rules from a field descriptor.
82pub trait FieldConstraintsExt {
83    /// Returns the `FieldRules` for this field, if any.
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if the extension value cannot be transcoded to `FieldRules`.
88    fn field_constraints(&self) -> anyhow::Result<Option<FieldRules>>;
89
90    /// Returns the real (non-synthetic) oneof containing this field, if any.
91    fn real_oneof(&self) -> Option<OneofDescriptor>;
92
93    /// Returns true if this field is proto3 optional (synthetic oneof).
94    fn is_optional(&self) -> bool;
95}
96
97impl FieldConstraintsExt for FieldDescriptor {
98    fn field_constraints(&self) -> anyhow::Result<Option<FieldRules>> {
99        let options = self.options();
100        if !options.has_extension(&BUF_VALIDATE_FIELD) {
101            return Ok(None);
102        }
103        match options.get_extension(&BUF_VALIDATE_FIELD).as_message() {
104            Some(r) => Ok(Some(r.transcode_to::<FieldRules>()?)),
105            None => Ok(None),
106        }
107    }
108
109    fn real_oneof(&self) -> Option<OneofDescriptor> {
110        self.containing_oneof().filter(|o| !o.is_synthetic())
111    }
112
113    fn is_optional(&self) -> bool {
114        self.containing_oneof().is_some_and(|d| d.is_synthetic())
115    }
116}
117
118/// Extension trait for extracting `buf.validate.oneof` rules from a oneof descriptor.
119pub trait OneofConstraintsExt {
120    /// Returns true if this oneof requires exactly one field to be set.
121    fn is_required(&self) -> bool;
122}
123
124impl OneofConstraintsExt for OneofDescriptor {
125    fn is_required(&self) -> bool {
126        let options = self.options();
127        if !options.has_extension(&BUF_VALIDATE_ONEOF) {
128            return false;
129        }
130        options
131            .get_extension(&BUF_VALIDATE_ONEOF)
132            .as_message()
133            .and_then(|msg| msg.transcode_to::<OneofRules>().ok())
134            .is_some_and(|rules| rules.required.unwrap_or(false))
135    }
136}
137
138/// Extension trait for extracting `buf.validate.message` rules from a message descriptor.
139pub trait MessageConstraintsExt {
140    /// Returns the `MessageRules` for this message, if any.
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if the extension value cannot be transcoded to `MessageRules`.
145    fn message_constraints(&self) -> anyhow::Result<Option<MessageRules>>;
146}
147
148impl MessageConstraintsExt for MessageDescriptor {
149    fn message_constraints(&self) -> anyhow::Result<Option<MessageRules>> {
150        let options = self.options();
151        if !options.has_extension(&BUF_VALIDATE_MESSAGE) {
152            return Ok(None);
153        }
154        match options.get_extension(&BUF_VALIDATE_MESSAGE).as_message() {
155            Some(r) => Ok(Some(r.transcode_to::<MessageRules>()?)),
156            None => Ok(None),
157        }
158    }
159}
160
161/// Extension trait for extracting `buf.validate.predefined` rules from a field descriptor.
162pub trait PredefinedConstraintsExt {
163    /// Returns the `PredefinedRules` for this field, if any.
164    ///
165    /// # Errors
166    ///
167    /// Returns an error if the extension value cannot be transcoded to `PredefinedRules`.
168    fn predefined_constraints(&self) -> anyhow::Result<Option<PredefinedRules>>;
169}
170
171impl PredefinedConstraintsExt for FieldDescriptor {
172    fn predefined_constraints(&self) -> anyhow::Result<Option<PredefinedRules>> {
173        let options = self.options();
174        if !options.has_extension(&BUF_VALIDATE_PREDEFINED) {
175            return Ok(None);
176        }
177        match options.get_extension(&BUF_VALIDATE_PREDEFINED).as_message() {
178            Some(r) => Ok(Some(r.transcode_to::<PredefinedRules>()?)),
179            None => Ok(None),
180        }
181    }
182}
183
184/// Extension trait for extracting the `DynamicMessage` form of field constraints.
185/// This is useful for the runtime validator which needs to read rule fields dynamically.
186pub trait FieldConstraintsDynExt {
187    /// Returns the raw `DynamicMessage` for the `buf.validate.field` extension.
188    fn field_constraints_dynamic(&self) -> Option<DynamicMessage>;
189}
190
191impl FieldConstraintsDynExt for FieldDescriptor {
192    fn field_constraints_dynamic(&self) -> Option<DynamicMessage> {
193        let options = self.options();
194        if !options.has_extension(&BUF_VALIDATE_FIELD) {
195            return None;
196        }
197        options
198            .get_extension(&BUF_VALIDATE_FIELD)
199            .as_message()
200            .cloned()
201    }
202}
203
204/// Extension trait for extracting the `DynamicMessage` form of message constraints.
205pub trait MessageConstraintsDynExt {
206    /// Returns the raw `DynamicMessage` for the `buf.validate.message` extension.
207    fn message_constraints_dynamic(&self) -> Option<DynamicMessage>;
208}
209
210impl MessageConstraintsDynExt for MessageDescriptor {
211    fn message_constraints_dynamic(&self) -> Option<DynamicMessage> {
212        let options = self.options();
213        if !options.has_extension(&BUF_VALIDATE_MESSAGE) {
214            return None;
215        }
216        options
217            .get_extension(&BUF_VALIDATE_MESSAGE)
218            .as_message()
219            .cloned()
220    }
221}