protovalidate_buffa/lib.rs
1//! Runtime companion for `protoc-gen-protovalidate-buffa`.
2//!
3//! Provides the [`Validate`] trait, the [`ValidationError`] /
4//! [`Violation`] / [`FieldPath`] types returned from generated
5//! `validate()` methods, the [`cel`] module of thin helpers that
6//! compile-time-expanded CEL rules call into (scalar widening,
7//! Duration/Timestamp converters, `now`), and the [`rules`] module
8//! of pure-Rust helpers used by generated code (UUID / ULID / IP /
9//! URI / hostname checks and friends, mostly thin wrappers over
10//! `uuid`, `ulid`, `ipnet`, and `fluent-uri`).
11//!
12//! There is **no CEL interpreter at runtime**: the paired
13//! `protoc-gen-protovalidate-buffa` plugin transpiles every CEL rule
14//! to native Rust at codegen time. Generated `validate()` methods are
15//! direct field-access checks with zero per-call `Value` / `HashMap`
16//! allocations.
17//!
18//! [`ValidationError`] carries three orthogonal signals:
19//!
20//! - `violations`: list of per-field rule failures (the common case).
21//! - `compile_error`: non-empty when the codegen plugin detected a
22//! schema-level mismatch (rule type / field type, duplicate / unknown
23//! fields in `message.oneof`, CEL referencing a non-existent field).
24//! - `runtime_error`: non-empty when a rule's precondition could not be
25//! evaluated (e.g. `bytes.pattern` on non-UTF-8 input, or a CEL rule
26//! that compiled-time analysis flagged as always-runtime-error such as
27//! `dyn(this).<unknown_field>`).
28//!
29//! The full upstream `protovalidate-conformance` suite (2872 cases,
30//! covering proto2, proto3, and editions 2023) passes against code
31//! emitted by the paired plugin.
32
33pub mod cel;
34mod error;
35pub mod rules;
36
37#[cfg(feature = "connect")]
38mod connect;
39
40// Re-export `regex` so generated patterns (`::protovalidate_buffa::regex::Regex`)
41// resolve without each downstream crate having to add a direct `regex` dep.
42// `buffa` is re-exported for convenience but generated code uses the
43// `::buffa::` path directly; downstream crates already depend on buffa for
44// their message types.
45pub use buffa;
46/// IANA timezone database, re-exported so generated code can reference
47/// `::protovalidate_buffa::chrono_tz::Tz` when a CEL rule uses the
48/// timezone-argument form of a timestamp accessor
49/// (`t.getHours("America/New_York")`). Only exported when the `tz`
50/// feature is enabled — rules without tz args don't need this dep.
51#[cfg(feature = "tz")]
52pub use chrono_tz;
53pub use error::{FieldPath, FieldPathElement, FieldType, Subscript, ValidationError, Violation};
54/// `#[connect_impl]` — attribute macro applied to a Connect service `impl`
55/// block that inserts `req.validate()?` at the top of every handler method.
56/// Guarantees protovalidate runs for every RPC without relying on per-handler
57/// discipline.
58///
59/// Only exported when the `connect` feature is enabled (the default), since
60/// the emitted code calls [`ValidationError::into_connect_error`].
61#[cfg(feature = "connect")]
62pub use protovalidate_buffa_macros::connect_impl;
63pub use regex;
64
65pub trait Validate {
66 /// Runs every rule attached to this message (and any nested messages),
67 /// collecting violations rather than short-circuiting on the first.
68 ///
69 /// # Errors
70 ///
71 /// Returns a [`ValidationError`] containing one or more [`Violation`]s
72 /// when any rule fails. Callers typically map this to
73 /// `ConnectError::invalid_argument` via
74 /// [`ValidationError::into_connect_error`] (requires the `connect` feature).
75 fn validate(&self) -> Result<(), ValidationError>;
76}
77
78#[macro_export]
79macro_rules! field_path {
80 ( $( $part:expr ),* $(,)? ) => {{
81 let mut elements = ::std::vec::Vec::new();
82 $(
83 elements.push($crate::FieldPathElement {
84 field_number: None,
85 field_name: Some(::std::borrow::Cow::Borrowed($part)),
86 field_type: None,
87 key_type: None,
88 value_type: None,
89 subscript: None,
90 });
91 )*
92 $crate::FieldPath { elements }
93 }};
94}