Skip to content

mathematic-inc/protovalidate-buffa

Repository files navigation

protovalidate-buffa

Static-codegen protovalidate for the buffa Rust protobuf runtime.

Annotate your .proto messages with (buf.validate.*) rules; a codegen plugin emits pure-Rust impl Validate blocks per message. Handlers call req.validate()? at entry (or use the #[connect_impl] macro to do it automatically on every handler in a service impl).

For what the rules mean, the full rule catalogue, CEL semantics, and design docs, read the upstream project — this crate intentionally does not duplicate that material:

Status

Conformance: 2872 / 2872 (100%) against the upstream protovalidate-conformance harness, covering proto2, proto3, and editions 2023.

Why a different crate?

Existing Rust implementations of protovalidate (prost-protovalidate, protocheck, protify) all target prost. buffa has a different runtime model — two-tier owned/borrowed types with zero-copy views, and static generated types rather than descriptor-driven dynamic messages — so prost-based validators are incompatible. This repo fills that gap.

Compared to reflection-based implementations, the codegen approach has two characteristics:

  • No runtime descriptor lookup. Every validate() is a direct struct field walk that LLVM can inline.
  • Schema-aware compile errors. Rule / field type mismatches, malformed message.oneof specs, and CEL expressions that reference non-existent fields are surfaced at codegen time rather than at first-call.

Crates

Crate Purpose
protovalidate-buffa Runtime library: Validate trait, structured ValidationError (with typed compile_error / runtime_error slots), Violation / FieldPath, rule helpers, CelScalar widening trait + Duration/Timestamp helpers, Connect error adapter. CEL rules are transpiled to native Rust at codegen time, so the runtime carries no interpreter.
protovalidate-buffa-macros #[connect_impl] attribute macro — inserts request validation at the top of every handler in a service impl block. Re-exported from the runtime crate.
protoc-gen-protovalidate-buffa Codegen plugin. Reads (buf.validate.*) extensions off descriptors via buffa's ExtensionSet, emits impl Validate for Foo blocks. Wire into buf.gen.yaml.
protovalidate-buffa-protos Compiled Rust for buf/validate/validate.proto (vendored under proto/). Consumed by the codegen plugin.

protovalidate-buffa-conformance also lives in this workspace but is private (publish = false) — see its README for the conformance test-run flow.

Compatibility

This workspace currently targets buffa 0.8, connectrpc 0.8, and Rust 1.88+ (edition 2024).

The emitted validators reference buffa's generated-code shape directly (field placement, map and view types), so the plugin and your buffa-build output must agree on the buffa minor version. buffa is pre-1.0 and treats minor bumps as breaking, so upgrading buffa generally means upgrading this crate in lockstep.

buffa 0.9 is intentionally not adopted yet: connectrpc 0.8 still depends on buffa ^0.8, so moving this workspace to 0.9 would link two incompatible buffa versions and break the default connect feature for downstream handlers. This crate will move to buffa 0.9 once connectrpc does.

Supported rules

Every rule family in the upstream standard-rules catalogue plus predefined rules is implemented — that's what the 2872 / 2872 conformance number above is measuring. See the upstream docs for semantics; this repo doesn't maintain a parallel list.

Quick start

For the proto-annotation side (which rules exist, how to combine them, CEL syntax), follow the upstream protovalidate quick start. The Rust-specific bits are:

# Install the plugin
cargo install --git https://github.com/mathematic-inc/protovalidate-buffa protoc-gen-protovalidate-buffa

Add to your buf.gen.yaml:

- local: protoc-gen-protovalidate-buffa
  out: gen/protovalidate
  strategy: all

Annotate a proto (see upstream for the full rule vocabulary):

syntax = "proto3";
import "buf/validate/validate.proto";

message CreateUserRequest {
  string email = 1 [(buf.validate.field).string = { min_len: 5, max_len: 254, email: true }];
  int32 age = 3 [(buf.validate.field).int32 = { gte: 13, lte: 150 }];
}

Use in a Connect handler:

use protovalidate_buffa::Validate;

#[protovalidate_buffa::connect_impl]
impl UserService for UserServiceImpl {
    async fn create_user(
        &self,
        ctx: Context,
        request: connectrpc::ServiceRequest<'_, pb::CreateUserRequest>,
    ) -> Result<(pb::CreateUserResponse, Context), ConnectError> {
        // #[connect_impl] validates the request here automatically.
        // Body only sees already-validated requests.
    }
}

Error model

Validate::validate returns Result<(), ValidationError>:

pub struct ValidationError {
    pub violations: Vec<Violation>,
    pub compile_error: Option<String>,  // schema mismatch detected at codegen time
    pub runtime_error: Option<String>,  // rule precondition failed (e.g. non-UTF-8 bytes under `pattern`)
}

Match on the typed fields rather than stringly-typed rule-id prefixes. Violation / FieldPath mirror the upstream proto shape — see those message definitions for field semantics. The connect feature provides ValidationError::into_connect_error mapping to InvalidArgument.

Conformance testing

See crates/protovalidate-buffa-conformance/README.md for how to build the dispatch binary and drive the upstream harness locally. CI runs cargo clippy --workspace --all-targets -- -D warnings and cargo test --workspace on every push; conformance is currently a local-only / pre-release check.

License

Dual-licensed under Apache-2.0 or MIT at your option.

About

Static-codegen protovalidate for the buffa Rust protobuf runtime

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages