Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b7b68fb
Add benchmark suite for protovalidate-es
jonbodner-buf May 13, 2026
9a6d8b4
Add disableNativeRules option and native rule dispatcher seam
jonbodner-buf May 13, 2026
dacf5c0
Consolidate bench suites into a single case registry
jonbodner-buf May 14, 2026
a75f6bd
Merge jbodner/add-benchmarks
jonbodner-buf May 14, 2026
a37e50a
fix license header in checkbench.js
jonbodner-buf May 14, 2026
e055774
Merge branch 'jbodner/add-benchmarks' into jbodner/native-rules-phase…
jonbodner-buf May 14, 2026
fcb9a0c
Add native rule handlers for bool and the 12 numeric scalar types
jonbodner-buf May 14, 2026
bb20f88
Address phase 1 code review
jonbodner-buf May 14, 2026
7712e90
Drop unused kind discriminator from native dispatch result
jonbodner-buf May 14, 2026
58cbce2
Add native handlers for enum, repeated, and map rules
jonbodner-buf May 14, 2026
84598dd
Address phase 2 code review
jonbodner-buf May 14, 2026
5dc6d9a
Extract shared diff/compile helpers into native/testing.ts
jonbodner-buf May 14, 2026
057ebec
Add native handler for bytes rules
jonbodner-buf May 14, 2026
5fc1054
Address phase 3 code review
jonbodner-buf May 14, 2026
33755e1
Drop redundant *Descs aliases for bytes/enum/repeated/map
jonbodner-buf May 14, 2026
58f347b
Probe custom regexMatch at plan time for symmetry with default engine
jonbodner-buf May 14, 2026
e7af3f8
Merge branch 'main' into jbodner/add-benchmarks
jonbodner-buf May 22, 2026
efb400f
improve benchmark file selection and argument parsing.
jonbodner-buf May 26, 2026
35b0ab0
improve threshold validation
jonbodner-buf May 26, 2026
edc4761
relocate checkbench to the src directory and convert it to typescript…
jonbodner-buf May 27, 2026
5ec5fb2
remove unneeded file ignores from biome.json
jonbodner-buf May 27, 2026
f0cee20
Merge branch 'main' into jbodner/add-benchmarks
jonbodner-buf May 27, 2026
a2ffbb2
fix formatting
jonbodner-buf May 27, 2026
49b3106
Merge branch 'jbodner/add-benchmarks' of github.com:bufbuild/protoval…
jonbodner-buf May 27, 2026
492bc41
switch benchmarking from tinybench to mitata
jonbodner-buf May 27, 2026
70a2969
fix formatting
jonbodner-buf May 27, 2026
1fc15f5
improve benchmark stability by adding multi-run support and different…
jonbodner-buf May 28, 2026
52ede79
additional tweaks to improve noise
jonbodner-buf May 28, 2026
9e5d1e1
Merge branch 'jbodner/add-benchmarks' into jbodner/native-rules-phase…
jonbodner-buf May 28, 2026
cf1c101
don't include EvalExtendedRulesCel when there are no rules and don't…
jonbodner-buf May 28, 2026
b29b0ad
add --metric flag and drop min from regression gating
jonbodner-buf May 29, 2026
58ae4d2
define simpler benchmarking and checkbench
jonbodner-buf Jun 2, 2026
fac66b8
Merge branch 'jbodner/add-benchmarks' into jbodner/native-rules-phase…
jonbodner-buf Jun 2, 2026
48eb3b3
don't include EvalExtendedRulesCel when there are no rules and don't…
jonbodner-buf May 28, 2026
78bf859
Merge branch 'main' into jbodner/native-rules-phase-1-numeric-bool
jonbodner-buf Jun 5, 2026
4829305
remove obsolete benchmark code
jonbodner-buf Jun 5, 2026
f7fa861
respond to code review feedback
jonbodner-buf Jun 8, 2026
c11d6a8
Merge branch 'main' into jbodner/native-rules-phase-1-numeric-bool
jonbodner-buf Jun 9, 2026
974b6f9
Merge branch 'jbodner/native-rules-phase-2-enum-repeated-map' into jb…
jonbodner-buf Jun 11, 2026
8122b83
remove sites.ts from maps/enum/repeated
jonbodner-buf Jun 11, 2026
164f57b
Merge branch 'jbodner/native-rules-phase-3-bytes' into jbodner/native…
jonbodner-buf Jun 11, 2026
4fe448a
remove obsolete benchmark code. make sure benchmarks run against late…
jonbodner-buf Jun 11, 2026
8977ba4
Merge branch 'jbodner/native-rules-phase-1-numeric-bool' of github.co…
jonbodner-buf Jun 11, 2026
3a721c0
fix formatting
jonbodner-buf Jun 11, 2026
b3f864f
add support for disabling native tests in benchmark
jonbodner-buf Jun 11, 2026
7fe6e8f
string support completed
jonbodner-buf Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ if (result.kind !== "valid") {
>
> The `string.pattern` rule supports regular expressions with CEL's standard [RE2 syntax](https://github.com/google/re2/wiki/syntax).
>
> Protovalidate translates RE2 to ECMAScript's regular expressions. This works except for some RE2 flags, but it cannot support RE2's most important property: Execution in linear time, which guards against [ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS).
> Protovalidate evaluates patterns with [@bufbuild/re2](https://www.npmjs.com/package/@bufbuild/re2), an RE2-compatible engine that executes in linear time, guarding against [ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS).
>
> If you need full support for RE2, you can bring your own RE2 implementation:
> If you prefer a different engine, you can bring your own RE2 implementation:
>
> ```ts
> const validator = createValidator({
Expand Down
9 changes: 8 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions packages/protovalidate-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ so that runtime cost can be tracked across changes and compared cross-language.
With turborepo:

```shell
npx turbo run bench -- [regex] --dir <dir>
npx turbo run bench --filter=@bufbuild/protovalidate-bench -- [regex]
```

With npm (make sure to generate proto and build dependencies first):
This command will rebuild the `protovalidate` package and run the benchmarks. This is preferred to make sure
the latest changes are reflected in the results.

You can also run the benchmarks directly from the `protovalidate-bench` package:

```shell
npm run bench
Expand Down
10 changes: 8 additions & 2 deletions packages/protovalidate-bench/src/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import * as console from "node:console";
import { writeFileSync } from "node:fs";
import { parseArgs } from "node:util";
import { createValidator } from "@bufbuild/protovalidate";
import {
createValidator,
type ValidatorOptions,
} from "@bufbuild/protovalidate";
import { Bench, type Task } from "tinybench";
import { cases } from "./cases.js";

Expand Down Expand Up @@ -62,7 +65,10 @@ if (tests.length == 0) {
}

const bench = new Bench({ name: "protovalidate benchmarks", time: 100 });
const validator = createValidator();
const disableNative = process.env.DISABLE_NATIVE_RULES;
const opts: ValidatorOptions =
disableNative !== undefined ? { disableNativeRules: true } : {};
const validator = createValidator(opts);
for (const test of tests) {
bench.add(test.name, () => {
validator.validate(test.schema, test.fixture);
Expand Down
8 changes: 7 additions & 1 deletion packages/protovalidate-testing/src/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ if (!request.fdset) {
throw new Error(`Empty request field "fdset"`);
}
const registry = createFileRegistry(request.fdset);
const validator = createValidator({ registry });
// Set PROTOVALIDATE_DISABLE_NATIVE_RULES=1 to run the conformance suite
// against the pure-CEL path, proving equivalence with the (default-on)
// native rules.
const validator = createValidator({
registry,
disableNativeRules: process.env.PROTOVALIDATE_DISABLE_NATIVE_RULES === "1",
});
const response = create(TestConformanceResponseSchema);
for (const [name, any] of Object.entries(request.cases)) {
const testResult = create(TestResultSchema);
Expand Down
5 changes: 3 additions & 2 deletions packages/protovalidate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"postfetch-proto": "license-header proto",
"generate": "buf generate",
"postgenerate": "license-header src/gen",
"test": "npx tsx --test ./src/*.test.ts",
"test": "npx tsx --test ./src/*.test.ts ./src/**/*.test.ts",
"prebuild": "rm -rf ./dist/*",
"build": "npm run build:cjs && npm run build:esm",
"build:cjs": "tsc --project tsconfig.json --module commonjs --verbatimModuleSyntax false --moduleResolution node10 --outDir ./dist/cjs && echo >./dist/cjs/package.json '{\"type\":\"commonjs\"}'",
Expand All @@ -42,7 +42,8 @@
}
},
"dependencies": {
"@bufbuild/cel": "0.4.0"
"@bufbuild/cel": "0.4.0",
"@bufbuild/re2": "^0.6.0"
},
"peerDependencies": {
"@bufbuild/protobuf": "^2.8.0"
Expand Down
47 changes: 47 additions & 0 deletions packages/protovalidate/src/native/bool.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2024-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { suite, test } from "node:test";
import { create } from "@bufbuild/protobuf";
import { compile, diff } from "./testing.js";

void suite("native bool rules", () => {
void suite("bool.const", () => {
const schema = compile(
`message M {
bool b = 1 [(buf.validate.field).bool.const = true];
}`,
);
void test("matches: valid", () => {
diff(schema, create(schema, { b: true }));
});
void test("mismatches: invalid", () => {
diff(schema, create(schema, { b: false }));
});
});

void suite("BoolValue wrapper", () => {
const schema = compile(
`message M {
google.protobuf.BoolValue b = 1 [(buf.validate.field).bool.const = true];
}`,
);
void test("inner value matches: valid", () => {
diff(schema, create(schema, { b: true }));
});
void test("inner value mismatches: invalid", () => {
diff(schema, create(schema, { b: false }));
});
});
});
78 changes: 78 additions & 0 deletions packages/protovalidate/src/native/bool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2024-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { isFieldSet } from "@bufbuild/protobuf";
import type {
Path,
PathBuilder,
ScalarValue,
} from "@bufbuild/protobuf/reflect";
import type { Cursor } from "../cursor.js";
import type { Eval } from "../eval.js";
import {
type BoolRules,
BoolRulesSchema,
} from "../gen/buf/validate/validate_pb.js";
import type { ScalarNativeResult } from "./dispatcher.js";

/**
* Native evaluator for `bool.const`.
*
* Bool only supports the `const` rule. Anything else on a BoolRules instance
* falls through to CEL via the dispatcher.
*/
class EvalNativeBoolRules implements Eval<ScalarValue> {
constructor(
private readonly forMapKey: boolean,
private readonly constVal: boolean,
private readonly rulePath: Path,
) {}

eval(val: ScalarValue, cursor: Cursor): void {
if (val !== this.constVal) {
cursor.violate(
`must equal ${this.constVal}`,
"bool.const",
this.rulePath,
this.forMapKey,
);
}
}

prune(): boolean {
return false;
}
}

/**
* Try to build a native evaluator for BoolRules. Returns `undefined` if no
* native handler applies (no const set, or unknown extensions present).
*/
export function tryBuildNativeBoolRules(
rules: BoolRules,
rulePath: PathBuilder,
forMapKey: boolean,
): ScalarNativeResult | undefined {
if (rules.$unknown && rules.$unknown.length > 0) {
return undefined;
}
if (!isFieldSet(rules, BoolRulesSchema.field.const)) {
return undefined;
}
const path = rulePath.clone().field(BoolRulesSchema.field.const).toPath();
return {
eval: new EvalNativeBoolRules(forMapKey, rules.const, path),
handledFields: new Set([BoolRulesSchema.field.const]),
};
}
Loading
Loading