Skip to content

Upgrade to Protovalidate v0.11.0 #273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Conformance](https://github.com/bufbuild/protovalidate-java/actions/workflows/conformance.yaml/badge.svg)](https://github.com/bufbuild/protovalidate-java/actions/workflows/conformance.yaml)
[![BSR](https://img.shields.io/badge/BSR-Module-0C65EC)][buf-mod]

[Protovalidate][protovalidate] provides standard annotations to validate common constraints on messages and fields, as well as the ability to use [CEL][cel] to write custom constraints. It's the next generation of [protoc-gen-validate][protoc-gen-validate], the only widely used validation library for Protobuf.
[Protovalidate][protovalidate] 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], the only widely used validation library for Protobuf.

With Protovalidate, you can annotate your Protobuf messages with both standard and custom validation rules:

Expand Down
132 changes: 31 additions & 101 deletions conformance/expected-failures.yaml
Original file line number Diff line number Diff line change
@@ -1,109 +1,39 @@
custom_constraints:
custom_rules:
- field_expression/map/enum/invalid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionMapEnum]:{val:{key:4 value:ENUM_ONE} val:{key:8 value:ENUM_UNSPECIFIED} val:{key:12 value:ENUM_ONE}}
# want: validation error (1 violation)
# 1. constraint_id: "field_expression.map.enum"
# message: "test message field_expression.map.enum"
# field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_MESSAGE}
# rule: "cel[0]" elements:{field_number:23 field_name:"cel" field_type:TYPE_MESSAGE index:0}
# got: compilation err: Failed to compile expression field_expression.map.enum:
#ERROR: <input>:1:1: expression of type 'buf.validate.conformance.cases.custom_constraints.FieldExpressionMapEnum.ValEntry' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(k, this[k] == 1)
# | ^
#ERROR: <input>:1:17: found no matching overload for '_[_]' applied to '(buf.validate.conformance.cases.custom_constraints.FieldExpressionMapEnum.ValEntry, !error!)'
# | this.all(k, this[k] == 1)
# | ................^
- field_expression/map/enum/valid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionMapEnum]:{val:{key:4 value:ENUM_ONE} val:{key:8 value:ENUM_ONE}}
# want: valid
# got: compilation err: Failed to compile expression field_expression.map.enum:
#ERROR: <input>:1:1: expression of type 'buf.validate.conformance.cases.custom_constraints.FieldExpressionMapEnum.ValEntry' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(k, this[k] == 1)
# | ^
#ERROR: <input>:1:17: found no matching overload for '_[_]' applied to '(buf.validate.conformance.cases.custom_constraints.FieldExpressionMapEnum.ValEntry, !error!)'
# | this.all(k, this[k] == 1)
# | ................^
- field_expression/map/message/invalid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionMapMessage]:{val:{key:4 value:{a:1}} val:{key:8 value:{a:2}} val:{key:12 value:{a:1}}}
# want: validation error (1 violation)
# 1. constraint_id: "field_expression.map.message"
# message: "test message field_expression.map.message"
# field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_MESSAGE}
# rule: "cel[0]" elements:{field_number:23 field_name:"cel" field_type:TYPE_MESSAGE index:0}
# got: compilation err: Failed to compile expression field_expression.map.message:
#ERROR: <input>:1:1: expression of type 'buf.validate.conformance.cases.custom_constraints.FieldExpressionMapMessage.ValEntry' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(k, this[k].a == 1)
# | ^
#ERROR: <input>:1:17: found no matching overload for '_[_]' applied to '(buf.validate.conformance.cases.custom_constraints.FieldExpressionMapMessage.ValEntry, !error!)'
# | this.all(k, this[k].a == 1)
# | ................^
- field_expression/map/message/valid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionMapMessage]:{val:{key:4 value:{a:1}} val:{key:8 value:{a:1}}}
# want: valid
# got: compilation err: Failed to compile expression field_expression.map.message:
#ERROR: <input>:1:1: expression of type 'buf.validate.conformance.cases.custom_constraints.FieldExpressionMapMessage.ValEntry' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(k, this[k].a == 1)
# | ^
#ERROR: <input>:1:17: found no matching overload for '_[_]' applied to '(buf.validate.conformance.cases.custom_constraints.FieldExpressionMapMessage.ValEntry, !error!)'
# | this.all(k, this[k].a == 1)
# | ................^
- field_expression/map/scalar/invalid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionMapScalar]:{val:{key:1 value:1} val:{key:2 value:2} val:{key:3 value:1}}
# want: validation error (1 violation)
# 1. constraint_id: "field_expression.map.scalar"
# message: "test message field_expression.map.scalar"
# field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_MESSAGE}
# rule: "cel[0]" elements:{field_number:23 field_name:"cel" field_type:TYPE_MESSAGE index:0}
# got: compilation err: Failed to compile expression field_expression.map.scalar:
#ERROR: <input>:1:1: expression of type 'buf.validate.conformance.cases.custom_constraints.FieldExpressionMapScalar.ValEntry' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(k, this[k] == 1)
# | ^
#ERROR: <input>:1:17: found no matching overload for '_[_]' applied to '(buf.validate.conformance.cases.custom_constraints.FieldExpressionMapScalar.ValEntry, !error!)'
# | this.all(k, this[k] == 1)
# | ................^
- field_expression/map/scalar/valid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionMapScalar]:{val:{key:1 value:1}}
# want: valid
# got: compilation err: Failed to compile expression field_expression.map.scalar:
#ERROR: <input>:1:1: expression of type 'buf.validate.conformance.cases.custom_constraints.FieldExpressionMapScalar.ValEntry' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(k, this[k] == 1)
# | ^
#ERROR: <input>:1:17: found no matching overload for '_[_]' applied to '(buf.validate.conformance.cases.custom_constraints.FieldExpressionMapScalar.ValEntry, !error!)'
# | this.all(k, this[k] == 1)
# | ................^
- field_expression/repeated/enum/invalid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionRepeatedEnum]:{val:ENUM_ONE val:ENUM_UNSPECIFIED val:ENUM_ONE}
# want: validation error (1 violation)
# 1. constraint_id: "field_expression.repeated.enum"
# message: "test message field_expression.repeated.enum"
# field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_ENUM}
# rule: "cel[0]" elements:{field_number:23 field_name:"cel" field_type:TYPE_MESSAGE index:0}
# got: compilation err: Failed to compile expression field_expression.repeated.enum:
#ERROR: <input>:1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(e, e == 1)
# | ^
- field_expression/repeated/enum/valid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionRepeatedEnum]:{val:ENUM_ONE val:ENUM_ONE}
# want: valid
# got: compilation err: Failed to compile expression field_expression.repeated.enum:
#ERROR: <input>:1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(e, e == 1)
# | ^
- field_expression/repeated/scalar/invalid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionRepeatedScalar]:{val:1 val:2 val:1}
# want: validation error (1 violation)
# 1. constraint_id: "field_expression.repeated.scalar"
# message: "test message field_expression.repeated.scalar"
# field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_INT32}
# rule: "cel[0]" elements:{field_number:23 field_name:"cel" field_type:TYPE_MESSAGE index:0}
# got: compilation err: Failed to compile expression field_expression.repeated.scalar:
#ERROR: <input>:1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(e, e == 1)
# | ^
- field_expression/repeated/scalar/valid
# input: [type.googleapis.com/buf.validate.conformance.cases.custom_constraints.FieldExpressionRepeatedScalar]:{val:1 val:1}
# want: valid
# got: compilation err: Failed to compile expression field_expression.repeated.scalar:
#ERROR: <input>:1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic)
# | this.all(e, e == 1)
# | ^
- field_expression/map/bool/valid
- field_expression/map/bool/invalid
- field_expression/map/string/valid
- field_expression/map/string/invalid
- field_expression/map/int32/valid
- field_expression/map/int32/invalid
- field_expression/map/uint32/valid
- field_expression/map/uint32/invalid
- field_expression/map/int64/valid
- field_expression/map/int64/invalid
- field_expression/map/uint64/valid
- field_expression/map/uint64/invalid
kitchen_sink:
- field/embedded/invalid
- field/transitive/invalid
- many/all-non-message-fields/invalid
- field/invalid
standard_rules/repeated:
- items/in/invalid
- items/not_in/invalid
library/is_ip:
- version/6/invalid/ipv6/7h16_double_colon_1h16
- version/6/invalid/ipv6/7h16_double_colon
- version/6/invalid/ipv6/double_colon_8h16
- version/6/invalid/ipv6/1h16_double_colon_7h16
standard_rules/well_known_types/duration:
- in/invalid
- not in/invalid


Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
import build.buf.validate.conformance.cases.TimestampConst;
import build.buf.validate.conformance.cases.TimestampWithin;
import build.buf.validate.conformance.cases.WrapperDouble;
import build.buf.validate.conformance.cases.custom_constraints.DynRuntimeError;
import build.buf.validate.conformance.cases.custom_constraints.FieldExpressionMultipleScalar;
import build.buf.validate.conformance.cases.custom_constraints.FieldExpressionNestedScalar;
import build.buf.validate.conformance.cases.custom_constraints.FieldExpressionScalar;
import build.buf.validate.conformance.cases.custom_rules.DynRuntimeError;
import build.buf.validate.conformance.cases.custom_rules.FieldExpressionMultipleScalar;
import build.buf.validate.conformance.cases.custom_rules.FieldExpressionNestedScalar;
import build.buf.validate.conformance.cases.custom_rules.FieldExpressionScalar;
import com.google.protobuf.ByteString;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.Duration;
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Version of buf.build/bufbuild/protovalidate to use.
protovalidate.version = v0.10.4
protovalidate.version = v0.11.0

