#![warn(missing_docs)]
#[allow(
missing_docs,
clippy::len_without_is_empty,
clippy::doc_lazy_continuation,
clippy::doc_markdown,
clippy::must_use_candidate
)]
mod proto;
use std::sync::LazyLock;
use prost::Message;
use prost_reflect::{
DynamicMessage, ExtensionDescriptor, FieldDescriptor, MessageDescriptor, OneofDescriptor,
};
pub use proto::*;
static BUF_VALIDATE_MESSAGE: LazyLock<Option<ExtensionDescriptor>> =
LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.message"));
static BUF_VALIDATE_ONEOF: LazyLock<Option<ExtensionDescriptor>> =
LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.oneof"));
static BUF_VALIDATE_FIELD: LazyLock<Option<ExtensionDescriptor>> =
LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.field"));
static BUF_VALIDATE_PREDEFINED: LazyLock<Option<ExtensionDescriptor>> =
LazyLock::new(|| DESCRIPTOR_POOL.get_extension_by_name("buf.validate.predefined"));
#[derive(Debug, thiserror::Error)]
pub enum ConstraintDecodeError {
#[error("descriptor pool initialization failed: {0}")]
DescriptorPoolInitialization(String),
#[error("missing extension descriptor `{0}`")]
MissingExtension(&'static str),
#[error(transparent)]
Decode(#[from] prost::DecodeError),
}
pub type ConstraintDecodeResult<T> = Result<Option<T>, ConstraintDecodeError>;
fn decode_extension_constraints<T>(
options: &DynamicMessage,
extension_name: &'static str,
extension: &'static LazyLock<Option<ExtensionDescriptor>>,
) -> ConstraintDecodeResult<T>
where
T: Message + Default,
{
let extension = resolve_extension_descriptor(extension_name, extension)?;
if !options.has_extension(extension) {
return Ok(None);
}
match options.get_extension(extension).as_message() {
Some(message) => message.transcode_to::<T>().map(Some).map_err(Into::into),
None => Ok(None),
}
}
fn resolve_extension_descriptor(
extension_name: &'static str,
extension: &'static LazyLock<Option<ExtensionDescriptor>>,
) -> Result<&'static ExtensionDescriptor, ConstraintDecodeError> {
if let Some(err) = descriptor_pool_decode_error() {
return Err(ConstraintDecodeError::DescriptorPoolInitialization(
err.to_string(),
));
}
extension
.as_ref()
.ok_or(ConstraintDecodeError::MissingExtension(extension_name))
}
pub fn field_constraints_typed(field: &FieldDescriptor) -> ConstraintDecodeResult<FieldRules> {
let options = field.options();
decode_extension_constraints(&options, "buf.validate.field", &BUF_VALIDATE_FIELD)
}
pub fn oneof_constraints_typed(oneof: &OneofDescriptor) -> ConstraintDecodeResult<OneofRules> {
let options = oneof.options();
decode_extension_constraints(&options, "buf.validate.oneof", &BUF_VALIDATE_ONEOF)
}
pub fn message_constraints_typed(
message: &MessageDescriptor,
) -> ConstraintDecodeResult<MessageRules> {
let options = message.options();
decode_extension_constraints(&options, "buf.validate.message", &BUF_VALIDATE_MESSAGE)
}
pub fn predefined_constraints_typed(
field: &FieldDescriptor,
) -> ConstraintDecodeResult<PredefinedRules> {
let options = field.options();
decode_extension_constraints(
&options,
"buf.validate.predefined",
&BUF_VALIDATE_PREDEFINED,
)
}
pub trait FieldConstraintsExt {
fn field_constraints(&self) -> ConstraintDecodeResult<FieldRules>;
fn real_oneof(&self) -> Option<OneofDescriptor>;
fn is_optional(&self) -> bool;
}
impl FieldConstraintsExt for FieldDescriptor {
fn field_constraints(&self) -> ConstraintDecodeResult<FieldRules> {
field_constraints_typed(self)
}
fn real_oneof(&self) -> Option<OneofDescriptor> {
self.containing_oneof().filter(|o| !o.is_synthetic())
}
fn is_optional(&self) -> bool {
self.containing_oneof().is_some_and(|d| d.is_synthetic())
}
}
pub trait OneofConstraintsExt {
fn oneof_constraints(&self) -> ConstraintDecodeResult<OneofRules>;
fn try_is_required(&self) -> Result<bool, ConstraintDecodeError> {
Ok(self
.oneof_constraints()?
.is_some_and(|rules| rules.required.unwrap_or(false)))
}
}
impl OneofConstraintsExt for OneofDescriptor {
fn oneof_constraints(&self) -> ConstraintDecodeResult<OneofRules> {
oneof_constraints_typed(self)
}
}
pub trait MessageConstraintsExt {
fn message_constraints(&self) -> ConstraintDecodeResult<MessageRules>;
}
impl MessageConstraintsExt for MessageDescriptor {
fn message_constraints(&self) -> ConstraintDecodeResult<MessageRules> {
message_constraints_typed(self)
}
}
pub trait PredefinedConstraintsExt {
fn predefined_constraints(&self) -> ConstraintDecodeResult<PredefinedRules>;
}
impl PredefinedConstraintsExt for FieldDescriptor {
fn predefined_constraints(&self) -> ConstraintDecodeResult<PredefinedRules> {
predefined_constraints_typed(self)
}
}
pub trait FieldConstraintsDynExt {
fn field_constraints_dynamic(&self) -> Option<DynamicMessage>;
}
impl FieldConstraintsDynExt for FieldDescriptor {
fn field_constraints_dynamic(&self) -> Option<DynamicMessage> {
let options = self.options();
let Ok(extension) = resolve_extension_descriptor("buf.validate.field", &BUF_VALIDATE_FIELD)
else {
return None;
};
if !options.has_extension(extension) {
return None;
}
options.get_extension(extension).as_message().cloned()
}
}
pub trait MessageConstraintsDynExt {
fn message_constraints_dynamic(&self) -> Option<DynamicMessage>;
}
impl MessageConstraintsDynExt for MessageDescriptor {
fn message_constraints_dynamic(&self) -> Option<DynamicMessage> {
let options = self.options();
let Ok(extension) =
resolve_extension_descriptor("buf.validate.message", &BUF_VALIDATE_MESSAGE)
else {
return None;
};
if !options.has_extension(extension) {
return None;
}
options.get_extension(extension).as_message().cloned()
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
fn descriptor_field(message: &str, field: &str) -> FieldDescriptor {
DESCRIPTOR_POOL
.get_message_by_name(message)
.and_then(|message| message.get_field_by_name(field))
.expect("descriptor field must exist")
}
#[test]
fn typed_helpers_return_none_when_extension_is_absent() {
let field = descriptor_field("buf.validate.FieldRules", "required");
let message = DESCRIPTOR_POOL
.get_message_by_name("buf.validate.FieldRules")
.expect("message must exist");
let oneof = message
.oneofs()
.find(|oneof| oneof.name() == "type")
.expect("oneof must exist");
assert_eq!(field_constraints_typed(&field).ok().flatten(), None);
assert_eq!(message_constraints_typed(&message).ok().flatten(), None);
assert_eq!(oneof_constraints_typed(&oneof).ok().flatten(), None);
}
#[test]
fn typed_predefined_helper_decodes_known_extension() {
let field = descriptor_field("buf.validate.RepeatedRules", "min_items");
let rules = predefined_constraints_typed(&field)
.expect("predefined extension should decode")
.expect("predefined extension should be present");
assert!(!rules.cel.is_empty());
}
}