#validation #protobuf #cel

build protocheck-build

⚙️ Build-time entrypoint for protocheck

7 releases

Uses new Rust 2024

0.1.11 Oct 10, 2025
0.1.10 Oct 9, 2025
0.1.4 Aug 30, 2025

#411 in Build Utils

Download history 233/week @ 2025-08-13 66/week @ 2025-08-20 279/week @ 2025-08-27 13/week @ 2025-09-03 2/week @ 2025-09-17 11/week @ 2025-10-01 396/week @ 2025-10-08 21/week @ 2025-10-15 8/week @ 2025-10-22

436 downloads per month

Apache-2.0

11KB
117 lines

⚒️ Protocheck-build

This is the build-time entrypoint for protocheck.

➡️ Getting started

To get started, you need to use protocheck-build as a build dependency in your crate, which will use protocheck-proc-macro to add all the validation logic to your structs. The setup will look more or less like this (this is taken directly from the tests crate)

// In your build.rs file
use prost_build::Config;
use protocheck_build::{compile_protos_with_validators, get_proto_files_recursive};
use std::{
  env,
  path::PathBuf
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
  println!("cargo:rerun-if-changed=proto/");

  let out_dir = env::var("OUT_DIR")
    .map(PathBuf::from)
    .unwrap_or(env::temp_dir());

  let descriptor_path = out_dir.join("file_descriptor_set.bin");

  let proto_include_paths = &["proto"];

  // Use the helper to get all proto files recursively in a directory
  let proto_files = get_proto_files_recursive("proto")?;

  let mut config = Config::new();
  config
    .file_descriptor_set_path(&descriptor_path)
    // Enable the use of bytes::Bytes for `bytes` fields
    .bytes(["."])
    .out_dir(&out_dir);

  // Call the build helper
  compile_protos_with_validators(&mut config, &proto_files, proto_include_paths, &["myapp.v1"])?;

  // Compile protos
  config.compile_protos(&proto_files, proto_include_paths)?;

  // Set the env for the file descriptor location
  println!(
    "cargo:rustc-env=PROTO_DESCRIPTOR_SET={}",
    descriptor_path.display()
  );

  Ok(())
}

compile_protos_with_validators takes these arguments:

  1. The config struct.
  2. The proto files and include paths being used by the config.
  3. The list of packages to apply validators to. If a given message contains Cel validation or is validated as a field by another message, its package name must be included in this list.

The function will then:

  1. Compile your protos to create an intermediary descriptor
  2. Iterate its messages, and use the information extracted from them to add the derives and attributes to the actual config that are needed by protocheck-proc-macro to add the validation logic.
  3. Add the derive macros used by protocheck
  4. Use proto_types as the source for the well known types and the protovalidate types (it is re-exported by protocheck, so there is no need to add it as a dependency).

🍸 Tonic example

When using tonic-prost-build, the workflow is almost identical, except that we pass the config to compile_with_config:

  let mut config = Config::new();
  config
    .file_descriptor_set_path(&descriptor_path)
    .bytes(["."])
    .out_dir(&out_dir);

  // Call the build helper
  compile_protos_with_validators(&mut config, &proto_files, &["proto".to_string()], &["myapp.v1"])?;

  // Compile protos
  tonic_prost_build::configure()
    // We pass the config here
    .compile_with_config(config, &proto_files, &["proto".to_string()])?;
}

Dependencies

~9–15MB
~294K SLoC