# Arguments to the protovalidate-conformance CLI
protovalidate.conformance.args = --strict_message --strict_error --expected_failures=expected-failures.yaml
Expand Down
34 changes: 17 additions & 17 deletions src/main/java/build/buf/protovalidate/AnyEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import build.buf.protovalidate.exceptions.ExecutionException;
import build.buf.validate.AnyRules;
import build.buf.validate.FieldConstraints;
import build.buf.validate.FieldPath;
import build.buf.validate.FieldRules;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import java.util.ArrayList;
Expand All @@ -33,15 +33,15 @@
* runtime.
*/
class AnyEvaluator implements Evaluator {
private final ConstraintViolationHelper helper;
private final RuleViolationHelper helper;
private final Descriptors.FieldDescriptor typeURLDescriptor;
private final Set<String> in;
private final List<String> inValue;
private final Set<String> notIn;
private final List<String> notInValue;

private static final Descriptors.FieldDescriptor ANY_DESCRIPTOR =
FieldConstraints.getDescriptor().findFieldByNumber(FieldConstraints.ANY_FIELD_NUMBER);
FieldRules.getDescriptor().findFieldByNumber(FieldRules.ANY_FIELD_NUMBER);

private static final Descriptors.FieldDescriptor IN_DESCRIPTOR =
AnyRules.getDescriptor().findFieldByNumber(AnyRules.IN_FIELD_NUMBER);
Expand All @@ -67,7 +67,7 @@ class AnyEvaluator implements Evaluator {
Descriptors.FieldDescriptor typeURLDescriptor,
List<String> in,
List<String> notIn) {
this.helper = new ConstraintViolationHelper(valueEvaluator);
this.helper = new RuleViolationHelper(valueEvaluator);
this.typeURLDescriptor = typeURLDescriptor;
this.in = stringsToSet(in);
this.inValue = in;
Expand All @@ -76,39 +76,39 @@ class AnyEvaluator implements Evaluator {
}

@Override
public List<ConstraintViolation.Builder> evaluate(Value val, boolean failFast)
public List<RuleViolation.Builder> evaluate(Value val, boolean failFast)
throws ExecutionException {
Message anyValue = val.messageValue();
if (anyValue == null) {
return ConstraintViolation.NO_VIOLATIONS;
return RuleViolation.NO_VIOLATIONS;
}
List<ConstraintViolation.Builder> violationList = new ArrayList<>();
List<RuleViolation.Builder> violationList = new ArrayList<>();
String typeURL = (String) anyValue.getField(typeURLDescriptor);
if (!in.isEmpty() && !in.contains(typeURL)) {
ConstraintViolation.Builder violation =
ConstraintViolation.newBuilder()
RuleViolation.Builder violation =
RuleViolation.newBuilder()
.addAllRulePathElements(helper.getRulePrefixElements())
.addAllRulePathElements(IN_RULE_PATH.getElementsList())
.addFirstFieldPathElement(helper.getFieldPathElement())
.setConstraintId("any.in")
.setRuleId("any.in")
.setMessage("type URL must be in the allow list")
.setFieldValue(new ConstraintViolation.FieldValue(val))
.setRuleValue(new ConstraintViolation.FieldValue(this.inValue, IN_DESCRIPTOR));
.setFieldValue(new RuleViolation.FieldValue(val))
.setRuleValue(new RuleViolation.FieldValue(this.inValue, IN_DESCRIPTOR));
violationList.add(violation);
if (failFast) {
return violationList;
}
}
if (!notIn.isEmpty() && notIn.contains(typeURL)) {
ConstraintViolation.Builder violation =
ConstraintViolation.newBuilder()
RuleViolation.Builder violation =
RuleViolation.newBuilder()
.addAllRulePathElements(helper.getRulePrefixElements())
.addAllRulePathElements(NOT_IN_RULE_PATH.getElementsList())
.addFirstFieldPathElement(helper.getFieldPathElement())
.setConstraintId("any.not_in")
.setRuleId("any.not_in")
.setMessage("type URL must not be in the block list")
.setFieldValue(new ConstraintViolation.FieldValue(val))
.setRuleValue(new ConstraintViolation.FieldValue(this.notInValue, NOT_IN_DESCRIPTOR));
.setFieldValue(new RuleViolation.FieldValue(val))
.setRuleValue(new RuleViolation.FieldValue(this.notInValue, NOT_IN_DESCRIPTOR));
violationList.add(violation);
}
return violationList;
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/build/buf/protovalidate/CelPrograms.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

/** Evaluator that executes a {@link CompiledProgram}. */
class CelPrograms implements Evaluator {
private final ConstraintViolationHelper helper;
private final RuleViolationHelper helper;

/** A list of {@link CompiledProgram} that will be executed against the input message. */
private final List<CompiledProgram> programs;
Expand All @@ -32,7 +32,7 @@ class CelPrograms implements Evaluator {
* @param compiledPrograms The programs to execute.
*/
CelPrograms(@Nullable ValueEvaluator valueEvaluator, List<CompiledProgram> compiledPrograms) {
this.helper = new ConstraintViolationHelper(valueEvaluator);
this.helper = new RuleViolationHelper(valueEvaluator);
this.programs = compiledPrograms;
}

Expand All @@ -42,12 +42,12 @@ public boolean tautology() {
}

@Override
public List<ConstraintViolation.Builder> evaluate(Value val, boolean failFast)
public List<RuleViolation.Builder> evaluate(Value val, boolean failFast)
throws ExecutionException {
Variable activation = Variable.newThisVariable(val.value(Object.class));
List<ConstraintViolation.Builder> violations = new ArrayList<>();
List<RuleViolation.Builder> violations = new ArrayList<>();
for (CompiledProgram program : programs) {
ConstraintViolation.Builder violation = program.eval(val, activation);
RuleViolation.Builder violation = program.eval(val, activation);
if (violation != null) {
violations.add(violation);
if (failFast) {
Expand Down
Loading