Skip to content

Commit 888a3a2

Browse files
committed
Support cel_expression
Signed-off-by: Sri Krishna <[email protected]>
1 parent 17e130d commit 888a3a2

File tree

5 files changed

+72
-12
lines changed

5 files changed

+72
-12
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ tasks.register<Exec>("exportProtovalidateModule") {
172172
commandLine(
173173
buf.asPath,
174174
"export",
175-
"buf.build/bufbuild/protovalidate:${project.findProperty("protovalidate.version")}",
175+
"https://github.com/bufbuild/protovalidate.git#subdir=proto/protovalidate,ref=${project.findProperty("protovalidate.version")}",
176176
"--output",
177177
"src/main/resources",
178178
)

conformance/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ tasks.register<Exec>("generateConformance") {
7070
"generate",
7171
"--template",
7272
"${layout.buildDirectory.get()}/buf-gen-templates/buf.gen.yaml",
73-
"buf.build/bufbuild/protovalidate-testing:${project.findProperty("protovalidate.version")}",
73+
"https://github.com/bufbuild/protovalidate.git#subdir=proto/protovalidate-testing,ref=${project.findProperty(
74+
"protovalidate.version",
75+
)}",
7476
)
7577
}
7678

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Version of buf.build/bufbuild/protovalidate to use.
2-
protovalidate.version = v1.0.0
2+
protovalidate.version = 774f3764e09fcfc921b3ef5a42271754f0b7063a
33

44
# Arguments to the protovalidate-conformance CLI
55
protovalidate.conformance.args = --strict_message --strict_error --expected_failures=expected-failures.yaml

src/main/java/build/buf/protovalidate/EvaluatorBuilder.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import java.util.Map;
4343
import java.util.Objects;
4444
import java.util.Set;
45+
import java.util.stream.Collectors;
46+
import java.util.stream.Stream;
4547
import org.jspecify.annotations.Nullable;
4648

