Skip to content

Commit a0ab196

Browse files
authored
Support repeated fields in MessageOneofEvaluator (#428)
1 parent e46fac3 commit a0ab196

4 files changed

Lines changed: 101 additions & 7 deletions

File tree

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,7 @@ public List<RuleViolation.Builder> evaluate(Value val, boolean failFast)
9999
if (message == null) {
100100
return RuleViolation.NO_VIOLATIONS;
101101
}
102-
boolean hasField;
103-
if (descriptor.isRepeated()) {
104-
hasField = message.getRepeatedFieldCount(descriptor) != 0;
105-
} else {
106-
hasField = message.hasField(descriptor);
107-
}
102+
boolean hasField = isFieldSet(message, descriptor);
108103
if (required && !hasField) {
109104
return Collections.singletonList(
110105
RuleViolation.newBuilder()
@@ -121,4 +116,15 @@ public List<RuleViolation.Builder> evaluate(Value val, boolean failFast)
121116
return valueEvaluator.evaluate(
122117
new ObjectValue(descriptor, message.getField(descriptor)), failFast);
123118
}
119+
120+
/**
121+
* Returns whether the given field is set on the message. Handles repeated and map fields, which
122+
* are not supported by {@link Message#hasField}.
123+
*/
124+
static boolean isFieldSet(Message message, FieldDescriptor field) {
125+
if (field.isRepeated()) {
126+
return message.getRepeatedFieldCount(field) != 0;
127+
}
128+
return message.hasField(field);
129+
}
124130
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public List<RuleViolation.Builder> evaluate(Value val, boolean failFast)
5151
}
5252
int hasCount = 0;
5353
for (FieldDescriptor field : fields) {
54-
if (msg.hasField(field)) {
54+
if (FieldEvaluator.isFieldSet(msg, field)) {
5555
hasCount++;
5656
}
5757
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023-2025 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;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
import build.buf.validate.Violation;
20+
import org.junit.jupiter.api.Test;
21+
22+
public class Issue427Test {
23+
@Test
24+
public void testMessageOneofWithNameOnly() throws Exception {
25+
com.example.imports.validationtest.Issue427 msg =
26+
com.example.imports.validationtest.Issue427.newBuilder().setName("foo").build();
27+
Validator validator = ValidatorFactory.newBuilder().build();
28+
assertThat(validator.validate(msg).toProto().getViolationsList()).isEmpty();
29+
}
30+
31+
@Test
32+
public void testMessageOneofWithTagsOnly() throws Exception {
33+
com.example.imports.validationtest.Issue427 msg =
34+
com.example.imports.validationtest.Issue427.newBuilder().addTags("a").addTags("b").build();
35+
Validator validator = ValidatorFactory.newBuilder().build();
36+
assertThat(validator.validate(msg).toProto().getViolationsList()).isEmpty();
37+
}
38+
39+
@Test
40+
public void testMessageOneofWithMappingsOnly() throws Exception {
41+
com.example.imports.validationtest.Issue427 msg =
42+
com.example.imports.validationtest.Issue427.newBuilder().putMappings("k", "v").build();
43+
Validator validator = ValidatorFactory.newBuilder().build();
44+
assertThat(validator.validate(msg).toProto().getViolationsList()).isEmpty();
45+
}
46+
47+
@Test
48+
public void testMessageOneofNoneSet() throws Exception {
49+
com.example.imports.validationtest.Issue427 msg =
50+
com.example.imports.validationtest.Issue427.getDefaultInstance();
51+
Validator validator = ValidatorFactory.newBuilder().build();
52+
assertThat(validator.validate(msg).toProto().getViolationsList())
53+
.containsExactly(
54+
Violation.newBuilder()
55+
.setRuleId("message.oneof")
56+
.setMessage("one of name, tags, mappings must be set")
57+
.build());
58+
}
59+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2023-2025 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+
syntax = "proto3";
16+
17+
package validationtest;
18+
19+
import "buf/validate/validate.proto";
20+
21+
message Issue427 {
22+
option (buf.validate.message).oneof = {
23+
fields: ["name", "tags", "mappings"]
24+
required: true
25+
};
26+
string name = 1;
27+
repeated string tags = 2;
28+
map<string, string> mappings = 3;
29+
}

0 commit comments

Comments
 (0)