diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 2a60a6887..000000000
--- a/.editorconfig
+++ /dev/null
@@ -1,6 +0,0 @@
-[*]
-charset = utf-8
-
-[*.java]
-ij_java_class_count_to_use_import_on_demand = 999
-ij_java_use_single_class_imports = true
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 7b5280256..c9d495979 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -175,6 +175,6 @@ working together to make `protovalidate` the best it can be.
[file-bug]: https://github.com/bufbuild/protovalidate-java/issues/new?assignees=&labels=Bug&template=bug_report.md&title=%5BBUG%5D
-[file-feature-request]: https://github.com/bufbuild/protovalidate-java/issues/new?assignees=&labels=Feature&template=feature_request.md&title=%5BFeature+Request%5D
+[file-feature-request]: https://github.com/bufbuild/protovalidate=java/issues/new?assignees=&labels=Feature&template=feature_request.md&title=%5BFeature+Request%5D
[cel-spec]: https://github.com/google/cel-spec
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 49e66ebf8..23779eb76 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -36,6 +36,7 @@ assignees: ''
- **Version**:
- **Compiler/Toolchain**:
- **Protobuf Compiler & Version**:
+- **Protoc-gen-validate Version**:
- **Protovalidate Version**:
## Possible Solution
diff --git a/.github/buf-logo.svg b/.github/buf-logo.svg
index 2a24f001f..35bcafdee 100644
--- a/.github/buf-logo.svg
+++ b/.github/buf-logo.svg
@@ -1,10 +1 @@
-
+
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 5b5af4145..10ef83118 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -8,4 +8,3 @@ updates:
directory: "/"
schedule:
interval: "weekly"
-
diff --git a/.github/release.yml b/.github/release.yml
deleted file mode 100644
index 97b937216..000000000
--- a/.github/release.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-changelog:
- exclude:
- labels:
- - ignore-for-release
- authors:
- - dependabot[bot]
diff --git a/.github/workflows/add-to-project.yaml b/.github/workflows/add-to-project.yaml
deleted file mode 100644
index e3c0b4225..000000000
--- a/.github/workflows/add-to-project.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Add issues and PRs to project
-
-on:
- issues:
- types:
- - opened
- - reopened
- - transferred
- pull_request_target:
- types:
- - opened
- - reopened
- issue_comment:
- types:
- - created
-
-jobs:
- call-workflow-add-to-project:
- name: Call workflow to add issue to project
- uses: bufbuild/base-workflows/.github/workflows/add-to-project.yaml@main
- secrets: inherit
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index d04d969ac..02006c8b1 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -16,27 +16,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - name: Cache Go Modules
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ uses: actions/checkout@v3
with:
- path: |
- ~/.cache/go-build
- ~/go/pkg/mod
- key: ${{ runner.os }}-gomod-ci-${{ hashFiles('gradle.properties', 'gradle/libs.versions.toml') }}
- restore-keys:
- ${{ runner.os }}-gomod-ci-
- - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
+ fetch-depth: 1
+ - uses: actions/setup-java@v3
with:
distribution: 'temurin'
- java-version: '21'
+ java-version: '17'
cache: 'gradle'
- - uses: bufbuild/buf-action@8c6a16e16f12ba20b6470afa9c2ba9b5ba8c97c3 # v1.5.0
- with:
- setup_only: true
- token: ${{ secrets.BUF_TOKEN }}
- - name: Validate Gradle Wrapper
- uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Lint
run: make lint
- name: Generate
diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml
index e805a7edc..121b27af9 100644
--- a/.github/workflows/conformance.yaml
+++ b/.github/workflows/conformance.yaml
@@ -16,26 +16,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - name: Cache Go Modules
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ uses: actions/checkout@v3
with:
- path: |
- ~/.cache/go-build
- ~/go/pkg/mod
- key: ${{ runner.os }}-gomod-conformance-${{ hashFiles('gradle.properties', 'gradle/libs.versions.toml') }}
- restore-keys:
- ${{ runner.os }}-gomod-conformance-
- - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
+ fetch-depth: 1
+ - uses: actions/setup-java@v3
with:
distribution: 'temurin'
- java-version: '21'
+ java-version: '17'
cache: 'gradle'
- - uses: bufbuild/buf-action@8c6a16e16f12ba20b6470afa9c2ba9b5ba8c97c3 # v1.5.0
- with:
- setup_only: true
- token: ${{ secrets.BUF_TOKEN }}
- - name: Validate Gradle Wrapper
- uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Test conformance
- run: make conformance
+ run: make conformance
\ No newline at end of file
diff --git a/.github/workflows/pr-hygiene.yaml b/.github/workflows/pr-hygiene.yaml
index f75d0bc76..29e48ab30 100644
--- a/.github/workflows/pr-hygiene.yaml
+++ b/.github/workflows/pr-hygiene.yaml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Lint title
- uses: morrisoncole/pr-lint-action@51f3cfabaf5d46f94e54524214e45685f0401b2a # v1.7.1
+ uses: morrisoncole/pr-lint-action@v1.7.0
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# https://regex101.com/r/I6oK5v/1
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 5f9fa5aec..eb5e1b939 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -1,39 +1,25 @@
name: Gradle Publish
on:
- push:
- tags:
- - v*
- workflow_dispatch: {} # support manual runs
-permissions:
- contents: write
+ release:
+ types: [created]
jobs:
release:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - name: Cache Go Modules
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- with:
- path: |
- ~/.cache/go-build
- ~/go/pkg/mod
- key: ${{ runner.os }}-gomod-ci-${{ hashFiles('gradle.properties', 'gradle/libs.versions.toml') }}
- restore-keys:
- ${{ runner.os }}-gomod-ci-
- - name: Set up JDK
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
+ - uses: actions/checkout@v3
+
+ - name: Set up JDK 8
+ uses: actions/setup-java@v3
with:
distribution: 'temurin'
- java-version: '21'
- - uses: bufbuild/buf-action@8c6a16e16f12ba20b6470afa9c2ba9b5ba8c97c3 # v1.5.0
- with:
- setup_only: true
- token: ${{ secrets.BUF_TOKEN }}
+ java-version: '8'
+
- name: Validate Gradle Wrapper
- uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ uses: gradle/wrapper-validation-action@v1
+
- name: Configure GPG signing & publish
env:
GPG_KEY: ${{ secrets.GPG_KEY }}
diff --git a/.gitignore b/.gitignore
index e6ddc561b..603c3b5fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,8 +7,7 @@
*.iml
/.idea
/.tmp
-/bin/
# Cache of project
/.gradletasknamecache
# Ignore Gradle GUI config
-/gradle-app.setting
\ No newline at end of file
+/gradle-app.setting
diff --git a/LICENSE b/LICENSE
index 3e1d480e1..dbb493206 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2023-2025 Buf Technologies, Inc.
+ Copyright 2023 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.
@@ -198,4 +198,4 @@
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.
+ limitations under the License.
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 2aa35c07d..23bca38ad 100644
--- a/Makefile
+++ b/Makefile
@@ -6,18 +6,31 @@ SHELL := bash
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-print-directory
-GRADLE ?= ./gradlew
+BIN := .tmp/bin
+COPYRIGHT_YEARS := 2023
+LICENSE_IGNORE :=
+JAVA_VERSION = 20
+JAVAC = javac
+JAVA = java
+GO ?= go
+ARGS ?= --strict_message --streaming_host="http://localhost:1235"
+JAVA_COMPILE_OPTIONS = --enable-preview --release $(JAVA_VERSION)
+JAVA_OPTIONS = --enable-preview
+PROTOVALIDATE_VERSION ?= 7a97b32c49d7e9caec91e2fe104cc414d494f4e8
+JAVA_MAIN_CLASS = build.buf.protovalidate
+JAVA_SOURCES = $(wildcard src/main/java/**/**/**/*.java, src/main/java/**/**/*.java)
+JAVA_CLASSES = $(patsubst src/main/java/%.java, target/classes/%.class, $(JAVA_SOURCES))
.PHONY: all
all: lint generate build docs conformance ## Run all tests and lint (default)
.PHONY: build
build: ## Build the entire project.
- $(GRADLE) build
+ ./gradlew build
.PHONY: docs
docs: ## Build javadocs for the project.
- $(GRADLE) javadoc
+ ./gradlew javadoc
.PHONY: checkgenerate
checkgenerate: generate ## Checks if `make generate` produces a diff.
@@ -26,37 +39,58 @@ checkgenerate: generate ## Checks if `make generate` produces a diff.
.PHONY: clean
clean: ## Delete intermediate build artifacts
- $(GRADLE) clean
+ @# -X only removes untracked files, -d recurses into directories, -f actually removes files/dirs
+ git clean -Xdf
.PHONY: conformance
-conformance: ## Execute conformance tests with native rule evaluators enabled and disabled.
- ENABLE_NATIVE_RULES=true $(GRADLE) conformance:conformance
- ENABLE_NATIVE_RULES=false $(GRADLE) conformance:conformance
+conformance: build $(BIN)/protovalidate-conformance ## Execute conformance tests.
+ ./conformance/conformance.sh &
+ $(BIN)/protovalidate-conformance $(ARGS)
+ pkill -f "java"
+
+.PHONY: generate-license
+generate-license: $(BIN)/license-header ## Generates license headers for all source files.
+ $(BIN)/license-header \
+ --license-type apache \
+ --copyright-holder "Buf Technologies, Inc." \
+ --year-range "$(COPYRIGHT_YEARS)" $(LICENSE_IGNORE)
.PHONY: help
-help: ## Describe useful make targets
+help: ## Describe useful make targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-15s %s\n", $$1, $$2}'
.PHONY: generate
-generate: ## Regenerate code and license headers
- $(GRADLE) generate
+generate: generate-license ## Regenerate code and license headers
.PHONY: lint
lint: ## Lint code
- $(GRADLE) spotlessCheck
+ ./gradlew spotlessCheck
.PHONY: lintfix
lintfix: ## Applies the lint changes.
- $(GRADLE) spotlessApply
+ ./gradlew spotlessApply
.PHONY: release
-release: ## Upload artifacts to Maven Central.
- $(GRADLE) --info publishAndReleaseToMavenCentral --stacktrace --no-daemon --no-parallel --no-configuration-cache
+release: ## Upload artifacts to Sonatype Nexus.
+ ./gradlew --info publish --stacktrace --no-daemon --no-parallel
+ ./gradlew --info closeAndReleaseRepository
.PHONY: releaselocal
releaselocal: ## Release artifacts to local maven repository.
- $(GRADLE) --info publishToMavenLocal
+ ./gradlew --info publishToMavenLocal
.PHONY: test
test: ## Run all tests.
- $(GRADLE) test
+ ./gradlew test
+
+$(BIN):
+ @mkdir -p $(BIN)
+
+$(BIN)/license-header: $(BIN) Makefile
+ GOBIN=$(abspath $(@D)) $(GO) install \
+ github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@latest
+
+$(BIN)/protovalidate-conformance: $(BIN) Makefile
+ GOBIN=$(abspath $(BIN)) $(GO) install \
+ github.com/bufbuild/protovalidate/tools/protovalidate-conformance@$(PROTOVALIDATE_VERSION)
+
diff --git a/README.md b/README.md
index 7a535db8b..c9f324f91 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,33 @@
-[][buf]
-
-# protovalidate-java
+# [][buf] protovalidate-java
[](https://github.com/bufbuild/protovalidate-java/actions/workflows/ci.yaml)
[](https://github.com/bufbuild/protovalidate-java/actions/workflows/conformance.yaml)
[][buf-mod]
-[Protovalidate][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][cel] to write custom rules. It's the next generation of [protoc-gen-validate][protoc-gen-validate].
+`protovalidate-java` is the Java language implementation of [`protovalidate`](https://github.com/bufbuild/protovalidate) designed to validate Protobuf messages at runtime based on user-defined validation constraints. Powered by Google's Common Expression Language ([CEL](https://github.com/google/cel-spec)), it provides a flexible and efficient foundation for defining and evaluating custom validation rules. The primary goal of `protovalidate` is to help developers ensure data consistency and integrity across the network without requiring generated code.
-With Protovalidate, you can annotate your Protobuf messages with both standard and custom validation rules:
+## The `protovalidate` project
-```protobuf
-syntax = "proto3";
+Head over to the core [`protovalidate`](https://github.com/bufbuild/protovalidate/) repository for:
-package acme.user.v1;
-
-import "buf/validate/validate.proto";
+- [The API definition](https://github.com/bufbuild/protovalidate/tree/main/proto/protovalidate/buf/validate/validate.proto): used to describe validation constraints
+- [Documentation](https://github.com/bufbuild/protovalidate/tree/main/docs): how to apply `protovalidate` effectively
+- [Migration tooling](https://github.com/bufbuild/protovalidate/tree/main/docs/migrate.md): incrementally migrate from `protoc-gen-validate`
+- [Conformance testing utilities](https://github.com/bufbuild/protovalidate/tree/main/docs/conformance.md): for acceptance testing of `protovalidate` implementations
-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];
+Other `protovalidate` runtime implementations include:
- 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)"
- };
-}
-```
+- C++: [`protovalidate-cc`](https://github.com/bufbuild/protovalidate-cc)
+- Go: [`protovalidate-go`](https://github.com/bufbuild/protovalidate-go)
+- Python: [`protovalidate-python`](https://github.com/bufbuild/protovalidate-python)
-Once you've added `protovalidate-java` to your project, validation is idiomatic Java:
+And others coming soon:
-```java
-ValidationResult result = validator.validate(message);
-if (!result.isSuccess()) {
- // Handle failure.
-}
-```
+- TypeScript: `protovalidate-ts`
## Installation
-> [!TIP]
-> The easiest way to get started with Protovalidate for RPC APIs are the quickstarts in Buf's documentation. There's one available for [Java and gRPC][grpc-java].
-
-`protovalidate-java` is listed in [Maven Central][maven], which provides installation snippets for Gradle, Maven, and other package managers. In Gradle, it's:
+To include `protovalidate-java` in your project, add the following to your build file:
```gradle
dependencies {
@@ -54,82 +35,109 @@ dependencies {
}
```
-## Documentation
+Remember to always check for the latest version of `protovalidate-java` on the project's [GitHub releases page](https://github.com/bufbuild/protovalidate-java/releases) to ensure you're using the most up-to-date version.
-Comprehensive documentation for Protovalidate is available at [protovalidate.com][protovalidate].
+## Usage
-Highlights for Java developers include:
+### Implementing validation constraints
-* The [developer quickstart][quickstart]
-* A comprehensive RPC quickstart for [Java and gRPC][grpc-java]
-* A [migration guide for protoc-gen-validate][migration-guide] users
+Validation constraints are defined directly within `.proto` files. Documentation for adding constraints can be found in the `protovalidate` project [README](https://github.com/bufbuild/protovalidate) and its [comprehensive docs](https://github.com/bufbuild/protovalidate/tree/main/docs).
-## Native rule evaluators (opt-out)
+```protobuf
+syntax = "proto3";
-The standard rules can be evaluated either through CEL or through native Java code. Native evaluation is functionally identical (the conformance suite passes in both modes) but skips CEL compilation and runtime overhead for the rules it covers — a single `validate()` call on a complex message can run an order of magnitude faster and allocate ~10× less.
+package my.package;
-Native rules are **opt-out**. Disable them by configuring the validator:
+import "google/protobuf/timestamp.proto";
+import "buf/validate/validate.proto";
-```java
-Config config = Config.newBuilder().setEnableNativeRules(false).build();
-Validator validator = ValidatorFactory.newBuilder().withConfig(config).build();
+message Transaction {
+ uint64 id = 1 [(buf.validate.field).uint64.gt = 999];
+ google.protobuf.Timestamp purchase_date = 2;
+ google.protobuf.Timestamp delivery_date = 3;
+
+ string price = 4 [(buf.validate.field).cel = {
+ id: "transaction.price",
+ message: "price must be positive and include a valid currency symbol ($ or £)",
+ expression: "(this.startsWith('$') || this.startsWith('£')) && double(this.substring(1)) > 0"
+ }];
+
+ option (buf.validate.message).cel = {
+ id: "transaction.delivery_date",
+ message: "delivery date must be after purchase date",
+ expression: "this.delivery_date > this.purchase_date"
+ };
+}
```
-Forward compatibility is preserved by a clone-and-clear contract: when protovalidate adds a new rule that this codebase hasn't yet implemented natively, the rule remains on the residual `FieldRules` and CEL enforces it. Native evaluation is an optimization, never a replacement.
-
-## Additional languages and repositories
-
-Protovalidate isn't just for Java! You might be interested in sibling repositories for other languages:
-
-- [`protovalidate-go`][pv-go] (Go)
-- [`protovalidate-python`][pv-python] (Python)
-- [`protovalidate-cc`][pv-cc] (C++)
-- [`protovalidate-es`][pv-es] (TypeScript and JavaScript)
+### Example
-Additionally, [protovalidate's core repository](https://github.com/bufbuild/protovalidate) provides:
+In your Java code, create an instance of the `Validator` class and use the `validate` method to validate your messages.
-- [Protovalidate's Protobuf API][validate-proto]
-- [Conformance testing utilities][conformance] for acceptance testing of `protovalidate` implementations
-
-
-## Contributing
+```java
+// Import the required packages
+package build.buf;
+
+import build.buf.protovalidate.results.ValidationException;
+import build.buf.protovalidate.results.ValidationResult;
+import com.my.package.Transaction;
+import com.google.protobuf.Timestamp;
+
+import build.buf.protovalidate.Validator;
+import build.buf.protovalidate.Config;
+
+public class Main {
+
+ // Create timestamps for purchase and delivery date
+ Timestamp purchaseDate = Timestamp.newBuilder().build();
+ Timestamp deliveryDate = Timestamp.newBuilder().build();
+
+ // Create a transaction object using the Builder pattern
+ Transaction transaction =
+ Transaction.newBuilder()
+ .setId(1234)
+ .setPrice("$5.67")
+ .setPurchaseDate(purchaseDate)
+ .setDeliveryDate(deliveryDate)
+ .build();
+
+ // Create a Config instance
+ Config config = Config.builder().build();
+ // Create a validator object with your Config instance
+ Validator validator = new Validator(config);
+
+ {
+ // Validate the transaction object using the validator
+ try {
+ ValidationResult result = validator.validate(transaction);
+
+ // Check if there are any validation violations
+ if (result.violations.isEmpty()) {
+ // No violations, validation successful
+ System.out.println("Validation succeeded");
+ } else {
+ // Print the violations if any found
+ System.out.println(result.getMessage());
+ }
+ } catch (ValidationException e) {
+ // Catch and print any ValidationExceptions thrown during the validation process
+ System.out.println("Validation failed: " + e.getMessage());
+ }
+ }
+}
+```
-We genuinely appreciate any help! If you'd like to contribute, check out these resources:
+### Ecosystem
-- [Contributing Guidelines][contributing]: Guidelines to make your contribution process straightforward and meaningful
-- [Conformance testing utilities](https://github.com/bufbuild/protovalidate/tree/main/docs/conformance.md): Utilities providing acceptance testing of `protovalidate` implementations
+- [`protovalidate`](https://github.com/bufbuild/protovalidate) core repository
+- [Buf][buf]
+- [CEL Spec][cel-spec]
## Legal
Offered under the [Apache 2 license][license].
-[buf]: https://buf.build
-[cel]: https://cel.dev
-
-[pv-go]: https://github.com/bufbuild/protovalidate-go
-[pv-java]: https://github.com/bufbuild/protovalidate-java
-[pv-python]: https://github.com/bufbuild/protovalidate-python
-[pv-cc]: https://github.com/bufbuild/protovalidate-cc
-[pv-es]: https://github.com/bufbuild/protovalidate-es
-
[license]: LICENSE
-[contributing]: .github/CONTRIBUTING.md
+[buf]: https://buf.build
[buf-mod]: https://buf.build/bufbuild/protovalidate
-
-[protoc-gen-validate]: https://github.com/bufbuild/protoc-gen-validate
-
-[protovalidate]: https://protovalidate.com
-[quickstart]: https://protovalidate.com/quickstart/
-[connect-go]: https://protovalidate.com/quickstart/connect-go/
-[grpc-go]: https://protovalidate.com/quickstart/grpc-go/
-[grpc-java]: https://protovalidate.com/quickstart/grpc-java/
-[grpc-python]: https://protovalidate.com/quickstart/grpc-python/
-[migration-guide]: https://protovalidate.com/migration-guides/migrate-from-protoc-gen-validate/
-
-[maven]: https://central.sonatype.com/artifact/build.buf/protovalidate/overview
-[pkg-go]: https://pkg.go.dev/github.com/bufbuild/protovalidate-go
-
-[validate-proto]: https://buf.build/bufbuild/protovalidate/docs/main:buf.validate
-[conformance]: https://github.com/bufbuild/protovalidate/blob/main/docs/conformance.md
-[examples]: https://github.com/bufbuild/protovalidate/tree/main/examples
-[migrate]: https://protovalidate.com/migration-guides/migrate-from-protoc-gen-validate/
+[cel-spec]: https://github.com/google/cel-spec
diff --git a/conformance/expected-failures.yaml b/asdf
similarity index 100%
rename from conformance/expected-failures.yaml
rename to asdf
diff --git a/benchmarks/README.md b/benchmarks/README.md
deleted file mode 100644
index fe12957e5..000000000
--- a/benchmarks/README.md
+++ /dev/null
@@ -1,141 +0,0 @@
-# Benchmarks
-
-JMH microbenchmarks for protovalidate-java.
-Used locally to quantify performance changes.
-Not executed in CI; `./gradlew build` only verifies that benchmark code compiles.
-
-## Prerequisites
-
-- JDK 21
-- `buf` CLI (installed automatically by Gradle)
-- `jq` and `column` (preinstalled on macOS)
-
-## Running benchmarks
-
-Run all benchmarks:
-
-```
-./gradlew :benchmarks:jmh
-```
-
-Filter to a subset via `-Pbench` (accepts a regex over method names):
-
-```
-./gradlew :benchmarks:jmh -Pbench=validateSimple # one method
-./gradlew :benchmarks:jmh -Pbench='compile.*' # prefix match
-./gradlew :benchmarks:jmh -Pbench='validate.*' # all steady-state
-```
-
-Results land in `build/results/jmh/results.json`.
-
-## Comparing before and after a change
-
-Typical A/B workflow:
-
-```
-# 1. run baseline on the current tree and save it
-./gradlew :benchmarks:jmh -Pbench='compile.*' :benchmarks:jmhSaveBaseline
-
-# 2. apply your change (edit code, or gh pr checkout )
-
-# 3. re-run and diff against the saved baseline
-./gradlew :benchmarks:jmh -Pbench='compile.*' :benchmarks:jmhCompare
-```
-
-Output:
-
-```
-benchmark metric before after delta
-compileValidatorForRepeated time 4696209.43 ns/op 1064942.21 ns/op -77.3%
-compileValidatorForRepeated alloc 12950196.95 B/op 3262651.61 B/op -74.8%
-```
-
-`jmhSaveBaseline` copies the current `results.json` to `results-before.json`.
-`jmhCompare` diffs `results-before.json` against `results.json` by default.
-Pass explicit paths with `-Pbefore= -Pafter=`.
-
-## Comparing native rules vs CEL
-
-Benchmarks A/B the `enableNativeRules` flag via `@Param({"false", "true"})`, so a single run produces both variants.
-Diff them in place:
-
-```
-./gradlew :benchmarks:jmh
-./gradlew :benchmarks:jmhCompareNativeRules
-```
-
-Output (`before` = CEL, `after` = native; negative delta means native is faster / allocates less):
-
-```
-benchmark metric cel native delta
-buildBenchInt32GT time 1234567.89 ns/op 456789.01 ns/op -63.0%
-buildBenchInt32GT alloc 123456.78 B/op 45678.90 B/op -63.0%
-```
-
-Override the input file with `-Presults=`.
-
-## Adding a new benchmark
-
-Benchmarks live in `src/jmh/java/...` and target proto messages in `src/jmh/proto/...`.
-
-### 1. Define (or reuse) a proto message
-
-Edit `src/jmh/proto/bench/v1/bench.proto` to add a message that exercises the code path you want to measure.
-`buf generate` runs automatically before `compileJmhJava`, so no separate codegen step is needed.
-
-### 2. Add a `@Benchmark` method
-
-Edit `src/jmh/java/build/buf/protovalidate/benchmarks/ValidationBenchmark.java`.
-Put one-time state (validator, messages) in `@Setup` and the measured work in the `@Benchmark` method.
-
-Steady-state (hot-path) pattern:
-
-```java
-@Benchmark
-public void validateMyMessage(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(myMessage));
-}
-```
-
-Cold/compile-path pattern (each iteration builds a fresh validator):
-
-```java
-@Benchmark
-@OutputTimeUnit(TimeUnit.MILLISECONDS)
-public void compileValidatorForMyMessage(Blackhole bh) throws CompilationException {
- Validator v = ValidatorFactory.newBuilder()
- .buildWithDescriptors(Collections.singletonList(MyMessage.getDescriptor()), false);
- bh.consume(v);
-}
-```
-
-Choose based on what the change you want to measure actually touches.
-`EvaluatorBuilder` caches compiled evaluators per descriptor, so after the first `validate()` call, further calls skip compilation.
-If your fix is in the compile path (e.g. `RuleCache`, `DescriptorCacheBuilder`), a steady-state benchmark will not show the effect because `@Setup` absorbs it.
-
-## Configuration
-
-`build.gradle.kts` holds the JMH plugin config.
-Defaults are tuned for fast local iteration (~30s per benchmark):
-
-- 3 warmup iterations of 2s each
-- 5 measurement iterations of 2s each
-- 2 forks
-- Average-time mode, nanoseconds
-- GC profiler on (`gc.alloc.rate.norm` for per-op allocations)
-
-For higher-confidence numbers (tighter confidence intervals, useful for deltas under ~10%), bump `fork`, `warmup`, and `timeOnIteration` in the `jmh {}` block.
-Expect ~5 min per benchmark at `fork=5, warmup=5s, timeOnIteration=5s`.
-
-## Metrics
-
-Each benchmark emits:
-
-- **Primary:** average time per `@Benchmark` invocation (`ns/op` by default).
-- **Secondary (GC profiler):**
- - `gc.alloc.rate.norm` - bytes allocated per op; deterministic, used by `jmhCompare`.
- - `gc.alloc.rate` - allocation rate in MB/sec; varies with CPU.
- - `gc.count` / `gc.time` - GC activity during the run.
-
-For allocation flame graphs, uncomment the `async` profiler line in `build.gradle.kts`.
-Requires `async-profiler` installed locally.
diff --git a/benchmarks/buf.gen.yaml b/benchmarks/buf.gen.yaml
deleted file mode 100644
index 4434781f5..000000000
--- a/benchmarks/buf.gen.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-version: v2
-plugins:
- - remote: buf.build/protocolbuffers/java:$protocJavaPluginVersion
- out: build/generated/sources/bufgen
-inputs:
- - directory: src/jmh/proto
diff --git a/benchmarks/buf.lock b/benchmarks/buf.lock
deleted file mode 100644
index 709ae0239..000000000
--- a/benchmarks/buf.lock
+++ /dev/null
@@ -1,6 +0,0 @@
-# Generated by buf. DO NOT EDIT.
-version: v2
-deps:
- - name: buf.build/bufbuild/protovalidate
- commit: 50325440f8f24053b047484a6bf60b76
- digest: b5:74cb6f5c0853c3c10aafc701614194bbd63326bdb8ef4068214454b8894b03ba4113e04b3a33a8321cdf05336e37db4dc14a5e2495db8462566914f36086ba31
diff --git a/benchmarks/buf.yaml b/benchmarks/buf.yaml
deleted file mode 100644
index 56bbef242..000000000
--- a/benchmarks/buf.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-version: v2
-modules:
- - path: src/jmh/proto
-deps:
- - buf.build/bufbuild/protovalidate
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
deleted file mode 100644
index 8186a0d76..000000000
--- a/benchmarks/build.gradle.kts
+++ /dev/null
@@ -1,168 +0,0 @@
-import com.diffplug.gradle.spotless.SpotlessExtension
-
-plugins {
- java
- alias(libs.plugins.jmh)
- alias(libs.plugins.osdetector)
-}
-
-// JMH can use modern bytecode; benchmarks aren't shipped.
-java {
- sourceCompatibility = JavaVersion.VERSION_21
- targetCompatibility = JavaVersion.VERSION_21
-}
-
-configure {
- java {
- targetExclude("build/generated/**/*.java")
- }
-}
-
-val buf: Configuration by configurations.creating
-
-tasks.register("configureBuf") {
- description = "Installs the Buf CLI."
- File(buf.asPath).setExecutable(true)
-}
-
-tasks.register("filterBufGenYaml") {
- from(files("buf.gen.yaml"))
- includeEmptyDirs = false
- into(layout.buildDirectory.dir("buf-gen-templates"))
- expand("protocJavaPluginVersion" to "v${libs.versions.protobuf.get().substringAfter('.')}")
- filteringCharset = "UTF-8"
-}
-
-tasks.register("generateBenchmarkSources") {
- dependsOn("configureBuf", "filterBufGenYaml")
- description = "Generates Java sources for benchmark protos via buf generate."
- val template = layout.buildDirectory.file("buf-gen-templates/buf.gen.yaml")
- inputs.files(buf)
- inputs.dir("src/jmh/proto")
- inputs.file("buf.yaml")
- inputs.file(template)
- outputs.dir(layout.buildDirectory.dir("generated/sources/bufgen"))
- commandLine(buf.asPath, "generate", "--template", template.get().asFile.absolutePath)
-}
-
-sourceSets {
- named("jmh") {
- java {
- srcDir(layout.buildDirectory.dir("generated/sources/bufgen"))
- }
- }
-}
-
-tasks.matching { it.name == "compileJmhJava" }.configureEach {
- dependsOn("generateBenchmarkSources")
-}
-
-// Ensure `./gradlew build` (and `make build`) compiles the JMH sources so CI
-// catches breakages in benchmark code. Execution remains gated behind the
-// explicit `:benchmarks:jmh` task.
-tasks.named("build") {
- dependsOn("compileJmhJava")
-}
-
-dependencies {
- jmhImplementation(project(":"))
- jmhImplementation(libs.protobuf.java)
- buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe")
-}
-
-// Benchmarks produce fresh timing data each run; disable Gradle's up-to-date
-// check so the task always executes (otherwise -Pbench changes are ignored).
-tasks.named("jmh") {
- outputs.upToDateWhen { false }
-}
-
-jmh {
- // Defaults tuned for fast local A/B runs (~90s total).
- // For higher-confidence numbers bump iteration time and fork count.
- warmupIterations.set(3)
- warmup.set("2s")
- iterations.set(5)
- timeOnIteration.set("2s")
- fork.set(2)
- timeUnit.set("ns")
- benchmarkMode.set(listOf("avgt"))
- resultFormat.set("JSON")
- // GC profiler reports bytes allocated per op (gc.alloc.rate.norm), which
- // jmhCompare can diff alongside timing. ~5-10% overhead on timings.
- profilers.set(listOf("gc"))
- // For allocation flame graphs (requires async-profiler installed locally):
- // profilers.set(listOf("async:event=alloc;output=flamegraph;dir=build/reports/jmh/async"))
-
- // Filter to a subset of benchmarks via `-Pbench=`. Example:
- // ./gradlew :benchmarks:jmh -Pbench=validateSimple
- // ./gradlew :benchmarks:jmh -Pbench='compile.*'
- project.findProperty("bench")?.toString()?.let {
- includes.set(listOf(it))
- }
-}
-
-val jmhResults = layout.buildDirectory.file("results/jmh/results.json")
-val jmhBaseline = layout.buildDirectory.file("results/jmh/results-before.json")
-
-// Saves the latest JMH results.json as the baseline for jmhCompare.
-//
-// Usage:
-// ./gradlew :benchmarks:jmh :benchmarks:jmhSaveBaseline
-// # apply change...
-// ./gradlew :benchmarks:jmh :benchmarks:jmhCompare
-tasks.register("jmhSaveBaseline") {
- description = "Copies the latest JMH results.json to results-before.json as the baseline."
- from(jmhResults)
- into(jmhResults.get().asFile.parentFile)
- rename { "results-before.json" }
- mustRunAfter("jmh")
-}
-
-// Diffs two JMH results.json files as a concise benchstat-style table.
-// Defaults to comparing results-before.json (written by jmhSaveBaseline)
-// against the latest results.json.
-//
-// Override paths:
-// ./gradlew :benchmarks:jmhCompare -Pbefore=a.json -Pafter=b.json
-tasks.register("jmhCompare") {
- description = "Diffs two JMH result JSON files as a concise table."
- val before =
- project.findProperty("before")?.toString()
- ?: jmhBaseline.get().asFile.absolutePath
- val after =
- project.findProperty("after")?.toString()
- ?: jmhResults.get().asFile.absolutePath
- val jqScript = file("jmh-compare.jq").absolutePath
- commandLine(
- "bash",
- "-c",
- "jq --slurp --raw-output --from-file \"\$1\" \"\$2\" \"\$3\" | column -t -s \$'\\t'",
- "jmh-compare", // $0
- jqScript, // $1
- before, // $2
- after, // $3
- )
-}
-
-// Diffs the two enableNativeRules variants within a single results.json.
-// `before` column is CEL (enableNativeRules=false), `after` is native
-// (enableNativeRules=true), so a negative delta means native is faster /
-// allocates less.
-//
-// Override the input file:
-// ./gradlew :benchmarks:jmhCompareNative -Presults=path/to/results.json
-tasks.register("jmhCompareNativeRules") {
- description = "Diffs enableNativeRules=true vs false from a single JMH results.json."
- val results =
- project.findProperty("results")?.toString()
- ?: jmhResults.get().asFile.absolutePath
- val jqScript = file("jmh-compare-native-rules.jq").absolutePath
- commandLine(
- "bash",
- "-c",
- "jq --raw-output --from-file \"\$1\" \"\$2\" | column -t -s \$'\\t'",
- "jmh-compare-native-rules", // $0
- jqScript, // $1
- results, // $2
- )
-}
diff --git a/benchmarks/jmh-compare-native-rules.jq b/benchmarks/jmh-compare-native-rules.jq
deleted file mode 100644
index fcec2004f..000000000
--- a/benchmarks/jmh-compare-native-rules.jq
+++ /dev/null
@@ -1,32 +0,0 @@
-# this script builds a comparison between runs that used the CEL interpreter for
-# protovalidate rule evaluation vs runs that used native Java code for protovalidate
-# rule evaluation. The differentiator is the value of the "native" field (true for
-# native, false for CEL) and this script groups rows that have the same benchmark
-# and metric name, but different values for native.
-def pct(a; b):
- if a == null or b == null or b == 0 then "~"
- else (((a - b) / b * 100) * 10 | round / 10) as $d
- | if $d > 0 then "+\($d)%" elif $d == 0 then "~" else "\($d)%" end
- end;
-def num(x):
- if x == null then "-"
- else (x * 100 | round / 100 | tostring)
- end;
-
-def row: {
- key: (.benchmark | split(".") | last),
- native: .params.enableNativeRules,
- time: .primaryMetric.score,
- unit: .primaryMetric.scoreUnit,
- alloc: (.secondaryMetrics["·gc.alloc.rate.norm"].score // null)
-};
-
-map(row)
-| group_by(.key)
-| (["benchmark", "metric", "cel", "native", "delta"] | @tsv),
- (.[]
- | (map(select(.native == "false"))[0]) as $b
- | (map(select(.native == "true"))[0]) as $a
- | select($b and $a)
- | ([$b.key, "time", "\(num($b.time)) \($b.unit)", "\(num($a.time)) \($a.unit)", pct($a.time; $b.time)] | @tsv),
- ([$b.key, "alloc", "\(num($b.alloc)) B/op", "\(num($a.alloc)) B/op", pct($a.alloc; $b.alloc)] | @tsv))
\ No newline at end of file
diff --git a/benchmarks/jmh-compare.jq b/benchmarks/jmh-compare.jq
deleted file mode 100644
index 4d015a362..000000000
--- a/benchmarks/jmh-compare.jq
+++ /dev/null
@@ -1,24 +0,0 @@
-def pct(a; b):
- if a == null or b == null or b == 0 then "~"
- else (((a - b) / b * 100) * 10 | round / 10) as $d
- | if $d > 0 then "+\($d)%" elif $d == 0 then "~" else "\($d)%" end
- end;
-def num(x):
- if x == null then "-"
- else (x * 100 | round / 100 | tostring)
- end;
-
-def extract: map({
- key: (.benchmark | split(".") | last),
- time: .primaryMetric.score,
- time_unit: .primaryMetric.scoreUnit,
- alloc: (.secondaryMetrics["·gc.alloc.rate.norm"].score // null)
-});
-
-(.[0] | extract) as $b
-| (.[1] | extract) as $a
-| (["benchmark", "metric", "before", "after", "delta"] | @tsv),
- ($b[] | . as $bi
- | ($a[] | select(.key == $bi.key)) as $ai
- | ([$bi.key, "time", "\(num($bi.time)) \($bi.time_unit)", "\(num($ai.time)) \($ai.time_unit)", pct($ai.time; $bi.time)] | @tsv),
- ([$bi.key, "alloc", "\(num($bi.alloc)) B/op", "\(num($ai.alloc)) B/op", pct($ai.alloc; $bi.alloc)] | @tsv))
diff --git a/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/BenchFixtures.java b/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/BenchFixtures.java
deleted file mode 100644
index 95dd914f6..000000000
--- a/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/BenchFixtures.java
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate.benchmarks;
-
-import build.buf.protovalidate.benchmarks.gen.BenchBoolConst;
-import build.buf.protovalidate.benchmarks.gen.BenchBytesConst;
-import build.buf.protovalidate.benchmarks.gen.BenchBytesIn;
-import build.buf.protovalidate.benchmarks.gen.BenchComplexSchema;
-import build.buf.protovalidate.benchmarks.gen.BenchDoubleIn;
-import build.buf.protovalidate.benchmarks.gen.BenchEnum;
-import build.buf.protovalidate.benchmarks.gen.BenchEnumConst;
-import build.buf.protovalidate.benchmarks.gen.BenchEnumNotIn;
-import build.buf.protovalidate.benchmarks.gen.BenchEnumRules;
-import build.buf.protovalidate.benchmarks.gen.BenchGT;
-import build.buf.protovalidate.benchmarks.gen.BenchInt64Const;
-import build.buf.protovalidate.benchmarks.gen.BenchInt64In;
-import build.buf.protovalidate.benchmarks.gen.BenchMap;
-import build.buf.protovalidate.benchmarks.gen.BenchPhaseEnum;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedBytesUnique;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedInt32Unique;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedMessage;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedScalar;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedScalarUnique;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedStringUnique;
-import build.buf.protovalidate.benchmarks.gen.BenchScalar;
-import build.buf.protovalidate.benchmarks.gen.BenchStringConst;
-import build.buf.protovalidate.benchmarks.gen.BenchStringContains;
-import build.buf.protovalidate.benchmarks.gen.BenchStringIn;
-import build.buf.protovalidate.benchmarks.gen.BenchStringLen;
-import build.buf.protovalidate.benchmarks.gen.BenchStringMinLen;
-import build.buf.protovalidate.benchmarks.gen.BenchStringPrefix;
-import build.buf.protovalidate.benchmarks.gen.BenchUint32In;
-import build.buf.protovalidate.benchmarks.gen.MultiRule;
-import build.buf.protovalidate.benchmarks.gen.StringMatching;
-import build.buf.protovalidate.benchmarks.gen.TestByteMatching;
-import build.buf.protovalidate.benchmarks.gen.WrapperTesting;
-import com.google.protobuf.BoolValue;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.BytesValue;
-import com.google.protobuf.DoubleValue;
-import com.google.protobuf.FloatValue;
-import com.google.protobuf.Int32Value;
-import com.google.protobuf.Int64Value;
-import com.google.protobuf.StringValue;
-import com.google.protobuf.UInt32Value;
-import com.google.protobuf.UInt64Value;
-
-/**
- * Hand-built deterministic fixtures for the native-rules benchmark suite.
- *
- *
Each factory returns a fully-populated message that satisfies all of its validation rules.
- * Values are literal (no Random) so benchmarks are reproducible run-to-run. Chosen to match the
- * intent of the gofakeit annotations in the Go reference protos without depending on a faker
- * library.
- */
-final class BenchFixtures {
- private BenchFixtures() {}
-
- static BenchScalar benchScalar() {
- return BenchScalar.newBuilder().setX(42).build();
- }
-
- static BenchRepeatedScalar benchRepeatedScalar() {
- BenchRepeatedScalar.Builder b = BenchRepeatedScalar.newBuilder();
- for (int i = 1; i <= 5; i++) {
- b.addX(i);
- }
- return b.build();
- }
-
- static BenchRepeatedMessage benchRepeatedMessage() {
- BenchRepeatedMessage.Builder b = BenchRepeatedMessage.newBuilder();
- for (int i = 1; i <= 5; i++) {
- b.addX(BenchScalar.newBuilder().setX(i).build());
- }
- return b.build();
- }
-
- static BenchRepeatedScalarUnique benchRepeatedScalarUnique() {
- BenchRepeatedScalarUnique.Builder b = BenchRepeatedScalarUnique.newBuilder();
- for (int i = 1; i <= 8; i++) {
- b.addX((float) i);
- }
- return b.build();
- }
-
- static BenchRepeatedBytesUnique benchRepeatedBytesUnique() {
- BenchRepeatedBytesUnique.Builder b = BenchRepeatedBytesUnique.newBuilder();
- for (int i = 1; i <= 8; i++) {
- b.addX(ByteString.copyFromUtf8("entry-" + i));
- }
- return b.build();
- }
-
- static BenchMap benchMap() {
- BenchMap.Builder b = BenchMap.newBuilder();
- for (int i = 1; i <= 5; i++) {
- b.putEntries("key-" + i, "value-" + i);
- }
- return b.build();
- }
-
- static BenchComplexSchema benchComplexSchema() {
- BenchComplexSchema.Builder b =
- BenchComplexSchema.newBuilder()
- .setS1("hello")
- .setS2("world")
- .setI32(42)
- .setI64(42L)
- .setU32(42)
- .setU64(42L)
- .setSi32(42)
- .setSi64(42L)
- .setF32(42)
- .setF64(42L)
- .setSf32(42)
- .setSf64(42L)
- .setFl(42.0f)
- .setDb(42.0)
- .setBl(true)
- .setBy(ByteString.copyFromUtf8("payload"))
- .setNested(BenchScalar.newBuilder().setX(1).build())
- // self_ref intentionally left null; proto3 message fields default to absent
- .setEnumField(BenchEnum.BENCH_ENUM_ONE)
- .setOneofStr("hello");
-
- for (int i = 1; i <= 3; i++) {
- b.addRepStr("item-" + i);
- b.addRepI32(i);
- b.addRepBytes(ByteString.copyFromUtf8("bytes-" + i));
- b.addRepMsg(BenchScalar.newBuilder().setX(i).build());
- }
-
- for (int i = 1; i <= 3; i++) {
- b.putMapStrStr("k" + i, "v" + i);
- b.putMapI32I64(i, (long) i);
- b.putMapU64Bool((long) i, i % 2 == 0);
- b.putMapStrBytes("k" + i, ByteString.copyFromUtf8("v" + i));
- b.putMapStrMsg("k" + i, BenchScalar.newBuilder().setX(i).build());
- b.putMapI64Msg((long) i, BenchScalar.newBuilder().setX(i).build());
- }
-
- return b.build();
- }
-
- static BenchGT benchGT() {
- // For gt > lt / gte > lte cases, protovalidate interprets the range as
- // exclusive (value not in [lt, gt]). 50 is outside [-20, 0] for all four.
- return BenchGT.newBuilder()
- .setGt(50)
- .setGte(50)
- .setLt(50)
- .setLte(50)
- .setGtltin(50)
- .setGtltein(50)
- .setGtltex(50)
- .setGtlteex(50)
- .setGteltin(50)
- .setGteltein(50)
- .setGteltex(50)
- .setGtelteex(50)
- .setConst(10)
- .setConstgt(10)
- .setInTest(3)
- .setNotInTest(4)
- .build();
- }
-
- static TestByteMatching testByteMatching() {
- return TestByteMatching.newBuilder()
- .setIpAddr(ByteString.copyFrom(new byte[16])) // any 16 bytes (ip rule = 4 or 16)
- .setIpv4Addr(ByteString.copyFrom(new byte[4]))
- .setIpv6Addr(ByteString.copyFrom(new byte[16]))
- .setUuid(ByteString.copyFrom(new byte[16]))
- .build();
- }
-
- static StringMatching stringMatching() {
- return StringMatching.newBuilder()
- .setHostname("example.com")
- .setHostAndPort("example.com:8080")
- .setEmail("alice@example.com")
- .setUuid("550e8400-e29b-41d4-a716-446655440000")
- .build();
- }
-
- static WrapperTesting wrapperTesting() {
- return WrapperTesting.newBuilder()
- .setI32(Int32Value.of(11))
- .setD(DoubleValue.of(11))
- .setF(FloatValue.of(11))
- .setI64(Int64Value.of(11))
- .setU64(UInt64Value.of(11))
- .setU32(UInt32Value.of(11))
- .setB(BoolValue.of(true))
- .setS(StringValue.of("hello"))
- .setBs(BytesValue.of(ByteString.copyFromUtf8("hello")))
- .build();
- }
-
- /** Multi-rule fixture that PASSES — many=10 satisfies const=10 and gt=5. */
- static MultiRule multiRuleNoError() {
- return MultiRule.newBuilder().setMany(10).build();
- }
-
- /** Multi-rule fixture that FAILS both rules — many=1 violates const=10 and gt=5. */
- static MultiRule multiRuleError() {
- return MultiRule.newBuilder().setMany(1).build();
- }
-
- /** Phase 2 measurement target — exercises BoolRulesEvaluator on bool.const. */
- static BenchBoolConst benchBoolConst() {
- return BenchBoolConst.newBuilder().setFlag(true).build();
- }
-
- /** Phase 4 measurement target — exercises EnumRulesEvaluator on enum.in. */
- static BenchEnumRules benchEnumRules() {
- return BenchEnumRules.newBuilder().setVal(BenchPhaseEnum.BENCH_PHASE_ENUM_TWO).build();
- }
-
- // --- Single-rule fixtures for previously unbenchmarked rules ---
- // Each fixture's value satisfies its rule — benchmarks measure the happy path.
-
- static BenchStringConst benchStringConst() {
- return BenchStringConst.newBuilder().setS("hello").build();
- }
-
- static BenchStringLen benchStringLen() {
- return BenchStringLen.newBuilder().setS("hello").build();
- }
-
- static BenchStringMinLen benchStringMinLen() {
- return BenchStringMinLen.newBuilder().setS("x").build();
- }
-
- static BenchStringPrefix benchStringPrefix() {
- return BenchStringPrefix.newBuilder().setS("user-alice").build();
- }
-
- static BenchStringContains benchStringContains() {
- return BenchStringContains.newBuilder().setS("alice@example.com").build();
- }
-
- static BenchStringIn benchStringIn() {
- return BenchStringIn.newBuilder().setS("bar").build();
- }
-
- static BenchBytesConst benchBytesConst() {
- return BenchBytesConst.newBuilder().setB(ByteString.copyFromUtf8("abc")).build();
- }
-
- static BenchBytesIn benchBytesIn() {
- return BenchBytesIn.newBuilder().setB(ByteString.copyFromUtf8("bar")).build();
- }
-
- static BenchInt64Const benchInt64Const() {
- return BenchInt64Const.newBuilder().setV(42L).build();
- }
-
- static BenchInt64In benchInt64In() {
- return BenchInt64In.newBuilder().setV(2L).build();
- }
-
- static BenchUint32In benchUint32In() {
- return BenchUint32In.newBuilder().setV(2).build();
- }
-
- static BenchDoubleIn benchDoubleIn() {
- return BenchDoubleIn.newBuilder().setV(2.0).build();
- }
-
- static BenchEnumConst benchEnumConst() {
- return BenchEnumConst.newBuilder().setVal(BenchPhaseEnum.BENCH_PHASE_ENUM_ONE).build();
- }
-
- static BenchEnumNotIn benchEnumNotIn() {
- return BenchEnumNotIn.newBuilder().setVal(BenchPhaseEnum.BENCH_PHASE_ENUM_ONE).build();
- }
-
- static BenchRepeatedStringUnique benchRepeatedStringUnique() {
- BenchRepeatedStringUnique.Builder b = BenchRepeatedStringUnique.newBuilder();
- for (int i = 1; i <= 8; i++) {
- b.addX("entry-" + i);
- }
- return b.build();
- }
-
- static BenchRepeatedInt32Unique benchRepeatedInt32Unique() {
- BenchRepeatedInt32Unique.Builder b = BenchRepeatedInt32Unique.newBuilder();
- for (int i = 1; i <= 8; i++) {
- b.addX(i);
- }
- return b.build();
- }
-}
diff --git a/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/EvaluatorBuildBenchmark.java b/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/EvaluatorBuildBenchmark.java
deleted file mode 100644
index 8f22a121a..000000000
--- a/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/EvaluatorBuildBenchmark.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate.benchmarks;
-
-import build.buf.protovalidate.Config;
-import build.buf.protovalidate.Validator;
-import build.buf.protovalidate.ValidatorFactory;
-import build.buf.protovalidate.benchmarks.gen.BenchComplexSchema;
-import build.buf.protovalidate.benchmarks.gen.BenchGT;
-import build.buf.protovalidate.exceptions.ValidationException;
-import com.google.protobuf.Message;
-import java.util.concurrent.TimeUnit;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.BenchmarkMode;
-import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.OutputTimeUnit;
-import org.openjdk.jmh.annotations.Param;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.annotations.State;
-import org.openjdk.jmh.infra.Blackhole;
-
-/**
- * Compile-time evaluator construction benchmarks. Mirrors Go's {@code BenchmarkCompile} and {@code
- * BenchmarkCompileInt32GT}. These measure how long it takes to build a validator (compile rules,
- * cache evaluators) for a given message type — the cost paid once per descriptor.
- */
-@BenchmarkMode(Mode.AverageTime)
-@OutputTimeUnit(TimeUnit.NANOSECONDS)
-@State(Scope.Benchmark)
-public class EvaluatorBuildBenchmark {
-
- @Param({"false", "true"})
- public boolean enableNativeRules;
-
- private Config config;
- private Message benchComplexSchema;
- private Message benchGT;
-
- @Setup
- public void setup() {
- config = Config.newBuilder().setEnableNativeRules(enableNativeRules).build();
- benchComplexSchema = BenchComplexSchema.getDefaultInstance();
- benchGT = BenchGT.getDefaultInstance();
- }
-
- @Benchmark
- public Validator buildBenchComplexSchema(Blackhole bh) throws ValidationException {
- Validator v = ValidatorFactory.newBuilder().withConfig(config).build();
- // Force evaluator construction by validating the default instance.
- bh.consume(v.validate(benchComplexSchema));
- return v;
- }
-
- @Benchmark
- public Validator buildBenchInt32GT(Blackhole bh) throws ValidationException {
- Validator v = ValidatorFactory.newBuilder().withConfig(config).build();
- bh.consume(v.validate(benchGT));
- return v;
- }
-}
diff --git a/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/ValidationBenchmark.java b/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/ValidationBenchmark.java
deleted file mode 100644
index 93d34bc9b..000000000
--- a/benchmarks/src/jmh/java/build/buf/protovalidate/benchmarks/ValidationBenchmark.java
+++ /dev/null
@@ -1,411 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate.benchmarks;
-
-import build.buf.protovalidate.Config;
-import build.buf.protovalidate.Validator;
-import build.buf.protovalidate.ValidatorFactory;
-import build.buf.protovalidate.benchmarks.gen.BenchBoolConst;
-import build.buf.protovalidate.benchmarks.gen.BenchBytesConst;
-import build.buf.protovalidate.benchmarks.gen.BenchBytesIn;
-import build.buf.protovalidate.benchmarks.gen.BenchComplexSchema;
-import build.buf.protovalidate.benchmarks.gen.BenchDoubleIn;
-import build.buf.protovalidate.benchmarks.gen.BenchEnumConst;
-import build.buf.protovalidate.benchmarks.gen.BenchEnumNotIn;
-import build.buf.protovalidate.benchmarks.gen.BenchEnumRules;
-import build.buf.protovalidate.benchmarks.gen.BenchGT;
-import build.buf.protovalidate.benchmarks.gen.BenchInt64Const;
-import build.buf.protovalidate.benchmarks.gen.BenchInt64In;
-import build.buf.protovalidate.benchmarks.gen.BenchMap;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedBytesUnique;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedInt32Unique;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedMessage;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedScalar;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedScalarUnique;
-import build.buf.protovalidate.benchmarks.gen.BenchRepeatedStringUnique;
-import build.buf.protovalidate.benchmarks.gen.BenchScalar;
-import build.buf.protovalidate.benchmarks.gen.BenchStringConst;
-import build.buf.protovalidate.benchmarks.gen.BenchStringContains;
-import build.buf.protovalidate.benchmarks.gen.BenchStringIn;
-import build.buf.protovalidate.benchmarks.gen.BenchStringLen;
-import build.buf.protovalidate.benchmarks.gen.BenchStringMinLen;
-import build.buf.protovalidate.benchmarks.gen.BenchStringPrefix;
-import build.buf.protovalidate.benchmarks.gen.BenchUint32In;
-import build.buf.protovalidate.benchmarks.gen.ManyUnruledFieldsMessage;
-import build.buf.protovalidate.benchmarks.gen.MultiRule;
-import build.buf.protovalidate.benchmarks.gen.RegexPatternMessage;
-import build.buf.protovalidate.benchmarks.gen.RepeatedRuleMessage;
-import build.buf.protovalidate.benchmarks.gen.SimpleStringMessage;
-import build.buf.protovalidate.benchmarks.gen.StringMatching;
-import build.buf.protovalidate.benchmarks.gen.TestByteMatching;
-import build.buf.protovalidate.benchmarks.gen.WrapperTesting;
-import build.buf.protovalidate.exceptions.ValidationException;
-import com.google.protobuf.Descriptors.FieldDescriptor;
-import java.util.concurrent.TimeUnit;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.BenchmarkMode;
-import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.OutputTimeUnit;
-import org.openjdk.jmh.annotations.Param;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.annotations.State;
-import org.openjdk.jmh.infra.Blackhole;
-
-/**
- * Steady-state validation benchmarks. Exercises the hot path after the evaluator cache is warm.
- *
- *
The set of {@code validateBench*} methods mirrors the Go benchmark suite in protovalidate-go's
- * {@code validator_bench_test.go} and provides the baseline against which the native-rules port
- * measures its improvements. The original {@code validate*} methods exercise past PR fixes
- * (tautology skip, AST cache, etc.) and remain as regression guards.
- *
- *
The {@code enableNativeRules} parameter A/Bs the native-rules flag: {@code "false"} matches
- * the Phase 0 CEL-only baseline; {@code "true"} measures native evaluation. Each subsequent phase
- * reports the gap between the two modes for its covered benchmarks.
- */
-@BenchmarkMode(Mode.AverageTime)
-@OutputTimeUnit(TimeUnit.NANOSECONDS)
-@State(Scope.Benchmark)
-public class ValidationBenchmark {
-
- @Param({"false", "true"})
- public boolean enableNativeRules;
-
- private Validator validator;
-
- // --- Existing regression-guard fixtures ---
- private SimpleStringMessage simple;
- private ManyUnruledFieldsMessage manyUnruled;
- private RepeatedRuleMessage repeatedRule;
- private RegexPatternMessage regexPattern;
-
- // --- Native-rules port fixtures ---
- private BenchScalar benchScalar;
- private BenchRepeatedScalar benchRepeatedScalar;
- private BenchRepeatedMessage benchRepeatedMessage;
- private BenchRepeatedScalarUnique benchRepeatedScalarUnique;
- private BenchRepeatedBytesUnique benchRepeatedBytesUnique;
- private BenchMap benchMap;
- private BenchComplexSchema benchComplexSchema;
- private BenchGT benchGT;
- private TestByteMatching testByteMatching;
- private StringMatching stringMatching;
- private WrapperTesting wrapperTesting;
- private MultiRule multiRuleNoError;
- private MultiRule multiRuleError;
- private BenchBoolConst benchBoolConst;
- private BenchEnumRules benchEnumRules;
-
- // Single-rule fixtures filling earlier coverage gaps.
- private BenchStringConst benchStringConst;
- private BenchStringLen benchStringLen;
- private BenchStringMinLen benchStringMinLen;
- private BenchStringPrefix benchStringPrefix;
- private BenchStringContains benchStringContains;
- private BenchStringIn benchStringIn;
- private BenchBytesConst benchBytesConst;
- private BenchBytesIn benchBytesIn;
- private BenchInt64Const benchInt64Const;
- private BenchInt64In benchInt64In;
- private BenchUint32In benchUint32In;
- private BenchDoubleIn benchDoubleIn;
- private BenchEnumConst benchEnumConst;
- private BenchEnumNotIn benchEnumNotIn;
- private BenchRepeatedStringUnique benchRepeatedStringUnique;
- private BenchRepeatedInt32Unique benchRepeatedInt32Unique;
-
- @Setup
- public void setup() throws ValidationException {
- Config config = Config.newBuilder().setEnableNativeRules(enableNativeRules).build();
-
- validator = ValidatorFactory.newBuilder().withConfig(config).build();
-
- simple = SimpleStringMessage.newBuilder().setEmail("alice@example.com").build();
-
- manyUnruled =
- ManyUnruledFieldsMessage.newBuilder()
- .setNonEmpty("x")
- .setF1("v1")
- .setF2("v2")
- .setF3("v3")
- .setF4("v4")
- .setF5("v5")
- .setF6("v6")
- .setF7("v7")
- .setF8("v8")
- .setF9("v9")
- .build();
-
- RepeatedRuleMessage.Builder repeatedRuleBuilder = RepeatedRuleMessage.newBuilder();
- for (FieldDescriptor fd : RepeatedRuleMessage.getDescriptor().getFields()) {
- repeatedRuleBuilder.setField(fd, "v");
- }
- repeatedRule = repeatedRuleBuilder.build();
-
- regexPattern = RegexPatternMessage.newBuilder().setName("Alice Example").build();
-
- benchScalar = BenchFixtures.benchScalar();
- benchRepeatedScalar = BenchFixtures.benchRepeatedScalar();
- benchRepeatedMessage = BenchFixtures.benchRepeatedMessage();
- benchRepeatedScalarUnique = BenchFixtures.benchRepeatedScalarUnique();
- benchRepeatedBytesUnique = BenchFixtures.benchRepeatedBytesUnique();
- benchMap = BenchFixtures.benchMap();
- benchComplexSchema = BenchFixtures.benchComplexSchema();
- benchGT = BenchFixtures.benchGT();
- testByteMatching = BenchFixtures.testByteMatching();
- stringMatching = BenchFixtures.stringMatching();
- wrapperTesting = BenchFixtures.wrapperTesting();
- multiRuleNoError = BenchFixtures.multiRuleNoError();
- multiRuleError = BenchFixtures.multiRuleError();
- benchBoolConst = BenchFixtures.benchBoolConst();
- benchEnumRules = BenchFixtures.benchEnumRules();
-
- benchStringConst = BenchFixtures.benchStringConst();
- benchStringLen = BenchFixtures.benchStringLen();
- benchStringMinLen = BenchFixtures.benchStringMinLen();
- benchStringPrefix = BenchFixtures.benchStringPrefix();
- benchStringContains = BenchFixtures.benchStringContains();
- benchStringIn = BenchFixtures.benchStringIn();
- benchBytesConst = BenchFixtures.benchBytesConst();
- benchBytesIn = BenchFixtures.benchBytesIn();
- benchInt64Const = BenchFixtures.benchInt64Const();
- benchInt64In = BenchFixtures.benchInt64In();
- benchUint32In = BenchFixtures.benchUint32In();
- benchDoubleIn = BenchFixtures.benchDoubleIn();
- benchEnumConst = BenchFixtures.benchEnumConst();
- benchEnumNotIn = BenchFixtures.benchEnumNotIn();
- benchRepeatedStringUnique = BenchFixtures.benchRepeatedStringUnique();
- benchRepeatedInt32Unique = BenchFixtures.benchRepeatedInt32Unique();
-
- // Warm evaluator cache for steady-state benchmarks.
- validator.validate(simple);
- validator.validate(manyUnruled);
- validator.validate(repeatedRule);
- validator.validate(regexPattern);
- validator.validate(benchScalar);
- validator.validate(benchRepeatedScalar);
- validator.validate(benchRepeatedMessage);
- validator.validate(benchRepeatedScalarUnique);
- validator.validate(benchRepeatedBytesUnique);
- validator.validate(benchMap);
- validator.validate(benchComplexSchema);
- validator.validate(benchGT);
- validator.validate(testByteMatching);
- validator.validate(stringMatching);
- validator.validate(wrapperTesting);
- validator.validate(multiRuleNoError);
- validator.validate(multiRuleError);
- validator.validate(benchBoolConst);
- validator.validate(benchEnumRules);
- validator.validate(benchStringConst);
- validator.validate(benchStringLen);
- validator.validate(benchStringMinLen);
- validator.validate(benchStringPrefix);
- validator.validate(benchStringContains);
- validator.validate(benchStringIn);
- validator.validate(benchBytesConst);
- validator.validate(benchBytesIn);
- validator.validate(benchInt64Const);
- validator.validate(benchInt64In);
- validator.validate(benchUint32In);
- validator.validate(benchDoubleIn);
- validator.validate(benchEnumConst);
- validator.validate(benchEnumNotIn);
- validator.validate(benchRepeatedStringUnique);
- validator.validate(benchRepeatedInt32Unique);
- }
-
- // --- Existing regression-guard benchmarks ---
-
- @Benchmark
- public void validateSimple(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(simple));
- }
-
- @Benchmark
- public void validateManyUnruled(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(manyUnruled));
- }
-
- @Benchmark
- public void validateRepeatedRule(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(repeatedRule));
- }
-
- @Benchmark
- public void validateRegexPattern(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(regexPattern));
- }
-
- // --- Native-rules port benchmarks (mirror Go BenchmarkXxx names) ---
-
- @Benchmark
- public void validateBenchScalar(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchScalar));
- }
-
- @Benchmark
- public void validateBenchRepeatedScalar(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchRepeatedScalar));
- }
-
- @Benchmark
- public void validateBenchRepeatedMessage(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchRepeatedMessage));
- }
-
- @Benchmark
- public void validateBenchRepeatedScalarUnique(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchRepeatedScalarUnique));
- }
-
- @Benchmark
- public void validateBenchRepeatedBytesUnique(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchRepeatedBytesUnique));
- }
-
- @Benchmark
- public void validateBenchMap(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchMap));
- }
-
- @Benchmark
- public void validateBenchComplexSchema(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchComplexSchema));
- }
-
- @Benchmark
- public void validateBenchInt32GT(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchGT));
- }
-
- @Benchmark
- public void validateTestByteMatching(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(testByteMatching));
- }
-
- @Benchmark
- public void validateStringMatching(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(stringMatching));
- }
-
- @Benchmark
- public void validateWrapperTesting(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(wrapperTesting));
- }
-
- @Benchmark
- public void validateMultiRuleNoError(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(multiRuleNoError));
- }
-
- @Benchmark
- public void validateMultiRuleError(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(multiRuleError));
- }
-
- @Benchmark
- public void validateBenchBoolConst(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchBoolConst));
- }
-
- @Benchmark
- public void validateBenchEnumRules(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchEnumRules));
- }
-
- // --- Single-rule fixtures filling earlier coverage gaps ---
-
- @Benchmark
- public void validateBenchStringConst(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchStringConst));
- }
-
- @Benchmark
- public void validateBenchStringLen(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchStringLen));
- }
-
- @Benchmark
- public void validateBenchStringMinLen(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchStringMinLen));
- }
-
- @Benchmark
- public void validateBenchStringPrefix(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchStringPrefix));
- }
-
- @Benchmark
- public void validateBenchStringContains(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchStringContains));
- }
-
- @Benchmark
- public void validateBenchStringIn(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchStringIn));
- }
-
- @Benchmark
- public void validateBenchBytesConst(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchBytesConst));
- }
-
- @Benchmark
- public void validateBenchBytesIn(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchBytesIn));
- }
-
- @Benchmark
- public void validateBenchInt64Const(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchInt64Const));
- }
-
- @Benchmark
- public void validateBenchInt64In(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchInt64In));
- }
-
- @Benchmark
- public void validateBenchUint32In(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchUint32In));
- }
-
- @Benchmark
- public void validateBenchDoubleIn(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchDoubleIn));
- }
-
- @Benchmark
- public void validateBenchEnumConst(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchEnumConst));
- }
-
- @Benchmark
- public void validateBenchEnumNotIn(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchEnumNotIn));
- }
-
- @Benchmark
- public void validateBenchRepeatedStringUnique(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchRepeatedStringUnique));
- }
-
- @Benchmark
- public void validateBenchRepeatedInt32Unique(Blackhole bh) throws ValidationException {
- bh.consume(validator.validate(benchRepeatedInt32Unique));
- }
-}
diff --git a/benchmarks/src/jmh/proto/bench/v1/bench.proto b/benchmarks/src/jmh/proto/bench/v1/bench.proto
deleted file mode 100644
index d6fde070e..000000000
--- a/benchmarks/src/jmh/proto/bench/v1/bench.proto
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2023-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.
-
-syntax = "proto3";
-
-package bench.v1;
-
-option java_multiple_files = true;
-option java_package = "build.buf.protovalidate.benchmarks.gen";
-
-import "buf/validate/validate.proto";
-
-// Simple single-field message. Baseline for validate() overhead on a small
-// message with one cheap rule.
-message SimpleStringMessage {
- string email = 1 [(buf.validate.field).string.email = true];
-}
-
-// One ruled field and many fields without rules. Targets the tautology skip
-// in PR #454: before the fix, no-rule fields still ran a FieldEvaluator on
-// every validate() call.
-message ManyUnruledFieldsMessage {
- string non_empty = 1 [(buf.validate.field).string.min_len = 1];
- string f1 = 2;
- string f2 = 3;
- string f3 = 4;
- string f4 = 5;
- string f5 = 6;
- string f6 = 7;
- string f7 = 8;
- string f8 = 9;
- string f9 = 10;
-}
-
-// Twenty fields that share the same rule shape (min_len on string). Targets
-// the AST cache fix in PR #451. The cache key lookup mismatch made
-// compileRule rebuild the AST for every field during evaluator construction.
-// With many same-shape fields, the saved work is N-1 AST builds.
-message RepeatedRuleMessage {
- string f01 = 1 [(buf.validate.field).string.min_len = 1];
- string f02 = 2 [(buf.validate.field).string.min_len = 1];
- string f03 = 3 [(buf.validate.field).string.min_len = 1];
- string f04 = 4 [(buf.validate.field).string.min_len = 1];
- string f05 = 5 [(buf.validate.field).string.min_len = 1];
- string f06 = 6 [(buf.validate.field).string.min_len = 1];
- string f07 = 7 [(buf.validate.field).string.min_len = 1];
- string f08 = 8 [(buf.validate.field).string.min_len = 1];
- string f09 = 9 [(buf.validate.field).string.min_len = 1];
- string f10 = 10 [(buf.validate.field).string.min_len = 1];
- string f11 = 11 [(buf.validate.field).string.min_len = 1];
- string f12 = 12 [(buf.validate.field).string.min_len = 1];
- string f13 = 13 [(buf.validate.field).string.min_len = 1];
- string f14 = 14 [(buf.validate.field).string.min_len = 1];
- string f15 = 15 [(buf.validate.field).string.min_len = 1];
- string f16 = 16 [(buf.validate.field).string.min_len = 1];
- string f17 = 17 [(buf.validate.field).string.min_len = 1];
- string f18 = 18 [(buf.validate.field).string.min_len = 1];
- string f19 = 19 [(buf.validate.field).string.min_len = 1];
- string f20 = 20 [(buf.validate.field).string.min_len = 1];
-}
-
-// Single string field with a string.pattern rule. Targets the regex
-// recompile-per-evaluation cost: the CEL runtime's matches() calls
-// Pattern.compile on every invocation.
-message RegexPatternMessage {
- string name = 1 [(buf.validate.field).string = {
- pattern: "^[[:alpha:]]+( [[:alpha:]]+)*$"
- max_bytes: 256
- }];
-}
diff --git a/benchmarks/src/jmh/proto/bench/v1/native_bench.proto b/benchmarks/src/jmh/proto/bench/v1/native_bench.proto
deleted file mode 100644
index 57ec69e43..000000000
--- a/benchmarks/src/jmh/proto/bench/v1/native_bench.proto
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2023-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.
-
-// Messages ported from protovalidate-go's proto/tests/example/v1/bench.proto
-// and proto/tests/example/v1/native_test.proto for the native-rules port.
-// gofakeit annotations have been stripped; benchmark fixtures are hand-built
-// in BenchFixtures.java.
-
-syntax = "proto3";
-
-package bench.v1;
-
-import "buf/validate/validate.proto";
-import "google/protobuf/wrappers.proto";
-
-option java_multiple_files = true;
-option java_package = "build.buf.protovalidate.benchmarks.gen";
-
-// --- Messages ported from bench.proto ---
-
-message BenchScalar {
- int32 x = 1 [(buf.validate.field).int32.gt = 0];
-}
-
-message BenchRepeatedScalar {
- repeated int32 x = 1 [(buf.validate.field).repeated.max_items = 10];
-}
-
-message BenchRepeatedMessage {
- repeated BenchScalar x = 1 [(buf.validate.field).repeated.max_items = 10];
-}
-
-message BenchRepeatedScalarUnique {
- repeated float x = 1 [(buf.validate.field).repeated.unique = true];
-}
-
-message BenchRepeatedBytesUnique {
- repeated bytes x = 1 [(buf.validate.field).repeated.unique = true];
-}
-
-message BenchMap {
- map entries = 1 [(buf.validate.field).map.min_pairs = 1];
-}
-
-message BenchComplexSchema {
- string s1 = 1 [(buf.validate.field).string.min_len = 1];
- string s2 = 2 [(buf.validate.field).string.max_len = 100];
- int32 i32 = 3 [(buf.validate.field).int32.gt = 0];
- int64 i64 = 4 [(buf.validate.field).int64.lt = 1000];
- uint32 u32 = 5 [(buf.validate.field).uint32.gte = 1];
- uint64 u64 = 6 [(buf.validate.field).uint64.lte = 1000];
- sint32 si32 = 7 [(buf.validate.field).sint32.gt = 0];
- sint64 si64 = 8 [(buf.validate.field).sint64.lt = 1000];
- fixed32 f32 = 9 [(buf.validate.field).fixed32.gte = 1];
- fixed64 f64 = 10 [(buf.validate.field).fixed64.lte = 1000];
- sfixed32 sf32 = 11 [(buf.validate.field).sfixed32.gt = 0];
- sfixed64 sf64 = 12 [(buf.validate.field).sfixed64.lt = 1000];
- float fl = 13 [(buf.validate.field).float.finite = true];
- double db = 14 [(buf.validate.field).double.finite = true];
- bool bl = 15;
- bytes by = 16 [(buf.validate.field).bytes.min_len = 1];
-
- BenchScalar nested = 17;
-
- BenchComplexSchema self_ref = 18;
-
- repeated string rep_str = 19 [(buf.validate.field).repeated.max_items = 10];
- repeated int32 rep_i32 = 20 [(buf.validate.field).repeated.min_items = 1];
- repeated bytes rep_bytes = 21 [(buf.validate.field).repeated.unique = true];
-
- repeated BenchScalar rep_msg = 22 [(buf.validate.field).repeated.max_items = 5];
-
- map map_str_str = 23 [(buf.validate.field).map.min_pairs = 1];
- map map_i32_i64 = 24 [(buf.validate.field).map.max_pairs = 10];
- map map_u64_bool = 25;
- map map_str_bytes = 26 [(buf.validate.field).map.keys = {
- string: {min_len: 1}
- }];
-
- map map_str_msg = 27 [(buf.validate.field).map.values = {required: true}];
- map map_i64_msg = 28;
-
- BenchEnum enum_field = 29 [(buf.validate.field).enum.defined_only = true];
-
- oneof choice {
- string oneof_str = 30 [(buf.validate.field).string.min_len = 1];
- int32 oneof_i32 = 31 [(buf.validate.field).int32.gt = 0];
- BenchScalar oneof_msg = 32;
- }
-}
-
-enum BenchEnum {
- BENCH_ENUM_UNSPECIFIED = 0;
- BENCH_ENUM_ONE = 1;
- BENCH_ENUM_TWO = 2;
-}
-
-// --- Messages ported from native_test.proto ---
-
-message BenchGT {
- int32 gt = 1 [(buf.validate.field).int32.gt = 0];
- int32 gte = 2 [(buf.validate.field).int32.gte = 0];
- int32 lt = 3 [(buf.validate.field).int32.lt = 101];
- int32 lte = 4 [(buf.validate.field).int32.lte = 101];
- int32 gtltin = 5 [
- (buf.validate.field).int32.gt = 0,
- (buf.validate.field).int32.lt = 101
- ];
- int32 gtltein = 6 [
- (buf.validate.field).int32.gt = 0,
- (buf.validate.field).int32.lt = 101
- ];
- int32 gtltex = 7 [
- (buf.validate.field).int32.gt = 0,
- (buf.validate.field).int32.lt = -20
- ];
- int32 gtlteex = 8 [
- (buf.validate.field).int32.gt = 0,
- (buf.validate.field).int32.lte = -20
- ];
- int32 gteltin = 9 [
- (buf.validate.field).int32.gte = 0,
- (buf.validate.field).int32.lt = 101
- ];
- int32 gteltein = 10 [
- (buf.validate.field).int32.gte = 0,
- (buf.validate.field).int32.lt = 101
- ];
- int32 gteltex = 11 [
- (buf.validate.field).int32.gte = 0,
- (buf.validate.field).int32.lt = -20
- ];
- int32 gtelteex = 12 [
- (buf.validate.field).int32.gte = 0,
- (buf.validate.field).int32.lte = -20
- ];
- int32 const = 13 [(buf.validate.field).int32.const = 10];
- int32 constgt = 14 [
- (buf.validate.field).int32.const = 10,
- (buf.validate.field).int32.gte = 0
- ];
- int32 in_test = 15 [(buf.validate.field).int32 = {
- in: [
- 1,
- 3,
- 5
- ]
- }];
- int32 not_in_test = 16 [(buf.validate.field).int32 = {
- not_in: [
- 1,
- 3,
- 5
- ]
- }];
-}
-
-message TestByteMatching {
- bytes ip_addr = 1 [(buf.validate.field).bytes.ip = true];
- bytes ipv4_addr = 2 [(buf.validate.field).bytes.ipv4 = true];
- bytes ipv6_addr = 3 [(buf.validate.field).bytes.ipv6 = true];
- bytes uuid = 4 [(buf.validate.field).bytes.uuid = true];
-}
-
-message StringMatching {
- string hostname = 1 [(buf.validate.field).string.hostname = true];
- string host_and_port = 2 [(buf.validate.field).string.host_and_port = true];
- string email = 3 [(buf.validate.field).string.email = true];
- string uuid = 4 [(buf.validate.field).string.uuid = true];
-}
-
-message WrapperTesting {
- google.protobuf.Int32Value i32 = 1 [(buf.validate.field).int32.gt = 10];
- google.protobuf.DoubleValue d = 2 [(buf.validate.field).double.gt = 10];
- google.protobuf.FloatValue f = 3 [(buf.validate.field).float.gt = 10];
- google.protobuf.Int64Value i64 = 4 [(buf.validate.field).int64.gt = 10];
- google.protobuf.UInt64Value u64 = 5 [(buf.validate.field).uint64.gt = 10];
- google.protobuf.UInt32Value u32 = 6 [(buf.validate.field).uint32.gt = 10];
- google.protobuf.BoolValue b = 7 [(buf.validate.field).bool.const = true];
- google.protobuf.StringValue s = 8 [(buf.validate.field).string.const = "hello"];
- google.protobuf.BytesValue bs = 9 [(buf.validate.field).bytes.len = 5];
-}
-
-message MultiRule {
- int64 many = 1 [
- (buf.validate.field).int64.const = 10,
- (buf.validate.field).int64.gt = 5
- ];
-}
-
-// Phase 2 measurement target. Single bool field with a const rule so the bench has a direct
-// hit on BoolRulesEvaluator.
-message BenchBoolConst {
- bool flag = 1 [(buf.validate.field).bool.const = true];
-}
-
-enum BenchPhaseEnum {
- BENCH_PHASE_ENUM_UNSPECIFIED = 0;
- BENCH_PHASE_ENUM_ONE = 1;
- BENCH_PHASE_ENUM_TWO = 2;
- BENCH_PHASE_ENUM_THREE = 3;
-}
-
-// Phase 4 measurement target. Single enum field with const + in rules so the bench has a direct
-// hit on EnumRulesEvaluator.
-message BenchEnumRules {
- BenchPhaseEnum val = 1 [(buf.validate.field).enum = {
- in: [
- 1,
- 2,
- 3
- ]
- }];
-}
-
-// --- Single-rule fixtures for previously unbenchmarked rules ---
-//
-// These messages each isolate a single rule so the benchmark measures that rule's per-validation
-// cost. They cover gaps identified in the second-pass review: string scalar rules (const, len,
-// min_len, prefix, contains, in), bytes scalar rules (const, in), numeric in/const for non-int32
-// kinds, enum const/not_in, and repeated.unique on string/int32.
-
-message BenchStringConst {
- string s = 1 [(buf.validate.field).string.const = "hello"];
-}
-
-message BenchStringLen {
- string s = 1 [(buf.validate.field).string.len = 5];
-}
-
-message BenchStringMinLen {
- string s = 1 [(buf.validate.field).string.min_len = 1];
-}
-
-message BenchStringPrefix {
- string s = 1 [(buf.validate.field).string.prefix = "user-"];
-}
-
-message BenchStringContains {
- string s = 1 [(buf.validate.field).string.contains = "@"];
-}
-
-message BenchStringIn {
- string s = 1 [(buf.validate.field).string = {
- in: [
- "foo",
- "bar",
- "baz"
- ]
- }];
-}
-
-message BenchBytesConst {
- bytes b = 1 [(buf.validate.field).bytes.const = "abc"];
-}
-
-message BenchBytesIn {
- bytes b = 1 [(buf.validate.field).bytes = {
- in: [
- "foo",
- "bar"
- ]
- }];
-}
-
-message BenchInt64Const {
- int64 v = 1 [(buf.validate.field).int64.const = 42];
-}
-
-message BenchInt64In {
- int64 v = 1 [(buf.validate.field).int64 = {
- in: [
- 1,
- 2,
- 3
- ]
- }];
-}
-
-message BenchUint32In {
- uint32 v = 1 [(buf.validate.field).uint32 = {
- in: [
- 1,
- 2,
- 3
- ]
- }];
-}
-
-message BenchDoubleIn {
- double v = 1 [(buf.validate.field).double = {
- in: [
- 1.0,
- 2.0,
- 3.0
- ]
- }];
-}
-
-message BenchEnumConst {
- BenchPhaseEnum val = 1 [(buf.validate.field).enum.const = 1];
-}
-
-message BenchEnumNotIn {
- BenchPhaseEnum val = 1 [(buf.validate.field).enum = {
- not_in: [
- 2,
- 3
- ]
- }];
-}
-
-message BenchRepeatedStringUnique {
- repeated string x = 1 [(buf.validate.field).repeated.unique = true];
-}
-
-message BenchRepeatedInt32Unique {
- repeated int32 x = 1 [(buf.validate.field).repeated.unique = true];
-}
\ No newline at end of file
diff --git a/buf.gen.yaml b/buf.gen.yaml
deleted file mode 100644
index 3cb08a7c7..000000000
--- a/buf.gen.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-version: v2
-plugins:
- - remote: buf.build/protocolbuffers/java:$protocJavaPluginVersion
- out: build/generated/sources/bufgen
diff --git a/buf.yaml b/buf.yaml
deleted file mode 100644
index 3fa55acfe..000000000
--- a/buf.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-version: v2
-modules:
- - path: src/main/resources
- - path: src/test/resources/proto
-lint:
- use:
- - DEFAULT
-breaking:
- use:
- - FILE
diff --git a/build.gradle.kts b/build.gradle.kts
index 4255d3a7b..de7e6f79e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,15 +1,13 @@
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+import com.vanniktech.maven.publish.SonatypeHost
import com.diffplug.gradle.spotless.SpotlessExtension
-import com.vanniktech.maven.publish.JavaLibrary
-import com.vanniktech.maven.publish.JavadocJar
-import com.vanniktech.maven.publish.SourcesJar
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
plugins {
+ `version-catalog`
`java-library`
- alias(libs.plugins.errorprone)
- alias(libs.plugins.maven)
- alias(libs.plugins.osdetector)
+ alias(libs.plugins.errorprone.plugin)
}
java {
@@ -17,264 +15,10 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}
-// The releaseVersion property is set on official releases in the release.yml workflow.
-// If not specified, we attempt to calculate a snapshot version based on the last tagged release.
-// So if the local build's last tag was v0.1.9, this will set snapshotVersion to 0.1.10-SNAPSHOT.
-// If this fails for any reason, we'll fall back to using 0.0.0-SNAPSHOT version.
-var snapshotVersion = "0.0.0-SNAPSHOT"
-val lastTag =
- try {
- providers
- .exec {
- commandLine("git", "describe", "--tags", "--abbrev=0")
- isIgnoreExitValue = true
- }.standardOutput.asText
- .get()
- .trim()
- } catch (e: Exception) {
- ""
- }
-val matchResult = """^v(\d+)\.(\d+)\.(\d+)$""".toRegex().matchEntire(lastTag)
-if (matchResult != null) {
- val (major, minor, patch) = matchResult.destructured
- snapshotVersion = "$major.$minor.${patch.toInt() + 1}-SNAPSHOT"
-}
-val releaseVersion = project.findProperty("releaseVersion") as String? ?: snapshotVersion
-
-val buf: Configuration by configurations.creating
-val bufLicenseHeaderCLIFile =
- project.layout.buildDirectory
- .file("gobin/license-header")
- .get()
- .asFile
-val bufLicenseHeaderCLIPath: String = bufLicenseHeaderCLIFile.absolutePath
-
-tasks.register("configureBuf") {
- description = "Installs the Buf CLI."
- File(buf.asPath).setExecutable(true)
-}
-
-tasks.register("installLicenseHeader") {
- description = "Installs the Buf license-header CLI."
- environment("GOBIN", bufLicenseHeaderCLIFile.parentFile.absolutePath)
- inputs.property("bufVersion", libs.versions.buf.get())
- outputs.file(bufLicenseHeaderCLIFile)
- commandLine("go", "install", "github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v${libs.versions.buf.get()}")
-}
-
-tasks.register("licenseHeader") {
- dependsOn("installLicenseHeader")
- description = "Runs the Buf license-header CLI."
- commandLine(
- bufLicenseHeaderCLIPath,
- "--license-type",
- "apache",
- "--copyright-holder",
- "Buf Technologies, Inc.",
- "--year-range",
- project.findProperty("license-header.years")!!.toString(),
- "--ignore",
- "build/generated/sources/bufgen/",
- "--ignore",
- "conformance/build/generated/sources/bufgen/",
- "--ignore",
- "src/main/resources/buf/validate/",
- "--ignore",
- ".github",
- "--ignore",
- "src/test/resources/proto/buf.gen.*.yaml",
- "--ignore",
- "benchmarks/buf.gen.yaml",
- "--ignore",
- "benchmarks/buf.yaml",
- "--ignore",
- "conformance/buf.gen.yaml",
- "--ignore",
- "conformance/expected-failures.yaml",
- "--ignore",
- "buf.gen.yaml",
- "--ignore",
- "buf.yaml",
- )
-}
-
-tasks.register("filterBufGenYaml") {
- from(files("buf.gen.yaml"))
- from("src/test/resources/proto") {
- include("buf*gen*.yaml")
- into("src/test/resources/proto")
- }
- includeEmptyDirs = false
- into(layout.buildDirectory.dir("buf-gen-templates"))
- expand("protocJavaPluginVersion" to "v${libs.versions.protobuf.get().substringAfter('.')}")
- filteringCharset = "UTF-8"
-}
-
-tasks.register("generateTestSourcesImports") {
- dependsOn("exportProtovalidateModule", "filterBufGenYaml")
- description = "Generates code with buf generate --include-imports for unit tests."
- val template = layout.buildDirectory.file("buf-gen-templates/src/test/resources/proto/buf.gen.imports.yaml")
- inputs.files(buf)
- inputs.file(template)
- inputs.dir("src/test/resources/proto")
- outputs.dir(layout.buildDirectory.dir("generated/test-sources/bufgen-imports"))
- commandLine(
- buf.asPath,
- "generate",
- "--template",
- template.get().asFile.absolutePath,
- "--include-imports",
- )
-}
-
-tasks.register("generateTestSourcesNoImports") {
- dependsOn("exportProtovalidateModule", "filterBufGenYaml")
- description = "Generates code with buf generate --include-imports for unit tests."
- val template = layout.buildDirectory.file("buf-gen-templates/src/test/resources/proto/buf.gen.noimports.yaml")
- inputs.files(buf)
- inputs.file(template)
- inputs.dir("src/main/resources")
- inputs.dir("src/test/resources/proto")
- outputs.dir(layout.buildDirectory.dir("generated/test-sources/bufgen-noimports"))
- commandLine(
- buf.asPath,
- "generate",
- "--template",
- template.get().asFile.absolutePath,
- )
-}
-
-tasks.register("generateCelConformance") {
- dependsOn("generateCelConformanceTestTypes", "filterBufGenYaml")
- description = "Generates CEL conformance code with buf generate for unit tests."
- val template = layout.buildDirectory.file("buf-gen-templates/src/test/resources/proto/buf.gen.cel.yaml")
- inputs.files(buf)
- inputs.file(template)
- inputs.property("celSpecVersion", project.findProperty("cel.spec.version").toString())
- outputs.dir(layout.buildDirectory.dir("generated/test-sources/bufgen-cel"))
- commandLine(
- buf.asPath,
- "generate",
- "--template",
- template.get().asFile.absolutePath,
- "buf.build/google/cel-spec:${project.findProperty("cel.spec.version")}",
- "--exclude-path",
- "cel/expr/conformance/proto2",
- "--exclude-path",
- "cel/expr/conformance/proto3",
- )
-}
-
-// The conformance tests use the Protobuf package path for tests that use these types.
-// i.e. cel.expr.conformance.proto3.TestAllTypes. But, if we use managed mode it adds 'com'
-// to the prefix. Additionally, we can't disable managed mode because the java_package option
-// specified in these proto files is "dev.cel.expr.conformance.proto3". So, to get around this,
-// we're generating these separately and specifying a java_package override of the package we need.
-tasks.register("generateCelConformanceTestTypes") {
- dependsOn("exportProtovalidateModule", "filterBufGenYaml")
- description = "Generates CEL conformance test types with buf generate for unit tests using a Java package override."
- val template = layout.buildDirectory.file("buf-gen-templates/src/test/resources/proto/buf.gen.cel.testtypes.yaml")
- inputs.files(buf)
- inputs.file(template)
- inputs.property("celSpecVersion", project.findProperty("cel.spec.version").toString())
- outputs.dir(layout.buildDirectory.dir("generated/test-sources/bufgen-cel-testtypes"))
- commandLine(
- buf.asPath,
- "generate",
- "--template",
- template.get().asFile.absolutePath,
- "buf.build/google/cel-spec:${project.findProperty("cel.spec.version")}",
- "--path",
- "cel/expr/conformance/proto3",
- )
-}
-
-var getCelTestData =
- tasks.register("getCelTestData") {
- val celVersion = project.findProperty("cel.spec.version")
- val fileUrl = "https://raw.githubusercontent.com/google/cel-spec/refs/tags/$celVersion/tests/simple/testdata/string_ext.textproto"
- val targetDir = File("${project.projectDir}/src/test/resources/testdata")
- val file = File(targetDir, "string_ext_$celVersion.textproto")
-
- onlyIf {
- // Only run curl if file doesn't exist
- !file.exists()
- }
- doFirst {
- file.parentFile.mkdirs()
- commandLine(
- "curl",
- "-fsSL",
- "-o",
- file.absolutePath,
- fileUrl,
- )
- }
- }
-
-tasks.register("generateTestSources") {
- dependsOn("generateTestSourcesImports", "generateTestSourcesNoImports", "generateCelConformance")
- description = "Generates code with buf generate for unit tests"
-}
-
-val semverRegex = Regex("""^v\d+\.\d+\.\d+(?:-.+)?$""")
-tasks.register("exportProtovalidateModule") {
- dependsOn("configureBuf")
- description = "Exports the bufbuild/protovalidate module sources to src/main/resources."
- val version = "${project.findProperty("protovalidate.version")}"
- var input = "buf.build/bufbuild/protovalidate:$version"
- if (!semverRegex.matches(version)) {
- input = "https://github.com/bufbuild/protovalidate.git#subdir=proto/protovalidate,ref=$version"
- }
- inputs.files(buf)
- inputs.property("protovalidateVersion", version)
- outputs.dir("src/main/resources/buf")
- commandLine(
- buf.asPath,
- "export",
- input,
- "--output",
- "src/main/resources",
- )
-}
-
-tasks.register("generateSources") {
- dependsOn("exportProtovalidateModule", "filterBufGenYaml")
- description = "Generates sources for the bufbuild/protovalidate module sources to build/generated/sources/bufgen."
- val template = layout.buildDirectory.file("buf-gen-templates/buf.gen.yaml")
- inputs.files(buf)
- inputs.file(template)
- inputs.dir("src/main/resources/buf")
- outputs.dir(layout.buildDirectory.dir("generated/sources/bufgen"))
- commandLine(buf.asPath, "generate", "--template", template.get().asFile.absolutePath, "src/main/resources")
-}
-
-tasks.register("generate") {
- description = "Generates sources with buf generate and buf export."
- dependsOn(
- "generateTestSources",
- "generateSources",
- "licenseHeader",
- ":conformance:generateConformance",
- ":benchmarks:generateBenchmarkSources",
- )
-}
-
-// src/main/resources and build/generated/sources/bufgen are generate-task outputs; consumers need explicit ordering.
-tasks.named("processResources") { dependsOn("exportProtovalidateModule") }
-tasks.matching { it.name == "sourcesJar" }.configureEach {
- dependsOn("exportProtovalidateModule", "generateSources")
-}
-
tasks.withType {
- dependsOn("generate")
- if (JavaVersion.current().isJava9Compatible) {
- doFirst {
- options.compilerArgs = mutableListOf("--release", "8")
- }
+ if (JavaVersion.current().isJava9Compatible) doFirst {
+ options.compilerArgs = mutableListOf("--release", "8")
}
- // Disable errorprone on generated code
- options.errorprone.excludedPaths.set(".*/build/generated/.*")
if (!name.lowercase().contains("test")) {
options.errorprone {
check("NullAway", CheckSeverity.ERROR)
@@ -284,10 +28,9 @@ tasks.withType {
}
tasks.withType {
- val stdOptions = options as StandardJavadocDocletOptions
- stdOptions.addBooleanOption("Xwerror", true)
- // Ignore warnings for generated code.
- stdOptions.addBooleanOption("Xdoclint/package:-build.buf.validate,-build.buf.validate.priv", true)
+ // TODO: Enable when Javadoc changes are final
+// val stdOptions = options as StandardJavadocDocletOptions
+// stdOptions.addBooleanOption("Xwerror", true)
}
tasks.withType {
@@ -301,128 +44,77 @@ buildscript {
}
}
-sourceSets {
- main {
- java {
- srcDir(layout.buildDirectory.dir("generated/sources/bufgen"))
- }
- }
- test {
- java {
- srcDir(layout.buildDirectory.dir("generated/test-sources/bufgen-imports"))
- srcDir(layout.buildDirectory.dir("generated/test-sources/bufgen-noimports"))
- srcDir(layout.buildDirectory.dir("generated/test-sources/bufgen-cel"))
- srcDir(layout.buildDirectory.dir("generated/test-sources/bufgen-cel-testtypes"))
- }
- }
-}
-
-apply(plugin = "com.diffplug.spotless")
-configure {
- java {
- targetExclude("build/generated/sources/bufgen/build/buf/validate/**/*.java", "build/generated/test-sources/bufgen-*/**/*.java")
- }
- kotlinGradle {
- ktlint()
- target("**/*.kts")
- }
-}
-
allprojects {
- version = releaseVersion
repositories {
mavenCentral()
+ maven {
+ name = "buf"
+ url = uri("https://buf.build/gen/maven")
+ }
}
apply(plugin = "com.diffplug.spotless")
configure {
- isEnforceCheck = false // Disables lint on gradle builds.
+ setEnforceCheck(false) // Disables lint on gradle builds.
java {
importOrder()
removeUnusedImports()
- replaceRegex("Remove wildcard imports", "import\\s+[^\\*\\s]+\\*;(\\r\\n|\\r|\\n)", "$1")
googleJavaFormat()
endWithNewline()
trimTrailingWhitespace()
}
}
- tasks.withType().configureEach {
- if (name == "jar") {
- manifest {
- attributes("Implementation-Version" to releaseVersion)
- }
- }
- }
- tasks.withType().configureEach {
- dependsOn(getCelTestData)
- useJUnitPlatform()
- this.testLogging {
- events("failed")
- exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
- showExceptions = true
- showCauses = true
- showStackTraces = true
- }
- }
}
-mavenPublishing {
- val isAutoReleased = project.hasProperty("signingInMemoryKey")
- publishToMavenCentral(automaticRelease = true)
- if (isAutoReleased) {
- signAllPublications()
- }
- coordinates("build.buf", "protovalidate", releaseVersion)
- pomFromGradleProperties()
- configure(
- JavaLibrary(
- // configures the -javadoc artifact, possible values:
- // - `JavadocJar.None()` don't publish this artifact
- // - `JavadocJar.Empty()` publish an empty jar
- // - `JavadocJar.Javadoc()` to publish standard javadocs
- javadocJar = JavadocJar.Javadoc(),
- sourcesJar = SourcesJar.Sources(),
- ),
- )
- pom {
- name.set("protovalidate-java")
- group = "build.buf"
- description.set("Protocol Buffer Validation")
- url.set("https://github.com/bufbuild/protovalidate-java")
- licenses {
- license {
- name.set("The Apache Software License, Version 2.0")
- url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
- distribution.set("repo")
- }
- }
- developers {
- developer {
- id.set("bufbuild")
- name.set("Buf Technologies")
- }
+apply(plugin = "com.vanniktech.maven.publish.base")
+plugins.withId("com.vanniktech.maven.publish.base") {
+ configure {
+ val isAutoReleased = project.hasProperty("signingInMemoryKey")
+ publishToMavenCentral(SonatypeHost.S01)
+ if (isAutoReleased) {
+ signAllPublications()
}
- scm {
+ pom {
+ description.set("Protocol Buffer Validation")
+ name.set("protovalidate") // This is overwritten in subprojects.
+ group = "build.buf"
+ val releaseVersion = project.findProperty("releaseVersion") as String? ?: System.getenv("VERSION")
+ // Default to snapshot versioning for local publishing.
+ version = releaseVersion ?: "0.0.0-SNAPSHOT"
url.set("https://github.com/bufbuild/protovalidate-java")
- connection.set("scm:git:https://github.com/bufbuild/protovalidate-java.git")
- developerConnection.set("scm:git:ssh://git@github.com/bufbuild/protovalidate-java.git")
+ licenses {
+ license {
+ name.set("The Apache Software License, Version 2.0")
+ url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
+ distribution.set("repo")
+ }
+ }
+ developers {
+ developer {
+ id.set("bufbuild")
+ name.set("Buf Technologies")
+ }
+ }
+ scm {
+ url.set("https://github.com/bufbuild/protovalidate-java")
+ connection.set("scm:git:https://github.com/bufbuild/protovalidate-java.git")
+ developerConnection.set("scm:git:ssh://git@github.com/bufbuild/protovalidate-java.git")
+ }
}
}
}
dependencies {
annotationProcessor(libs.nullaway)
- api(libs.jspecify)
api(libs.protobuf.java)
- implementation(libs.cel)
- implementation(libs.re2j)
-
- buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe")
+ api(libs.protovalidate)
+ implementation(enforcedPlatform(libs.cel))
+ implementation(libs.cel.core)
+ implementation(libs.guava)
+ implementation(libs.jakarta.mail.api)
testImplementation(libs.assertj)
- testImplementation(libs.grpc.protobuf)
- testImplementation(platform(libs.junit.bom))
- testImplementation("org.junit.jupiter:junit-jupiter")
- testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+ testImplementation(libs.junit)
+ testImplementation(libs.protovalidate.testing)
- errorprone(libs.errorprone.core)
-}
+ errorprone(libs.errorprone)
+}
\ No newline at end of file
diff --git a/conformance/buf.gen.yaml b/conformance/buf.gen.yaml
deleted file mode 100644
index 4fecae0eb..000000000
--- a/conformance/buf.gen.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-version: v2
-managed:
- enabled: true
- override:
- - file_option: java_package_prefix
- value: build
-plugins:
- - remote: buf.build/protocolbuffers/java:$protocJavaPluginVersion
- out: build/generated/sources/bufgen
diff --git a/conformance/build.gradle.kts b/conformance/build.gradle.kts
index 297c6e307..a52130e23 100644
--- a/conformance/build.gradle.kts
+++ b/conformance/build.gradle.kts
@@ -1,108 +1,8 @@
-import com.diffplug.gradle.spotless.SpotlessExtension
-import net.ltgt.gradle.errorprone.errorprone
-
plugins {
`version-catalog`
- application
java
- alias(libs.plugins.errorprone)
- alias(libs.plugins.osdetector)
-}
-
-// Conformance tests aren't bound by lowest common library version.
-java {
- sourceCompatibility = JavaVersion.VERSION_21
- targetCompatibility = JavaVersion.VERSION_21
-}
-
-val buf: Configuration by configurations.creating
-
-tasks.register("configureBuf") {
- description = "Installs the Buf CLI."
- File(buf.asPath).setExecutable(true)
-}
-
-val conformanceCLIFile =
- project.layout.buildDirectory
- .file("gobin/protovalidate-conformance")
- .get()
- .asFile
-val conformanceCLIPath: String = conformanceCLIFile.absolutePath
-val conformanceAppScript: String =
- project.layout.buildDirectory
- .file("install/conformance/bin/conformance")
- .get()
- .asFile.absolutePath
-val conformanceArgs = (project.findProperty("protovalidate.conformance.args")?.toString() ?: "").split("\\s+".toRegex())
-
-tasks.register("installProtovalidateConformance") {
- description = "Installs the Protovalidate Conformance CLI."
- environment("GOBIN", conformanceCLIFile.parentFile.absolutePath)
- inputs.property("protovalidateVersion", project.findProperty("protovalidate.version").toString())
- outputs.file(conformanceCLIFile)
- commandLine(
- "go",
- "install",
- "github.com/bufbuild/protovalidate/tools/protovalidate-conformance@${project.findProperty("protovalidate.version")}",
- )
-}
-
-tasks.register("conformance") {
- dependsOn("installDist", "installProtovalidateConformance")
- description = "Runs protovalidate conformance tests."
- commandLine(*(listOf(conformanceCLIPath) + conformanceArgs + listOf(conformanceAppScript)).toTypedArray())
-}
-
-tasks.register("filterBufGenYaml") {
- from(".")
- include("buf.gen.yaml")
- includeEmptyDirs = false
- into(layout.buildDirectory.dir("buf-gen-templates"))
- expand("protocJavaPluginVersion" to "v${libs.versions.protobuf.get().substringAfter('.')}")
- filteringCharset = "UTF-8"
-}
-
-val semverRegex = Regex("""^v\d+\.\d+\.\d+(?:-.+)?$""")
-tasks.register("generateConformance") {
- dependsOn("configureBuf", "filterBufGenYaml")
- description = "Generates sources for the bufbuild/protovalidate-testing module to build/generated/sources/bufgen."
- val version = "${project.findProperty("protovalidate.version")}"
- var input = "buf.build/bufbuild/protovalidate-testing:$version"
- if (!semverRegex.matches(version)) {
- input = "https://github.com/bufbuild/protovalidate.git#subdir=proto/protovalidate-testing,ref=$version"
- }
- val template = layout.buildDirectory.file("buf-gen-templates/buf.gen.yaml")
- inputs.files(buf)
- inputs.file(template)
- inputs.property("protovalidateVersion", version)
- outputs.dir(layout.buildDirectory.dir("generated/sources/bufgen"))
- commandLine(
- buf.asPath,
- "generate",
- "--template",
- template.get().asFile.absolutePath,
- input,
- )
-}
-
-sourceSets {
- main {
- java {
- srcDir(layout.buildDirectory.dir("generated/sources/bufgen"))
- }
- }
-}
-
-tasks.withType {
- dependsOn("generateConformance")
- if (JavaVersion.current().isJava9Compatible) {
- doFirst {
- options.compilerArgs = mutableListOf("--release", "8")
- }
- }
- // Disable errorprone on generated code
- options.errorprone.excludedPaths.set(".*/build/generated/sources/bufgen/.*")
+ alias(libs.plugins.errorprone.plugin)
}
// Disable javadoc for conformance tests
@@ -110,49 +10,35 @@ tasks.withType {
enabled = false
}
-application {
- mainClass.set("build.buf.protovalidate.conformance.Main")
-}
-
tasks {
jar {
- dependsOn(":jar")
manifest {
- attributes(mapOf("Main-Class" to "build.buf.protovalidate.conformance.Main"))
+ attributes(mapOf("Main-Class" to "build.buf.Main"))
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
// This line of code recursively collects and copies all of a project's files
// and adds them to the JAR itself. One can extend this task, to skip certain
// files or particular types at will
val sourcesMain = sourceSets.main.get()
- val contents =
- configurations.runtimeClasspath
- .get()
- .map { if (it.isDirectory) it else zipTree(it) } +
+ val contents = configurations.runtimeClasspath.get()
+ .map { if (it.isDirectory) it else zipTree(it) } +
sourcesMain.output
from(contents)
}
}
-apply(plugin = "com.diffplug.spotless")
-configure {
- java {
- targetExclude("build/generated/sources/bufgen/**/*.java")
- }
-}
-
dependencies {
implementation(project(":"))
- implementation(libs.errorprone.annotations)
+ implementation(libs.guava)
implementation(libs.protobuf.java)
+ implementation(libs.protovalidate)
+ implementation(libs.protovalidate.testing)
+ implementation(libs.grpc.netty)
+ implementation("build.buf.gen:bufbuild_protovalidate-testing_grpc_java:1.56.1.1.00000000000000.79e519aab9ca")
- implementation(libs.assertj)
- implementation(platform(libs.junit.bom))
-
- buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe")
+ testImplementation(libs.assertj)
+ testImplementation(libs.junit)
+ testImplementation(libs.protovalidate.testing)
- testImplementation("org.junit.jupiter:junit-jupiter")
- testRuntimeOnly("org.junit.platform:junit-platform-launcher")
-
- errorprone(libs.errorprone.core)
-}
+ errorprone(libs.errorprone)
+}
\ No newline at end of file
diff --git a/conformance/conformance.sh b/conformance/conformance.sh
new file mode 100755
index 000000000..61ee87d40
--- /dev/null
+++ b/conformance/conformance.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+set -euo pipefail
+java -jar conformance/build/libs/conformance.jar
diff --git a/conformance/src/main/java/build/.DS_Store b/conformance/src/main/java/build/.DS_Store
deleted file mode 100644
index f17940234..000000000
Binary files a/conformance/src/main/java/build/.DS_Store and /dev/null differ
diff --git a/conformance/src/main/java/build/buf/protovalidate/conformance/FileDescriptorUtil.java b/conformance/src/main/java/build/buf/FileDescriptorUtil.java
similarity index 59%
rename from conformance/src/main/java/build/buf/protovalidate/conformance/FileDescriptorUtil.java
rename to conformance/src/main/java/build/buf/FileDescriptorUtil.java
index 2f01b236b..34ced95fd 100644
--- a/conformance/src/main/java/build/buf/protovalidate/conformance/FileDescriptorUtil.java
+++ b/conformance/src/main/java/build/buf/FileDescriptorUtil.java
@@ -1,4 +1,4 @@
-// Copyright 2023-2026 Buf Technologies, Inc.
+// Copyright 2023 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.
@@ -12,13 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package build.buf.protovalidate.conformance;
+package build.buf;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
-import com.google.protobuf.DynamicMessage;
-import com.google.protobuf.ExtensionRegistry;
-import com.google.protobuf.TypeRegistry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -73,50 +70,4 @@ static Map parseFileDescriptors(
}
return fileDescriptorMap;
}
-
- static TypeRegistry createTypeRegistry(
- Iterable extends Descriptors.FileDescriptor> fileDescriptors) {
- TypeRegistry.Builder registryBuilder = TypeRegistry.newBuilder();
- for (Descriptors.FileDescriptor fileDescriptor : fileDescriptors) {
- registryBuilder.add(fileDescriptor.getMessageTypes());
- }
- return registryBuilder.build();
- }
-
- static ExtensionRegistry createExtensionRegistry(
- Iterable extends Descriptors.FileDescriptor> fileDescriptors) {
- ExtensionRegistry registry = ExtensionRegistry.newInstance();
- for (Descriptors.FileDescriptor fileDescriptor : fileDescriptors) {
- registerFileExtensions(registry, fileDescriptor);
- }
- return registry;
- }
-
- private static void registerFileExtensions(
- ExtensionRegistry registry, Descriptors.FileDescriptor fileDescriptor) {
- registerExtensions(registry, fileDescriptor.getExtensions());
- for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
- registerMessageExtensions(registry, descriptor);
- }
- }
-
- private static void registerMessageExtensions(
- ExtensionRegistry registry, Descriptors.Descriptor descriptor) {
- registerExtensions(registry, descriptor.getExtensions());
- for (Descriptors.Descriptor nestedDescriptor : descriptor.getNestedTypes()) {
- registerMessageExtensions(registry, nestedDescriptor);
- }
- }
-
- private static void registerExtensions(
- ExtensionRegistry registry, List extensions) {
- for (Descriptors.FieldDescriptor fieldDescriptor : extensions) {
- if (fieldDescriptor.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) {
- registry.add(
- fieldDescriptor, DynamicMessage.getDefaultInstance(fieldDescriptor.getMessageType()));
- } else {
- registry.add(fieldDescriptor);
- }
- }
- }
}
diff --git a/conformance/src/main/java/build/buf/Main.java b/conformance/src/main/java/build/buf/Main.java
new file mode 100644
index 000000000..ebd22a0a9
--- /dev/null
+++ b/conformance/src/main/java/build/buf/Main.java
@@ -0,0 +1,160 @@
+// Copyright 2023 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.
+
+package build.buf;
+
+import build.buf.gen.buf.validate.ValidateProto;
+import build.buf.gen.buf.validate.Violations;
+import build.buf.gen.buf.validate.conformance.harness.ConformanceServiceGrpc;
+import build.buf.gen.buf.validate.conformance.harness.StreamingConformanceRequest;
+import build.buf.gen.buf.validate.conformance.harness.StreamingConformanceResponse;
+import build.buf.gen.buf.validate.conformance.harness.TestConformanceRequest;
+import build.buf.gen.buf.validate.conformance.harness.TestConformanceResponse;
+import build.buf.gen.buf.validate.conformance.harness.TestResult;
+import build.buf.protovalidate.Config;
+import build.buf.protovalidate.ValidationResult;
+import build.buf.protovalidate.Validator;
+import build.buf.protovalidate.exceptions.CompilationException;
+import build.buf.protovalidate.exceptions.ExecutionException;
+import com.google.common.base.Splitter;
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.protobuf.Any;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Server;
+import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+
+import java.util.List;
+import java.util.Map;
+
+public class Main {
+
+ public static final class Service extends ConformanceServiceGrpc.ConformanceServiceImplBase {
+ static ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
+ static {
+ extensionRegistry.add(ValidateProto.message);
+ extensionRegistry.add(ValidateProto.field);
+ extensionRegistry.add(ValidateProto.oneof);
+ }
+
+ @Override
+ public void testConformance(TestConformanceRequest request, StreamObserver responseObserver) {
+ StreamingConformanceResponse streamingConformanceResponse = test(StreamingConformanceRequest.newBuilder()
+ .setFdset(request.getFdset())
+ .putAllCases(request.getCasesMap())
+ .build());
+ responseObserver.onNext(TestConformanceResponse.newBuilder().putAllResults(streamingConformanceResponse.getResultsMap()).build());
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver streamingConformance(StreamObserver responseObserver) {
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(StreamingConformanceRequest request) {
+ try {
+ StreamingConformanceRequest hydratedRequest = StreamingConformanceRequest.parseFrom(request.toByteString(), extensionRegistry);
+ StreamingConformanceResponse streamingConformanceResponse = test(hydratedRequest);
+ responseObserver.onNext(streamingConformanceResponse);
+ } catch (InvalidProtocolBufferException e) {
+ responseObserver.onError(e);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ responseObserver.onError(t);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+ }
+ public static void main(String[] args) {
+ try {
+ Service bindableService = new Service();
+ Server server = NettyServerBuilder.forPort(1235)
+ .addService(bindableService)
+ .build();
+ server.start();
+ server.awaitTermination();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static StreamingConformanceResponse test(StreamingConformanceRequest request) {
+ try {
+ Map descriptorMap =
+ FileDescriptorUtil.parse(request.getFdset());
+ Validator validator = new Validator(Config.builder().build());
+ StreamingConformanceResponse.Builder responseBuilder = StreamingConformanceResponse.newBuilder().setSuiteName(request.getSuiteName());
+ for (Map.Entry entry : request.getCasesMap().entrySet()) {
+ TestResult testResult = testCase(validator, descriptorMap, entry.getValue());
+ responseBuilder.putResults(entry.getKey(), testResult);
+ }
+ return responseBuilder.build();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static TestResult testCase(
+ Validator validator, Map fileDescriptors, Any testCase)
+ throws InvalidProtocolBufferException {
+ List urlParts = Splitter.on('/').limit(2).splitToList(testCase.getTypeUrl());
+ String fullName = urlParts.get(urlParts.size() - 1);
+ Descriptors.Descriptor descriptor = fileDescriptors.get(fullName);
+ if (descriptor == null) {
+ return unexpectedErrorResult("Unable to find descriptor: %s", fullName);
+ }
+ ByteString testCaseValue = testCase.getValue();
+ DynamicMessage dynamicMessage =
+ DynamicMessage.newBuilder(descriptor).mergeFrom(testCaseValue).build();
+ return validate(validator, dynamicMessage);
+ }
+
+ private static TestResult validate(Validator validator, DynamicMessage dynamicMessage) {
+ try {
+ ValidationResult result = validator.validate(dynamicMessage);
+ if (result.getViolations().isEmpty()) {
+ return TestResult.newBuilder().setSuccess(true).build();
+ } else {
+ return TestResult.newBuilder()
+ .setValidationError(
+ Violations.newBuilder().addAllViolations(result.getViolations()).build())
+ .build();
+ }
+ } catch (CompilationException e) {
+ return TestResult.newBuilder().setCompilationError(e.getMessage()).build();
+ } catch (ExecutionException e) {
+ return TestResult.newBuilder().setRuntimeError(e.getMessage()).build();
+ } catch (Exception e) {
+ return unexpectedErrorResult("unknown error: %s", e.toString());
+ }
+ }
+
+ @FormatMethod
+ private static TestResult unexpectedErrorResult(String format, Object... args) {
+ String errorMessage = String.format(format, args);
+ return TestResult.newBuilder().setUnexpectedError(errorMessage).build();
+ }
+}
diff --git a/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java b/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java
deleted file mode 100644
index 902de0a63..000000000
--- a/conformance/src/main/java/build/buf/protovalidate/conformance/Main.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate.conformance;
-
-import build.buf.protovalidate.Config;
-import build.buf.protovalidate.ValidationResult;
-import build.buf.protovalidate.Validator;
-import build.buf.protovalidate.ValidatorFactory;
-import build.buf.protovalidate.exceptions.CompilationException;
-import build.buf.protovalidate.exceptions.ExecutionException;
-import build.buf.validate.ValidateProto;
-import build.buf.validate.Violations;
-import build.buf.validate.conformance.harness.TestConformanceRequest;
-import build.buf.validate.conformance.harness.TestConformanceResponse;
-import build.buf.validate.conformance.harness.TestResult;
-import com.google.errorprone.annotations.FormatMethod;
-import com.google.protobuf.Any;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.Descriptors;
-import com.google.protobuf.DynamicMessage;
-import com.google.protobuf.ExtensionRegistry;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.TypeRegistry;
-import java.util.HashMap;
-import java.util.Map;
-
-public class Main {
- public static void main(String[] args) {
- try {
- ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
- extensionRegistry.add(ValidateProto.message);
- extensionRegistry.add(ValidateProto.field);
- extensionRegistry.add(ValidateProto.oneof);
- TestConformanceRequest request =
- TestConformanceRequest.parseFrom(System.in, extensionRegistry);
- TestConformanceResponse response = testConformance(request);
- response.writeTo(System.out);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- static TestConformanceResponse testConformance(TestConformanceRequest request) {
- try {
- Map descriptorMap =
- FileDescriptorUtil.parse(request.getFdset());
- Map fileDescriptorMap =
- FileDescriptorUtil.parseFileDescriptors(request.getFdset());
- TypeRegistry typeRegistry = FileDescriptorUtil.createTypeRegistry(fileDescriptorMap.values());
- ExtensionRegistry extensionRegistry =
- FileDescriptorUtil.createExtensionRegistry(fileDescriptorMap.values());
- String envFlag = System.getenv("ENABLE_NATIVE_RULES");
- Config.Builder cfgBuilder =
- Config.newBuilder().setTypeRegistry(typeRegistry).setExtensionRegistry(extensionRegistry);
- if (envFlag != null) {
- cfgBuilder.setEnableNativeRules(Boolean.parseBoolean(envFlag));
- }
- Config cfg = cfgBuilder.build();
- Validator validator = ValidatorFactory.newBuilder().withConfig(cfg).build();
-
- TestConformanceResponse.Builder responseBuilder = TestConformanceResponse.newBuilder();
- Map resultsMap = new HashMap<>();
- for (Map.Entry entry : request.getCasesMap().entrySet()) {
- TestResult testResult = testCase(validator, descriptorMap, entry.getValue());
- resultsMap.put(entry.getKey(), testResult);
- }
- responseBuilder.putAllResults(resultsMap);
- return responseBuilder.build();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- static TestResult testCase(
- Validator validator, Map fileDescriptors, Any testCase)
- throws InvalidProtocolBufferException {
- String fullName = testCase.getTypeUrl();
- int slash = fullName.indexOf('/');
- if (slash != -1) {
- fullName = fullName.substring(slash + 1);
- }
- Descriptors.Descriptor descriptor = fileDescriptors.get(fullName);
- if (descriptor == null) {
- return unexpectedErrorResult("Unable to find descriptor: %s", fullName);
- }
- ByteString testCaseValue = testCase.getValue();
- DynamicMessage dynamicMessage =
- DynamicMessage.newBuilder(descriptor).mergeFrom(testCaseValue).build();
- return validate(validator, dynamicMessage);
- }
-
- private static TestResult validate(Validator validator, DynamicMessage dynamicMessage) {
- try {
- ValidationResult result = validator.validate(dynamicMessage);
- if (result.isSuccess()) {
- return TestResult.newBuilder().setSuccess(true).build();
- }
- Violations error =
- Violations.newBuilder().addAllViolations(result.toProto().getViolationsList()).build();
- return TestResult.newBuilder().setValidationError(error).build();
- } catch (CompilationException e) {
- return TestResult.newBuilder().setCompilationError(e.getMessage()).build();
- } catch (ExecutionException e) {
- return TestResult.newBuilder().setRuntimeError(e.getMessage()).build();
- } catch (Exception e) {
- return unexpectedErrorResult("unknown error: %s", e.toString());
- }
- }
-
- @FormatMethod
- static TestResult unexpectedErrorResult(String format, Object... args) {
- String errorMessage = String.format(format, args);
- return TestResult.newBuilder().setUnexpectedError(errorMessage).build();
- }
-}
diff --git a/conformance/src/test/java/build/buf/MainTest.java b/conformance/src/test/java/build/buf/MainTest.java
new file mode 100644
index 000000000..0c4d2b046
--- /dev/null
+++ b/conformance/src/test/java/build/buf/MainTest.java
@@ -0,0 +1,24 @@
+package build.buf;
+
+
+import build.buf.gen.buf.validate.conformance.harness.ConformanceServiceGrpc;
+import build.buf.gen.buf.validate.conformance.harness.TestConformanceRequest;
+import build.buf.gen.buf.validate.conformance.harness.TestConformanceResponse;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class MainTest {
+// @Test
+ public void asdf() {
+ ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 1234)
+ .usePlaintext() // Use clear text (no encryption)
+ .build();
+ ConformanceServiceGrpc.ConformanceServiceBlockingStub stub = ConformanceServiceGrpc.newBlockingStub(channel);
+ TestConformanceResponse testConformanceResponse = stub.testConformance(TestConformanceRequest.newBuilder().build());
+ assertEquals(testConformanceResponse, testConformanceResponse);
+ }
+
+}
\ No newline at end of file
diff --git a/conformance/src/test/java/build/buf/protovalidate/NativeRulesParityTest.java b/conformance/src/test/java/build/buf/protovalidate/NativeRulesParityTest.java
deleted file mode 100644
index 88f6266ca..000000000
--- a/conformance/src/test/java/build/buf/protovalidate/NativeRulesParityTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import build.buf.protovalidate.exceptions.ValidationException;
-import build.buf.validate.conformance.cases.AnEnum;
-import build.buf.validate.conformance.cases.BoolConstTrue;
-import build.buf.validate.conformance.cases.BytesContains;
-import build.buf.validate.conformance.cases.BytesIn;
-import build.buf.validate.conformance.cases.ComplexTestMsg;
-import build.buf.validate.conformance.cases.EnumDefined;
-import build.buf.validate.conformance.cases.Fixed32LT;
-import build.buf.validate.conformance.cases.Int32In;
-import build.buf.validate.conformance.cases.KitchenSinkMessage;
-import build.buf.validate.conformance.cases.RepeatedEnumIn;
-import build.buf.validate.conformance.cases.RepeatedExact;
-import build.buf.validate.conformance.cases.RepeatedUnique;
-import build.buf.validate.conformance.cases.SFixed64In;
-import build.buf.validate.conformance.cases.StringContains;
-import build.buf.validate.conformance.cases.StringLen;
-import build.buf.validate.conformance.cases.StringPrefix;
-import build.buf.validate.conformance.cases.WrapperDouble;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.DoubleValue;
-import com.google.protobuf.Message;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-/**
- * Parity test: runs a representative slice of conformance fixtures through both modes ({@code
- * enableNativeRules=true} and {@code false}) and asserts the resulting {@code Violation} protos are
- * byte-equal. The conformance suite proves each mode is correct in isolation; this test proves they
- * don't drift from each other on the same input.
- *
- *
When true, standard rules with a native Java implementation bypass CEL evaluation. When
- * false, all rules go through CEL. Defaults to true; applications opt out by calling {@link
- * Builder#setEnableNativeRules(boolean) setEnableNativeRules(false)}.
+ * Checks if the configuration for disabling lazy evaluation is enabled.
*
- * @return true if native rules are enabled.
+ * @return if disabling lazy evaluation is enabled
*/
- public boolean isNativeRulesEnabled() {
- return enableNativeRules;
+ public boolean isDisableLazy() {
+ return disableLazy;
}
/** Builder for configuration. Provides a forward compatible API for users. */
public static final class Builder {
private boolean failFast;
- private TypeRegistry typeRegistry = DEFAULT_TYPE_REGISTRY;
- private ExtensionRegistry extensionRegistry = DEFAULT_EXTENSION_REGISTRY;
- private boolean allowUnknownFields;
- // native rules are enabled by default
- private boolean enableNativeRules = true;
+ private boolean disableLazy;
private Builder() {}
@@ -123,68 +70,13 @@ public Builder setFailFast(boolean failFast) {
}
/**
- * Set the type registry for reparsing protobuf messages. This option should be set alongside
- * setExtensionRegistry to allow dynamic resolution of predefined rule extensions. It should be
- * set to a TypeRegistry with all the message types from your file descriptor set registered. By
- * default, if any unknown field rules are found, compilation of the rules will fail; use
- * setAllowUnknownFields to control this behavior.
- *
- *
Note that the message types for any extensions in setExtensionRegistry must be present in
- * the typeRegistry, and have an exactly-equal Descriptor. If the type registry is not set, the
- * extension types in the extension registry must have exactly-equal Descriptor types to the
- * protovalidate built-in messages. If these conditions are not met, extensions will not be
- * resolved as expected. These conditions will be met when constructing a TypeRegistry and
- * ExtensionRegistry using information from the same file descriptor sets.
- *
- * @param typeRegistry the type registry to use
- * @return this builder
- */
- public Builder setTypeRegistry(TypeRegistry typeRegistry) {
- this.typeRegistry = typeRegistry;
- return this;
- }
-
- /**
- * Set the extension registry for resolving unknown extensions. This option should be set
- * alongside setTypeRegistry to allow dynamic resolution of predefined rule extensions. It
- * should be set to an ExtensionRegistry with all the extension types from your file descriptor
- * set registered. By default, if any unknown field rules are found, compilation of the rules
- * will fail; use setAllowUnknownFields to control this behavior.
- *
- * @param extensionRegistry the extension registry to use
- * @return this builder
- */
- public Builder setExtensionRegistry(ExtensionRegistry extensionRegistry) {
- this.extensionRegistry = extensionRegistry;
- return this;
- }
-
- /**
- * Set whether unknown rule fields are allowed. If this setting is set to true, unknown standard
- * predefined field rules and predefined field rule extensions will be ignored. This setting
- * defaults to false, which will result in a CompilationException being thrown whenever an
- * unknown field rule is encountered. Setting this to true will cause some field rules to be
- * ignored; if the descriptor is dynamic, you can instead use setExtensionRegistry to provide
- * dynamic type information that protovalidate can use to resolve the unknown fields.
- *
- * @param allowUnknownFields setting to apply
- * @return this builder
- */
- public Builder setAllowUnknownFields(boolean allowUnknownFields) {
- this.allowUnknownFields = allowUnknownFields;
- return this;
- }
-
- /**
- * Enables or disables native (non-CEL) rule evaluators. Native rules are enabled by default.
- * Forward-compatible: any rule not yet implemented natively continues to be enforced via CEL
- * regardless of this setting.
+ * Set the configuration for disabling lazy evaluation.
*
- * @param enableNativeRules whether to enable native rules
+ * @param disableLazy the boolean for enabling
* @return this builder
*/
- public Builder setEnableNativeRules(boolean enableNativeRules) {
- this.enableNativeRules = enableNativeRules;
+ public Builder setDisableLazy(boolean disableLazy) {
+ this.disableLazy = disableLazy;
return this;
}
@@ -194,8 +86,7 @@ public Builder setEnableNativeRules(boolean enableNativeRules) {
* @return the configuration.
*/
public Config build() {
- return new Config(
- failFast, typeRegistry, extensionRegistry, allowUnknownFields, enableNativeRules);
+ return new Config(failFast, disableLazy);
}
}
}
diff --git a/src/main/java/build/buf/protovalidate/CustomDeclarations.java b/src/main/java/build/buf/protovalidate/CustomDeclarations.java
deleted file mode 100644
index a4f61bf8b..000000000
--- a/src/main/java/build/buf/protovalidate/CustomDeclarations.java
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate;
-
-import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration;
-import static dev.cel.common.CelOverloadDecl.newGlobalOverload;
-import static dev.cel.common.CelOverloadDecl.newMemberOverload;
-
-import dev.cel.common.CelFunctionDecl;
-import dev.cel.common.CelOverloadDecl;
-import dev.cel.common.types.CelType;
-import dev.cel.common.types.ListType;
-import dev.cel.common.types.SimpleType;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/** Defines custom declaration functions. */
-final class CustomDeclarations {
-
- /**
- * Create the custom function declaration list.
- *
- * @return the list of function declarations.
- */
- static List create() {
- List decls = new ArrayList<>();
-
- // Add 'getField' function declaration
- decls.add(
- newFunctionDeclaration(
- "getField",
- newGlobalOverload(
- "get_field_any_string",
- SimpleType.DYN,
- Arrays.asList(SimpleType.ANY, SimpleType.STRING))));
- // Add 'isIp' function declaration
- decls.add(
- newFunctionDeclaration(
- "isIp",
- newMemberOverload(
- "is_ip", SimpleType.BOOL, Arrays.asList(SimpleType.STRING, SimpleType.INT)),
- newMemberOverload(
- "is_ip_unary", SimpleType.BOOL, Collections.singletonList(SimpleType.STRING))));
-
- // Add 'isIpPrefix' function declaration
- decls.add(
- newFunctionDeclaration(
- "isIpPrefix",
- newMemberOverload(
- "is_ip_prefix_int_bool",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.STRING, SimpleType.INT, SimpleType.BOOL)),
- newMemberOverload(
- "is_ip_prefix_int",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.STRING, SimpleType.INT)),
- newMemberOverload(
- "is_ip_prefix_bool",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.STRING, SimpleType.BOOL)),
- newMemberOverload(
- "is_ip_prefix", SimpleType.BOOL, Collections.singletonList(SimpleType.STRING))));
-
- // Add 'isUriRef' function declaration
- decls.add(
- newFunctionDeclaration(
- "isUriRef",
- newMemberOverload(
- "is_uri_ref", SimpleType.BOOL, Collections.singletonList(SimpleType.STRING))));
-
- // Add 'isUri' function declaration
- decls.add(
- newFunctionDeclaration(
- "isUri",
- newMemberOverload(
- "is_uri", SimpleType.BOOL, Collections.singletonList(SimpleType.STRING))));
-
- // Add 'isEmail' function declaration
- decls.add(
- newFunctionDeclaration(
- "isEmail",
- newMemberOverload(
- "is_email", SimpleType.BOOL, Collections.singletonList(SimpleType.STRING))));
-
- // Add 'isHostname' function declaration
- decls.add(
- newFunctionDeclaration(
- "isHostname",
- newMemberOverload(
- "is_hostname", SimpleType.BOOL, Collections.singletonList(SimpleType.STRING))));
-
- // Redeclare 'matches' with the same overload ids as the stdlib.
- decls.add(
- newFunctionDeclaration(
- "matches",
- newGlobalOverload(
- "matches", SimpleType.BOOL, Arrays.asList(SimpleType.STRING, SimpleType.STRING)),
- newMemberOverload(
- "matches_string",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.STRING, SimpleType.STRING))));
-
- decls.add(
- newFunctionDeclaration(
- "isHostAndPort",
- newMemberOverload(
- "string_bool_is_host_and_port_bool",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.STRING, SimpleType.BOOL))));
-
- // Add 'startsWith' function declaration
- decls.add(
- newFunctionDeclaration(
- "startsWith",
- newMemberOverload(
- "starts_with_bytes",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.BYTES, SimpleType.BYTES))));
-
- // Add 'endsWith' function declaration
- decls.add(
- newFunctionDeclaration(
- "endsWith",
- newMemberOverload(
- "ends_with_bytes",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.BYTES, SimpleType.BYTES))));
-
- // Add 'contains' function declaration
- decls.add(
- newFunctionDeclaration(
- "contains",
- newMemberOverload(
- "contains_bytes",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.BYTES, SimpleType.BYTES))));
-
- // Add 'isNan' function declaration
- decls.add(
- newFunctionDeclaration(
- "isNan",
- newMemberOverload(
- "is_nan", SimpleType.BOOL, Collections.singletonList(SimpleType.DOUBLE))));
-
- // Add 'isInf' function declaration
- decls.add(
- newFunctionDeclaration(
- "isInf",
- newMemberOverload(
- "is_inf_unary", SimpleType.BOOL, Collections.singletonList(SimpleType.DOUBLE)),
- newMemberOverload(
- "is_inf_binary",
- SimpleType.BOOL,
- Arrays.asList(SimpleType.DOUBLE, SimpleType.INT))));
-
- // Add 'unique' function declaration
- List uniqueOverloads = new ArrayList<>();
- for (CelType type :
- Arrays.asList(
- SimpleType.STRING,
- SimpleType.INT,
- SimpleType.UINT,
- SimpleType.DOUBLE,
- SimpleType.BYTES,
- SimpleType.BOOL)) {
- uniqueOverloads.add(
- newMemberOverload(
- String.format("unique_list_%s", type.name().toLowerCase(Locale.US)),
- SimpleType.BOOL,
- Collections.singletonList(ListType.create(type))));
- }
- decls.add(newFunctionDeclaration("unique", uniqueOverloads));
-
- // Add 'format' function declaration
- List formatOverloads = new ArrayList<>();
- formatOverloads.add(
- newMemberOverload(
- "format_list_dyn",
- SimpleType.STRING,
- Arrays.asList(SimpleType.STRING, ListType.create(SimpleType.DYN))));
-
- decls.add(newFunctionDeclaration("format", formatOverloads));
-
- return Collections.unmodifiableList(decls);
- }
-}
diff --git a/src/main/java/build/buf/protovalidate/CustomOverload.java b/src/main/java/build/buf/protovalidate/CustomOverload.java
deleted file mode 100644
index 197fcfd42..000000000
--- a/src/main/java/build/buf/protovalidate/CustomOverload.java
+++ /dev/null
@@ -1,665 +0,0 @@
-// Copyright 2023-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.
-
-package build.buf.protovalidate;
-
-import com.google.protobuf.Descriptors;
-import com.google.protobuf.Message;
-import com.google.re2j.Matcher;
-import com.google.re2j.Pattern;
-import com.google.re2j.PatternSyntaxException;
-import dev.cel.common.CelOptions;
-import dev.cel.common.types.CelType;
-import dev.cel.common.types.SimpleType;
-import dev.cel.common.values.CelByteString;
-import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelFunctionBinding;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-
-/** Defines custom function overloads (the implementation). */
-final class CustomOverload {
-
- // See https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
- private static final Pattern EMAIL_REGEX =
- Pattern.compile(
- "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
-
- /**
- * Create a list of custom function overloads.
- *
- * @param patternCache cache used by the {@code matches}/{@code matches_string} overrides.
- * @param celOptions CEL options the enclosing runtime is built with.
- * @return a list of overloaded functions.
- */
- static List create(
- ConcurrentMap patternCache, CelOptions celOptions) {
- ArrayList bindings = new ArrayList<>();
- bindings.addAll(
- Arrays.asList(
- celGetField(),
- celFormat(),
- celStartsWithBytes(),
- celEndsWithBytes(),
- celContainsBytes(),
- celIsHostname(),
- celIsEmail(),
- celIsIpUnary(),
- celIsIp(),
- celIsIpPrefix(),
- celIsIpPrefixInt(),
- celIsIpPrefixBool(),
- celIsIpPrefixIntBool(),
- celIsUri(),
- celIsUriRef(),
- celIsNan(),
- celIsInfUnary(),
- celIsInfBinary(),
- celIsHostAndPort(),
- celMatches(patternCache, celOptions),
- celMatchesString(patternCache, celOptions)));
- bindings.addAll(celUnique());
- return Collections.unmodifiableList(bindings);
- }
-
- /**
- * Creates a custom function overload for the "getField" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "getField" operation.
- */
- private static CelFunctionBinding celGetField() {
- return CelFunctionBinding.from(
- "get_field_any_string",
- Message.class,
- String.class,
- (message, fieldName) -> {
- Descriptors.FieldDescriptor field =
- message.getDescriptorForType().findFieldByName(fieldName);
- if (field == null) {
- throw new CelEvaluationException("no such field: " + fieldName);
- }
- return ProtoAdapter.toCel(field, message.getField(field));
- });
- }
-
- /**
- * Creates a custom binary function overload for the "format" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "format" operation.
- */
- private static CelFunctionBinding celFormat() {
- return CelFunctionBinding.from("format_list_dyn", String.class, List.class, Format::format);
- }
-
- /**
- * Creates a custom unary function overload for the "unique" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "unique" operation.
- */
- private static List celUnique() {
- List uniqueOverloads = new ArrayList<>();
- for (CelType type :
- Arrays.asList(
- SimpleType.STRING,
- SimpleType.INT,
- SimpleType.UINT,
- SimpleType.DOUBLE,
- SimpleType.BYTES,
- SimpleType.BOOL)) {
- uniqueOverloads.add(
- CelFunctionBinding.from(
- String.format("unique_list_%s", type.name().toLowerCase(Locale.US)),
- List.class,
- CustomOverload::uniqueList));
- }
- return Collections.unmodifiableList(uniqueOverloads);
- }
-
- /**
- * Creates a custom binary function overload for the "startsWith" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "startsWith" operation.
- */
- private static CelFunctionBinding celStartsWithBytes() {
- return CelFunctionBinding.from(
- "starts_with_bytes",
- CelByteString.class,
- CelByteString.class,
- (receiver, param) -> {
- if (receiver.size() < param.size()) {
- return false;
- }
- byte[] paramBytes = param.toByteArray();
- byte[] receiverBytes = receiver.toByteArray();
- for (int i = 0; i < paramBytes.length; i++) {
- if (paramBytes[i] != receiverBytes[i]) {
- return false;
- }
- }
- return true;
- });
- }
-
- /**
- * Creates a custom binary function overload for the "endsWith" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "endsWith" operation.
- */
- private static CelFunctionBinding celEndsWithBytes() {
- return CelFunctionBinding.from(
- "ends_with_bytes",
- CelByteString.class,
- CelByteString.class,
- (receiver, param) -> {
- if (receiver.size() < param.size()) {
- return false;
- }
- byte[] paramBytes = param.toByteArray();
- byte[] receiverBytes = receiver.toByteArray();
- for (int i = 0; i < paramBytes.length; i++) {
- if (paramBytes[param.size() - i - 1] != receiverBytes[receiver.size() - i - 1]) {
- return false;
- }
- }
- return true;
- });
- }
-
- /**
- * Creates a custom binary function overload for the "contains" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "contains" operation.
- */
- private static CelFunctionBinding celContainsBytes() {
- return CelFunctionBinding.from(
- "contains_bytes",
- CelByteString.class,
- CelByteString.class,
- (receiver, param) -> bytesContains(receiver.toByteArray(), param.toByteArray()));
- }
-
- static boolean bytesContains(byte[] arr, byte[] subArr) {
- if (subArr.length == 0) {
- return true;
- }
- if (subArr.length > arr.length) {
- return false;
- }
- for (int i = 0; i < arr.length - subArr.length + 1; i++) {
- boolean found = true;
- for (int j = 0; j < subArr.length; j++) {
- if (arr[i + j] != subArr[j]) {
- found = false;
- break;
- }
- }
- if (found) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Creates a custom binary function overload for the "isHostname" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isHostname" operation.
- */
- private static CelFunctionBinding celIsHostname() {
- return CelFunctionBinding.from("is_hostname", String.class, CustomOverload::isHostname);
- }
-
- /**
- * Creates a custom unary function overload for the "isEmail" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isEmail" operation.
- */
- private static CelFunctionBinding celIsEmail() {
- return CelFunctionBinding.from("is_email", String.class, CustomOverload::isEmail);
- }
-
- /**
- * Creates a custom function overload for the "isIp" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isIp" operation.
- */
- private static CelFunctionBinding celIsIpUnary() {
- return CelFunctionBinding.from("is_ip_unary", String.class, value -> isIp(value, 0L));
- }
-
- /**
- * Creates a custom function overload for the "isIp" operation that also accepts a port.
- *
- * @return The {@link CelFunctionBinding} instance for the "isIp" operation.
- */
- private static CelFunctionBinding celIsIp() {
- return CelFunctionBinding.from("is_ip", String.class, Long.class, CustomOverload::isIp);
- }
-
- /**
- * Creates a custom function overload for the "isIpPrefix" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isIpPrefix" operation.
- */
- private static CelFunctionBinding celIsIpPrefix() {
- return CelFunctionBinding.from(
- "is_ip_prefix", String.class, prefix -> isIpPrefix(prefix, 0L, false));
- }
-
- /**
- * Creates a custom function overload for the "isIpPrefix" operation that accepts a version.
- *
- * @return The {@link CelFunctionBinding} instance for the "isIpPrefix" operation.
- */
- private static CelFunctionBinding celIsIpPrefixInt() {
- return CelFunctionBinding.from(
- "is_ip_prefix_int",
- String.class,
- Long.class,
- (prefix, version) -> isIpPrefix(prefix, version, false));
- }
-
- /**
- * Creates a custom function overload for the "isIpPrefix" operation that accepts a strict flag.
- *
- * @return The {@link CelFunctionBinding} instance for the "isIpPrefix" operation.
- */
- private static CelFunctionBinding celIsIpPrefixBool() {
- return CelFunctionBinding.from(
- "is_ip_prefix_bool",
- String.class,
- Boolean.class,
- (prefix, strict) -> isIpPrefix(prefix, 0L, strict));
- }
-
- /**
- * Creates a custom function overload for the "isIpPrefix" operation that accepts both version and
- * strict flag.
- *
- * @return The {@link CelFunctionBinding} instance for the "isIpPrefix" operation.
- */
- private static CelFunctionBinding celIsIpPrefixIntBool() {
- return CelFunctionBinding.from(
- "is_ip_prefix_int_bool",
- Arrays.asList(String.class, Long.class, Boolean.class),
- (args) -> isIpPrefix((String) args[0], (Long) args[1], (Boolean) args[2]));
- }
-
- /**
- * Creates a custom unary function overload for the "isUri" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isUri" operation.
- */
- private static CelFunctionBinding celIsUri() {
- return CelFunctionBinding.from("is_uri", String.class, CustomOverload::isUri);
- }
-
- /**
- * Creates a custom unary function overload for the "isUriRef" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isUriRef" operation.
- */
- private static CelFunctionBinding celIsUriRef() {
- return CelFunctionBinding.from("is_uri_ref", String.class, CustomOverload::isUriRef);
- }
-
- /**
- * Creates a custom unary function overload for the "isNan" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isNan" operation.
- */
- private static CelFunctionBinding celIsNan() {
- return CelFunctionBinding.from("is_nan", Double.class, value -> Double.isNaN(value));
- }
-
- /**
- * Creates a custom unary function overload for the "isInf" operation.
- *
- * @return The {@link CelFunctionBinding} instance for the "isInf" operation.
- */
- private static CelFunctionBinding celIsInfUnary() {
- return CelFunctionBinding.from("is_inf_unary", Double.class, value -> value.isInfinite());
- }
-
- /**
- * Creates a custom unary function overload for the "isInf" operation with sign option.
- *
- * @return The {@link CelFunctionBinding} instance for the "isInf" operation.
- */
- private static CelFunctionBinding celIsInfBinary() {
- return CelFunctionBinding.from(
- "is_inf_binary",
- Double.class,
- Long.class,
- (value, sign) -> {
- if (sign == 0) {
- return value.isInfinite();
- }
- double expectedValue = (sign > 0) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
- return value == expectedValue;
- });
- }
-
- private static CelFunctionBinding celIsHostAndPort() {
- return CelFunctionBinding.from(
- "string_bool_is_host_and_port_bool",
- String.class,
- Boolean.class,
- CustomOverload::isHostAndPort);
- }
-
- /** Caching replacement for CEL's global {@code matches(string, string)}. */
- @SuppressWarnings("Immutable")
- private static CelFunctionBinding celMatches(
- ConcurrentMap patternCache, CelOptions celOptions) {
- return CelFunctionBinding.from(
- "matches",
- String.class,
- String.class,
- (value, regex) -> matches(patternCache, celOptions, value, regex));
- }
-
- /** Caching replacement for CEL's member-style {@code string.matches(string)}. */
- @SuppressWarnings("Immutable")
- private static CelFunctionBinding celMatchesString(
- ConcurrentMap patternCache, CelOptions celOptions) {
- return CelFunctionBinding.from(
- "matches_string",
- String.class,
- String.class,
- (value, regex) -> matches(patternCache, celOptions, value, regex));
- }
-
- private static boolean matches(
- ConcurrentMap cache, CelOptions celOptions, String value, String regex)
- throws CelEvaluationException {
- Pattern pattern;
- try {
- pattern = cache.computeIfAbsent(regex, Pattern::compile);
- } catch (PatternSyntaxException e) {
- throw new CelEvaluationException("failed to compile regex: " + e.getMessage(), e);
- }
- Matcher matcher = pattern.matcher(value);
- return celOptions.enableRegexPartialMatch() ? matcher.find() : matcher.matches();
- }
-
- /**
- * Returns true if the string is a valid host/port pair, for example "example.com:8080".
- *
- *
If the argument portRequired is true, the port is required. If the argument is false, the
- * port is optional.
- *
- *
The host can be one of:
- *
- *
- *
An IPv4 address in dotted decimal format, for example {@code 192.168.0.1}.
- *
An IPv6 address enclosed in square brackets, for example {@code [::1]}.
- *
A hostname, for example {@code example.com}.
- *
- *
- *
The port is separated by a colon. It must be non-empty, with a decimal number in the range
- * of 0-65535, inclusive.
- */
- static boolean isHostAndPort(String str, boolean portRequired) {
- if (str.isEmpty()) {
- return false;
- }
-
- int splitIdx = str.lastIndexOf(':');
-
- if (str.charAt(0) == '[') {
- int end = str.lastIndexOf(']');
-
- int endPlus = end + 1;
- if (endPlus == str.length()) { // no port
- return !portRequired && isIp(str.substring(1, end), 6);
- } else if (endPlus == splitIdx) { // port
- return isIp(str.substring(1, end), 6) && isPort(str.substring(splitIdx + 1));
- }
- return false; // malformed
- }
-
- if (splitIdx < 0) {
- return !portRequired && (isHostname(str) || isIp(str, 4));
- }
-
- String host = str.substring(0, splitIdx);
- String port = str.substring(splitIdx + 1);
-
- return ((isHostname(host) || isIp(host, 4)) && isPort(port));
- }
-
- // Returns true if the string is a valid port for isHostAndPort.
- private static boolean isPort(String str) {
- if (str.isEmpty()) {
- return false;
- }
-
- if (str.length() > 1 && str.charAt(0) == '0') {
- return false;
- }
-
- for (int i = 0; i < str.length(); i++) {
- char c = str.charAt(i);
- if ('0' <= c && c <= '9') {
- continue;
- }
- return false;
- }
-
- try {
- int val = Integer.parseInt(str);
- return val <= 65535;
- } catch (NumberFormatException nfe) {
- return false;
- }
- }
-
- /**
- * Determines if the input list contains unique values. If the list contains duplicate values, it
- * returns {@code false}. If the list contains unique values, it returns {@code true}.
- *
- * @param list The input list to check for uniqueness.
- * @return {@code true} if the list contains unique scalar values, {@code false} otherwise.
- */
- static boolean uniqueList(List> list) {
- long size = list.size();
- if (size == 0) {
- return true;
- }
- Set