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.

    import "buf/validate/validate.proto";
    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.

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.

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.

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

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;
}

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.

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.

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:

Terminal window
$ buf generate

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

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')"
}];
}

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.

Clone and build the protovalidate-cc repository:

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

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",
...
]
)

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", ...]
...
)

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
}

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

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