4749
/** A build-through cache of message evaluators keyed off the provided descriptor. */
@@ -50,6 +52,10 @@ final class EvaluatorBuilder {
5052
FieldPathUtils.fieldPathElement(
5153
FieldRules.getDescriptor().findFieldByNumber(FieldRules.CEL_FIELD_NUMBER));
5254

55+
private static final FieldPathElement CEL_EXPRESSION_FIELD_PATH_ELEMENT =
56+
FieldPathUtils.fieldPathElement(
57+
FieldRules.getDescriptor().findFieldByNumber(FieldRules.CEL_EXPRESSION_FIELD_NUMBER));
58+
5359
private volatile Map<Descriptor, MessageEvaluator> evaluatorCache = Collections.emptyMap();
5460

5561
private final Cel cel;
@@ -187,7 +193,11 @@ private void buildMessage(Descriptor desc, MessageEvaluator msgEval)
187193
private void processMessageExpressions(
188194
Descriptor desc, MessageRules msgRules, MessageEvaluator msgEval, DynamicMessage message)
189195
throws CompilationException {
190-
List<Rule> celList = msgRules.getCelList();
196+
List<Rule> celList =
197+
Stream.concat(
198+
expressionsToRules(msgRules.getCelExpressionList()).stream(),
199+
msgRules.getCelList().stream())
200+
.collect(Collectors.toList());
191201
if (celList.isEmpty()) {
192202
return;
193203
}
@@ -196,7 +206,7 @@ private void processMessageExpressions(
196206
.addMessageTypes(message.getDescriptorForType())
197207
.addVar(Variable.THIS_NAME, StructTypeReference.create(desc.getFullName()))
198208
.build();
199-
List<CompiledProgram> compiledPrograms = compileRules(celList, finalCel, false);
209+
List<CompiledProgram> compiledPrograms = compileRules(celList, finalCel, null);
200210
if (compiledPrograms.isEmpty()) {
201211
throw new CompilationException("compile returned null");
202212
}
@@ -354,7 +364,8 @@ private void processFieldExpressions(
354364
FieldDescriptor fieldDescriptor, FieldRules fieldRules, ValueEvaluator valueEvaluatorEval)
355365
throws CompilationException {
356366
List<Rule> rulesCelList = fieldRules.getCelList();
357-
if (rulesCelList.isEmpty()) {
367+
List<String> exprList = fieldRules.getCelExpressionList();
368+
if (rulesCelList.isEmpty() && exprList.isEmpty()) {
358369
return;
359370
}
360371
CelBuilder builder = cel.toCelBuilder();
@@ -367,7 +378,16 @@ private void processFieldExpressions(
367378
builder = builder.addMessageTypes(fieldDescriptor.getMessageType());
368379
}
369380
Cel finalCel = builder.build();
370-
List<CompiledProgram> compiledPrograms = compileRules(rulesCelList, finalCel, true);
381+
List<CompiledProgram> compiledPrograms = new ArrayList<>();
382+
if (!rulesCelList.isEmpty()) {
383+
compiledPrograms.addAll(compileRules(rulesCelList, finalCel, CEL_FIELD_PATH_ELEMENT));
384+
}
385+
if (!exprList.isEmpty()) {
386+
compiledPrograms.addAll(
387+
compileRules(
388+
expressionsToRules(exprList), finalCel, CEL_EXPRESSION_FIELD_PATH_ELEMENT));
389+
}
390+
371391
if (!compiledPrograms.isEmpty()) {
372392
valueEvaluatorEval.append(new CelPrograms(valueEvaluatorEval, compiledPrograms));
373393
}
@@ -510,19 +530,18 @@ private void processRepeatedRules(
510530
valueEvaluatorEval.append(listEval);
511531
}
512532

513-
private static List<CompiledProgram> compileRules(List<Rule> rules, Cel cel, boolean isField)
533+
private static List<CompiledProgram> compileRules(
534+
List<Rule> rules, Cel cel, @Nullable FieldPathElement fieldPathElement)
514535
throws CompilationException {
515536
List<Expression> expressions = Expression.fromRules(rules);
516537
List<CompiledProgram> compiledPrograms = new ArrayList<>();
517538
for (int i = 0; i < expressions.size(); i++) {
518539
Expression expression = expressions.get(i);
519540
AstExpression astExpression = AstExpression.newAstExpression(cel, expression);
520541
@Nullable FieldPath rulePath = null;
521-
if (isField) {
542+
if (fieldPathElement != null) {
522543
rulePath =
523-
FieldPath.newBuilder()
524-
.addElements(CEL_FIELD_PATH_ELEMENT.toBuilder().setIndex(i))
525-
.build();
544+
FieldPath.newBuilder().addElements(fieldPathElement.toBuilder().setIndex(i)).build();
526545
}
527546
try {
528547
compiledPrograms.add(
@@ -538,5 +557,11 @@ private static List<CompiledProgram> compileRules(List<Rule> rules, Cel cel, boo
538557
}
539558
return compiledPrograms;
540559
}
560+
561+
private static List<Rule> expressionsToRules(List<String> expressions) {
562+
return expressions.stream()
563+
.map(expr -> Rule.newBuilder().setExpression(expr).build())
564+
.collect(Collectors.toList());
565+
}
541566
}
542567
}

src/main/resources/buf/validate/validate.proto

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,24 @@ message Rule {
109109
// MessageRules represents validation rules that are applied to the entire message.
110110
// It includes disabling options and a list of Rule messages representing Common Expression Language (CEL) validation rules.
111111
message MessageRules {
112+
// `cel_expression` is a repeated field CEL expressions. Each expression specifies a validation
113+
// rule to be applied to this message. These rules are written in Common Expression Language (CEL) syntax.
114+
//
115+
// This is a simplified form of the `cel` Rule field, where only `expression` is set. This allows for
116+
// simpler syntax when defining CEL Rules where `id` and `message` are largely redundant.
117+
//
118+
// For more information, [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).
119+
//
120+
// ```proto
121+
// message MyMessage {
122+
// // The field `foo` must be greater than 42.
123+
// option (buf.validate.message).cel_expression = "this.foo > 42";
124+
// // The field `foo` must be less than 84.
125+
// option (buf.validate.message).cel_expression = "this.foo < 84";
126+
// optional int32 foo = 1;
127+
// }
128+
// ```
129+
repeated string cel_expression = 5;
112130
// `cel` is a repeated field of type Rule. Each Rule specifies a validation rule to be applied to this message.
113131
// These rules are written in Common Expression Language (CEL) syntax. For more information,
114132
// [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).
@@ -201,6 +219,21 @@ message OneofRules {
201219
// FieldRules encapsulates the rules for each type of field. Depending on
202220
// the field, the correct set should be used to ensure proper validations.
203221
message FieldRules {
222+
// `cel_expression` is a repeated field CEL expressions. Each expression specifies a validation
223+
// rule to be applied to this message. These rules are written in Common Expression Language (CEL) syntax.
224+
//
225+
// This is a simplified form of the `cel` Rule field, where only `expression` is set. This allows for
226+
// simpler syntax when defining CEL Rules where `id` and `message` are largely redundant.
227+
//
228+
// For more information, [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).
229+
//
230+
// ```proto
231+
// message MyMessage {
232+
// // The field `value` must be greater than 42.
233+
// optional int32 value = 1 [(buf.validate.field).cel_expression = "this > 42"];
234+
// }
235+
// ```
236+
repeated string cel_expression = 28;
204237
// `cel` is a repeated field used to represent a textual expression
205238
// in the Common Expression Language (CEL) syntax. For more information,
206239
// [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).

0 commit comments

Comments
 (0)