Skip to content

Commit c91d8a0

Browse files
committed
introduce MessageLike to enable alternative protobuf runtimes
1 parent feda8bf commit c91d8a0

12 files changed

+135
-48
lines changed

src/main/java/build/buf/protovalidate/internal/evaluator/AnyEvaluator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ class AnyEvaluator implements Evaluator {
4545

4646
@Override
4747
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
48-
Message anyValue = val.messageValue();
48+
MessageLike anyValue = val.messageValue();
4949
if (anyValue == null) {
5050
return ValidationResult.EMPTY;
5151
}
5252
List<Violation> violationList = new ArrayList<>();
53-
String typeURL = (String) anyValue.getField(typeURLDescriptor);
53+
String typeURL = anyValue.getField(typeURLDescriptor).jvmValue(String.class);
5454
if (!in.isEmpty() && !in.contains(typeURL)) {
5555
Violation violation =
5656
Violation.newBuilder()

src/main/java/build/buf/protovalidate/internal/evaluator/EnumEvaluator.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,8 @@ public boolean tautology() {
6262
*/
6363
@Override
6464
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
65-
Descriptors.EnumValueDescriptor enumValue = val.value(Descriptors.EnumValueDescriptor.class);
66-
if (enumValue == null) {
67-
return ValidationResult.EMPTY;
68-
}
69-
if (!values.contains(enumValue.getNumber())) {
65+
Integer enumValue = val.jvmValue(Integer.class);
66+
if (!values.contains(enumValue)) {
7067
return new ValidationResult(
7168
Collections.singletonList(
7269
Violation.newBuilder()

src/main/java/build/buf/protovalidate/internal/evaluator/FieldEvaluator.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,17 @@ public boolean tautology() {
6868

6969
@Override
7070
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
71-
Message message = val.messageValue();
71+
MessageLike message = val.messageValue();
7272
if (message == null) {
7373
return ValidationResult.EMPTY;
7474
}
7575
boolean hasField;
7676
if (descriptor.isRepeated()) {
77-
hasField = message.getRepeatedFieldCount(descriptor) != 0;
77+
if (descriptor.isMapField()) {
78+
hasField = !message.getField(descriptor).mapValue().isEmpty();
79+
} else {
80+
hasField = !message.getField(descriptor).repeatedValue().isEmpty();
81+
}
7882
} else {
7983
hasField = message.hasField(descriptor);
8084
}
@@ -90,12 +94,11 @@ public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionEx
9094
if (ignoreEmpty && !hasField) {
9195
return ValidationResult.EMPTY;
9296
}
93-
Object fieldValue = message.getField(descriptor);
94-
if (ignoreDefault && Objects.equals(zero, fieldValue)) {
97+
Value fieldValue = message.getField(descriptor);
98+
if (ignoreDefault && Objects.equals(zero, fieldValue.jvmValue(Object.class))) {
9599
return ValidationResult.EMPTY;
96100
}
97-
ValidationResult evalResult =
98-
valueEvaluator.evaluate(new ObjectValue(descriptor, fieldValue), failFast);
101+
ValidationResult evalResult = valueEvaluator.evaluate(fieldValue, failFast);
99102
List<Violation> violations =
100103
ErrorPathUtils.prefixErrorPaths(evalResult.getViolations(), "%s", descriptor.getName());
101104
return new ValidationResult(violations);

src/main/java/build/buf/protovalidate/internal/evaluator/MapEvaluator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ private List<Violation> evalPairs(Value key, Value value, boolean failFast)
100100
violations.addAll(keyViolations);
101101
violations.addAll(valueViolations);
102102

103-
Object keyName = key.value(Object.class);
103+
Object keyName = key.jvmValue(Object.class);
104104
if (keyName == null) {
105105
return Collections.emptyList();
106106
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2023 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package build.buf.protovalidate.internal.evaluator;
16+
17+
import com.google.protobuf.Descriptors.FieldDescriptor;
18+
19+
public interface MessageLike {
20+
boolean hasField(FieldDescriptor field);
21+
22+
Value getField(FieldDescriptor field);
23+
}

src/main/java/build/buf/protovalidate/internal/evaluator/MessageValue.java

+14-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package build.buf.protovalidate.internal.evaluator;
1616

1717
import com.google.protobuf.Message;
18+
19+
import javax.annotation.Nullable;
1820
import java.util.Collections;
1921
import java.util.List;
2022
import java.util.Map;
@@ -25,26 +27,31 @@
2527
*/
2628
public final class MessageValue implements Value {
2729

28-
/** Object type since the object type is inferred from the field descriptor. */
29-
private final Object value;
30+
private final ProtobufMessageLike value;
3031

3132
/**
3233
* Constructs a {@link MessageValue} with the provided message value.
3334
*
3435
* @param value The message value.
3536
*/
3637
public MessageValue(Message value) {
37-
this.value = value;
38+
this.value = new ProtobufMessageLike(value);
39+
}
40+
41+
@Override
42+
public MessageLike messageValue() {
43+
return value;
3844
}
3945

4046
@Override
41-
public Message messageValue() {
42-
return (Message) value;
47+
@Nullable
48+
public <T> T jvmValue(Class<T> clazz) {
49+
return null;
4350
}
4451

4552
@Override
46-
public <T> T value(Class<T> clazz) {
47-
return clazz.cast(value);
53+
public Object celValue() {
54+
return value.getMessage();
4855
}
4956

5057
@Override

src/main/java/build/buf/protovalidate/internal/evaluator/ObjectValue.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,25 @@ public final class ObjectValue implements Value {
5050
this.value = value;
5151
}
5252

53-
@Nullable
5453
@Override
55-
public Message messageValue() {
56-
if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
57-
return (Message) value;
54+
public MessageLike messageValue() {
55+
return new ProtobufMessageLike((Message) value);
56+
}
57+
58+
@Override
59+
public <T> T jvmValue(Class<T> clazz) {
60+
if (value instanceof Descriptors.EnumValueDescriptor) {
61+
return clazz.cast(((Descriptors.EnumValueDescriptor) value).getNumber());
62+
} else {
63+
return clazz.cast(value);
5864
}
59-
return null;
6065
}
6166

6267
@Override
63-
public <T> T value(Class<T> clazz) {
68+
public Object celValue() {
69+
if (value instanceof Descriptors.EnumValueDescriptor) {
70+
return ((Descriptors.EnumValueDescriptor) value).getNumber();
71+
}
6472
Descriptors.FieldDescriptor.Type type = fieldDescriptor.getType();
6573
if (!fieldDescriptor.isRepeated()
6674
&& (type == Descriptors.FieldDescriptor.Type.UINT32
@@ -74,9 +82,9 @@ public <T> T value(Class<T> clazz) {
7482
* When using uint32/uint64 in your protobuf objects or CEL expressions in Java,
7583
* wrap them with the org.projectnessie.cel.common.ULong type.
7684
*/
77-
return clazz.cast(ULong.valueOf(((Number) value).longValue()));
85+
return ULong.valueOf(((Number) value).longValue());
7886
}
79-
return clazz.cast(value);
87+
return value;
8088
}
8189

8290
@Override

src/main/java/build/buf/protovalidate/internal/evaluator/OneofEvaluator.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,12 @@ public boolean tautology() {
4747

4848
@Override
4949
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
50-
Message message = val.messageValue();
50+
MessageLike message = val.messageValue();
5151
if (message == null) {
5252
return ValidationResult.EMPTY;
5353
}
54-
if (required && (message.getOneofFieldDescriptor(descriptor) == null)) {
54+
boolean hasField = descriptor.getFields().stream().anyMatch(message::hasField);
55+
if (required && !hasField) {
5556
return new ValidationResult(
5657
Collections.singletonList(
5758
Violation.newBuilder()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2023 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package build.buf.protovalidate.internal.evaluator;
16+
17+
import com.google.protobuf.Descriptors.FieldDescriptor;
18+
import com.google.protobuf.Message;
19+
20+
class ProtobufMessageLike implements MessageLike {
21+
private final Message message;
22+
23+
ProtobufMessageLike(Message message) {
24+
this.message = message;
25+
}
26+
27+
public Message getMessage() {
28+
return message;
29+
}
30+
31+
@Override
32+
public boolean hasField(FieldDescriptor field) {
33+
return message.hasField(field);
34+
}
35+
36+
@Override
37+
public Value getField(FieldDescriptor field) {
38+
return new ObjectValue(field, message.getField(field));
39+
}
40+
}

src/main/java/build/buf/protovalidate/internal/evaluator/Value.java

+22-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
package build.buf.protovalidate.internal.evaluator;
1616

17-
import com.google.protobuf.Message;
1817
import java.util.List;
1918
import java.util.Map;
2019
import javax.annotation.Nullable;
@@ -25,22 +24,13 @@
2524
*/
2625
public interface Value {
2726
/**
28-
* Get the underlying value as a {@link Message} type.
27+
* Get the underlying value as a {@link MessageLike} type.
2928
*
30-
* @return The underlying {@link Message} value. null if the underlying value is not a {@link
31-
* Message} type.
29+
* @return The underlying {@link MessageLike} value. null if the underlying value is not a {@link
30+
* MessageLike} type.
3231
*/
3332
@Nullable
34-
Message messageValue();
35-
36-
/**
37-
* Get the underlying value and cast it to the class type.
38-
*
39-
* @param clazz The inferred class.
40-
* @return The value casted to the inferred class type.
41-
* @param <T> The class type.
42-
*/
43-
<T> T value(Class<T> clazz);
33+
MessageLike messageValue();
4434

4535
/**
4636
* Get the underlying value as a list.
@@ -57,4 +47,22 @@ public interface Value {
5747
* list.
5848
*/
5949
Map<Value, Value> mapValue();
50+
51+
/**
52+
* Get the underlying value as it should be provided to CEL.
53+
*
54+
* @return The underlying value as a CEL-compatible type.
55+
*/
56+
Object celValue();
57+
58+
/**
59+
* Get the underlying value and cast it to the class type, which will be a type checkable
60+
* internally by protovalidate-java.
61+
*
62+
* @param clazz The inferred class.
63+
* @return The value cast to the inferred class type.
64+
* @param <T> The class type.
65+
*/
66+
@Nullable
67+
<T> T jvmValue(Class<T> clazz);
6068
}

src/main/java/build/buf/protovalidate/internal/evaluator/ValueEvaluator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public boolean tautology() {
4949

5050
@Override
5151
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
52-
if (this.shouldIgnore(val.value(Object.class))) {
52+
if (this.shouldIgnore(val.jvmValue(Object.class))) {
5353
return ValidationResult.EMPTY;
5454
}
5555
List<Violation> violations = new ArrayList<>();
@@ -82,7 +82,7 @@ public void setIgnoreEmpty(Object zero) {
8282
this.zero = zero;
8383
}
8484

85-
private boolean shouldIgnore(Object value) {
85+
private boolean shouldIgnore(@Nullable Object value) {
8686
return this.ignoreEmpty && Objects.equals(value, this.zero);
8787
}
8888
}

src/main/java/build/buf/protovalidate/internal/expression/CelPrograms.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public boolean tautology() {
4343

4444
@Override
4545
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
46-
Variable activation = Variable.newThisVariable(val.value(Object.class));
46+
Variable activation = Variable.newThisVariable(val.celValue());
4747
List<Violation> violationList = new ArrayList<>();
4848
for (CompiledProgram program : programs) {
4949
Violation violation = program.eval(activation);

0 commit comments

Comments
 (0)