Skip to content

C++ quickstart#

Getting started with Protovalidate is simple if you're familiar with C++ and Buf—otherwise, you may want to follow the step-by-step guide.

  1. Add buf.build/bufbuild/protovalidate to buf.yaml then buf dep update.
  2. Add validation rules and generate code.
    message User {
        string name = 1 [(buf.validate.field).required = true];
    }
    
  3. Install Protovalidate using Bazel WORKSPACE, bzlmod, or build from source.
  4. Validate Protobuf messages:
    std::unique_ptr<buf::validate::ValidatorFactory> factory =
      buf::validate::ValidatorFactory::New().value();
    google::protobuf::Arena arena;
    buf::validate::Validator validator = factory->NewValidator(&arena);
    buf::validate::Violations results = validator.Validate(moneyTransfer).value();
    if (results.violations_size() > 0) {
        // Handle failure
    }
    
  5. Validate RPC requests with gRPC interceptors.

Step-by-step guide#

This guide shows how to integrate Protovalidate into C++ projects using the Buf CLI and your choice of a Bazel WORKSPACE, bzlmod, or building protovalidate-cc directly from source.

Add Protovalidate to schemas#

Depend on Protovalidate#

Protovalidate is available through the Buf Schema Registry and provides the Protobuf extensions, options, and messages powering validation.

Add it as a dependency in buf.yaml:

buf.yaml
version: v2
modules:
  - path: proto
+ deps:
+   - buf.build/bufbuild/protovalidate
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE

Don't forget to run buf dep update. You may see a warning that Protovalidate hasn't yet been used. That's fine.

$ buf dep update
WARN    Module buf.build/bufbuild/protovalidate is declared in your buf.yaml deps but is unused...

Add rules to a message#

Add rules to your Protobuf messages describing validation constraints. In this example, we're adding rules to enforce valid latitudes and longitudes in GetWeatherRequest:

weather_service.proto
syntax = "proto3";

package bufbuild.weather.v1;

+ import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";

// GetWeatherRequest is a request for weather at a point on Earth.
message GetWeatherRequest {
  // latitude must be between -90 and 90, inclusive, to be valid. Use of a
  // float allows precision to about one square meter.
- float latitude = 1;
+ float latitude = 1 [
+   (buf.validate.field).float.gte = -90,
+   (buf.validate.field).float.lte = 90
+ ];

  // longitude must be between -180 and 180, inclusive, to be valid. Use of a
  // float allows precision to about one square meter.
- float longitude = 2;
+ float longitude = 2 [
+   (buf.validate.field).float.gte = -180,
+   (buf.validate.field).float.lte = 180
+ ];

  // forecast_date for the weather request. It must be within the next
  // three days.
  google.protobuf.Timestamp forecast_date = 3;
}

Try it in the Playground#

Experiment with Protovalidate rules in the Protovalidate playground—modify this example, try out a custom CEL rule, or write your own validation logic without any local setup.

Lint Protovalidate rules#

Some rule combinations compile successfully but fail at runtime. This example requires latitude but also skips its validation when it has its zero value, creating a logical contradiction:

