Protovalidate is the semantic validation library for Protobuf. It provides standard annotations to validate common rules on messages and fields, as well as the ability to use CEL to write custom rules. It's the next generation of protoc-gen-validate.
With Protovalidate, you can annotate your Protobuf messages with both standard and custom validation rules:
syntax = "proto3";
package acme.user.v1;
import "buf/validate/validate.proto";
message User {
string id = 1 [(buf.validate.field).string.uuid = true];
uint32 age = 2 [(buf.validate.field).uint32.lte = 150]; // We can only hope.
string email = 3 [(buf.validate.field).string.email = true];
string first_name = 4 [(buf.validate.field).string.max_len = 64];
string last_name = 5 [(buf.validate.field).string.max_len = 64];
option (buf.validate.message).cel = {
id: "first_name_requires_last_name"
message: "last_name must be present if first_name is present"
expression: "!has(this.first_name) || has(this.last_name)"
};
}Once you've added @bufbuild/protovalidate to your project, validation is simple:
import { create } from "@bufbuild/protobuf";
import { createValidator } from "@bufbuild/protovalidate";
import { MoneyTransferSchema } from "./gen/banking_pb";
const transfer = create(MoneyTransferSchema);
const validator = createValidator();
const result = validator.validate(MoneyTransferSchema, transfer);
if (result.kind !== "valid") {
// Handle failure.
}Tip
The string.pattern rule supports regular expressions with CEL's standard RE2 syntax.
Protovalidate evaluates patterns with @bufbuild/re2, an RE2-compatible engine that executes in linear time, guarding against ReDoS. It is the default because it is scoped to what CEL and Protovalidate need, which keeps it small.
To evaluate patterns with a different engine, pass a regexMatch function. For example, with re2js, a complete port of RE2/J that offers a broader API at the cost of a larger bundle:
import { RE2JS } from "re2js";
import { createValidator } from "@bufbuild/protovalidate";
// Patterns come from schema rules, so the same handful are matched over and
// over. Caching the compiled form keeps repeat matches cheap.
const compiled = new Map<string, RE2JS>();
const validator = createValidator({
regexMatch: (pattern: string, against: string): boolean => {
let re = compiled.get(pattern);
if (re === undefined) {
re = RE2JS.compile(pattern);
compiled.set(pattern, re);
}
// Use `find`, which searches anywhere in the input, to match the
// unanchored semantics of CEL's `matches()`. `matches` requires the
// entire input to match and would reject values that Protovalidate
// considers valid.
return re.matcher(against).find();
},
});- @bufbuild/protovalidate: Validates Protobuf messages at runtime based on user-defined validation rules.
Note that protovalidate-es requires the Protobuf runtime @bufbuild/protobuf.
Protovalidate isn't just for ECMAScript! You might be interested in sibling repositories for other languages:
protovalidate-go(Go)protovalidate-java(Java)protovalidate-python(Python)protovalidate-cc(C++)
Additionally, protovalidate's core repository provides:
- Protovalidate's Protobuf API
- Example
.protofiles usingprotovalidate - Conformance testing utilities for acceptance testing of
protovalidateimplementations
We genuinely appreciate any help! If you'd like to contribute, check out these resources:
- Contributing Guidelines: Guidelines to make your contribution process straightforward and meaningful
- Conformance testing utilities: Utilities providing acceptance testing of
protovalidateimplementations - Protovalidate-ES conformance executor: Conformance testing executor
Offered under the Apache 2 license.