Example IGNORE_IF_ZERO_VALUE lint error
message GetWeatherRequest {
  float latitude = 1 [
    (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
    (buf.validate.field).required = true,
    (buf.validate.field).float.gte = -90,
    (buf.validate.field).float.lte = 90
  ];
}

buf lint identifies these and other problems, like invalid CEL expressions, with its PROTOVALIDATE rule :

Buf lint errors for the PROTOVALIDATE rule
$ buf lint
proto/bufbuild/weather/v1/weather_service.proto:29:5:Field "latitude" has both
(buf.validate.field).required and (buf.validate.field).ignore=IGNORE_IF_ZERO_VALUE.
A field cannot be empty if it is required.

Run buf lint whenever you edit your schemas and in GitHub Actions or other CI/CD tools.

Generate code#

With Protovalidate, you don't need any new code generation plugins: its rules are compiled as part of your message descriptors.

You do need to re-generate code after changing rules:

$ buf generate

To learn more about generating code with the Buf CLI, read the code generation overview.

Add business logic with CEL#

Real world validation rules are often complicated and need more than a simple set of static rules:

  1. A BuyMovieTicketsRequest request must be for a showtime in the future but no more than two weeks in the future.
  2. A CreateTeamRequest with repeated members must ensure all email addresses are unique across the team.
  3. A ScheduleMeetingRequest must have a start_time before its end_time, and the meeting duration can't exceed 8 hours.

Protovalidate can meet all of these requirements because all Protovalidate rules are defined in Common Expression Language (CEL). CEL is a lightweight, high-performance expression language that allows expressions like this.first_flight_duration + this.second_flight_duration < duration('48h') to evaluate consistently across languages.

Adding a CEL-based rule to a field is straightforward. Instead of a providing a static value, you provide a unique identifier (id), an error message, and a CEL expression. Building on the prior GetWeatherRequest example, this custom rule states that users must ask for weather forecasts within the next 72 hours:

proto/bufbuild/weather/v1/weather_service.proto
syntax = "proto3";

package bufbuild.weather.v1;

import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";

// GetWeatherRequest is a request for weather at a point on Earth.
message GetWeatherRequest {
  // latitude must be between -90 and 90, inclusive, to be valid. Use of a
  // float allows precision to about one square meter.
  float latitude = 1 [
    (buf.validate.field).float.gte = -90,
    (buf.validate.field).float.lte = 90
  ];
  // longitude must be between -180 and 180, inclusive, to be valid. Use of a
  // float allows precision to about one square meter.
  float longitude = 2 [
    (buf.validate.field).float.gte = -180,
    (buf.validate.field).float.lte = 180
  ];

  // forecast_date for the weather request. It must be within the next
  // three days.
- google.protobuf.Timestamp forecast_date = 3;
+ google.protobuf.Timestamp forecast_date = 3 [(buf.validate.field).cel = {
+     id: "forecast_date.within_72_hours"
+     message: "Forecast date must be in the next 72 hours."
+     expression: "this >= now && this <= now + duration('72h')"
+ }];
}

Install protovalidate-cc#

Choose one of three installation methods based on your build system.

Remember to always check for the latest version of protovalidate-cc on the project's GitHub releases page to ensure you're using the most up-to-date version.

Build from source#

Clone and build the protovalidate-cc repository:

$ git clone https://github.com/bufbuild/protovalidate-cc.git
$ cd protovalidate-cc
$ make build

Bazel WORKSPACE#

Add protovalidate-cc as an external repository in your WORKSPACE file:

Bazel WORKSPACE file
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "com_github_bufbuild_protovalidate_cc",
    sha256 = ...,
    strip_prefix = "protovalidate-cc-{version}",
    urls = [
        "https://github.com/bufbuild/protovalidate-cc/releases/download/v{version}/protovalidate-cc-{version}.tar.gz",
    ],
)

load("@com_github_bufbuild_protovalidate_cc//bazel:deps.bzl", "protovalidate_cc_dependencies")

protovalidate_cc_dependencies()

Then add a dependency to your cc_library or cc_binary target:

cc_library(
    ...
    deps = [
        "@com_github_bufbuild_protovalidate_cc//buf/validate:validator",
        ...
    ]
)

bzlmod#

To use protovalidate-cc as an external dependency for bzlmod, add the following to MODULE.bazel:

MODULE.bazel
module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "cel-cpp", repo_name = "com_google_cel_cpp", version="0.11.0")
bazel_dep(name = "protovalidate-cc", version = "1.0.0-rc.2")

Then add the following to your BUILD.bazel:

BUILD.bazel
cc_binary(
   ...
   deps = [ ..., "@protovalidate-cc//buf/validate:validator", ...]
   ...
)

Validate messages#

Use the Protovalidate validator to check messages against your validation rules:

Runtime validation with protovalidate-cc
std::unique_ptr<buf::validate::ValidatorFactory> factory =
  buf::validate::ValidatorFactory::New().value();
google::protobuf::Arena arena;
buf::validate::Validator validator = factory->NewValidator(&arena);
buf::validate::Violations results = validator.Validate(moneyTransfer).value();
if (results.violations_size() > 0) {
    // Handle failure
}

Validate RPC requests#

One of Protovalidate's most common use cases is for validating requests made to RPCs. Use gRPC interceptors for automatic validation across your APIs.

Next steps#

Read on to learn more about enabling schema-first validation with Protovalidate: