Skip to content

Commit 8ada37d

Browse files
srikrsna-bufSteve Ayers
andauthored
Validate MessageOneofRule (#311)
Supports cases in bufbuild/protovalidate#379 --------- Signed-off-by: Sri Krishna <[email protected]> Co-authored-by: Steve Ayers <[email protected]>
1 parent 04eac4d commit 8ada37d

File tree

3 files changed

+25
-11
lines changed

3 files changed

+25
-11
lines changed

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 = v0.13.0
2+
protovalidate.version = v0.13.1
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: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
import java.util.ArrayList;
3838
import java.util.Collections;
3939
import java.util.HashMap;
40+
import java.util.LinkedHashSet;
4041
import java.util.List;
4142
import java.util.Map;
4243
import java.util.Objects;
44+
import java.util.Set;
4345
import org.jspecify.annotations.Nullable;
4446

4547
/** A build-through cache of message evaluators keyed off the provided descriptor. */
@@ -208,16 +210,26 @@ private void processMessageOneofRules(
208210
Descriptor desc, MessageRules msgRules, MessageEvaluator msgEval)
209211
throws CompilationException {
210212
for (MessageOneofRule rule : msgRules.getOneofList()) {
211-
List<FieldDescriptor> fields = new ArrayList<>(rule.getFieldsCount());
213+
if (rule.getFieldsCount() == 0) {
214+
throw new CompilationException(
215+
String.format(
216+
"at least one field must be specified in oneof rule for the message %s",
217+
desc.getFullName()));
218+
}
219+
Set<FieldDescriptor> fields = new LinkedHashSet<>(rule.getFieldsCount());
212220
for (String name : rule.getFieldsList()) {
213221
FieldDescriptor field = desc.findFieldByName(name);
214222
if (field == null) {
215223
throw new CompilationException(
216-
String.format("field \"%s\" not found in %s", name, desc.getFullName()));
224+
String.format("field %s not found in %s", name, desc.getFullName()));
225+
}
226+
if (!fields.add(field)) {
227+
throw new CompilationException(
228+
String.format(
229+
"duplicate %s in oneof rule for the message %s", name, desc.getFullName()));
217230
}
218-
fields.add(field);
219231
}
220-
msgEval.append(new MessageOneofEvaluator(fields, rule.getRequired()));
232+
msgEval.append(new MessageOneofEvaluator(new ArrayList<>(fields), rule.getRequired()));
221233
}
222234
}
223235

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,18 @@ message MessageRules {
150150
// fields have explicit presence. This means that, for the purpose of determining
151151
// how many fields are set, explicitly setting such a field to its zero value is
152152
// effectively the same as not setting it at all.
153-
// 3. This will generate validation errors when unmarshalling, even from the binary
154-
// format. With a Protobuf oneof, if multiple fields are present in the serialized
155-
// form, earlier values are usually silently ignored when unmarshalling, with only
156-
// the last field being present when unmarshalling completes.
153+
// 3. This will always generate validation errors for a message unmarshalled from
154+
// serialized data that sets more than one field. With a Protobuf oneof, when
155+
// multiple fields are present in the serialized form, earlier values are usually
156+
// silently ignored when unmarshalling, with only the last field being set when
157+
// unmarshalling completes.
157158
//
158159
//
159160
// ```proto
160161
// message MyMessage {
161162
// // Only one of `field1` or `field2` _can_ be present in this message.
162163
// option (buf.validate.message).oneof = { fields: ["field1", "field2"] };
163-
// // Only one of `field3` or `field4` _must_ be present in this message.
164+
// // Exactly one of `field3` or `field4` _must_ be present in this message.
164165
// option (buf.validate.message).oneof = { fields: ["field3", "field4"], required: true };
165166
// string field1 = 1;
166167
// bytes field2 = 2;
@@ -173,7 +174,8 @@ message MessageRules {
173174

174175
message MessageOneofRule {
175176
// A list of field names to include in the oneof. All field names must be
176-
// defined in the message.
177+
// defined in the message. At least one field must be specified, and
178+
// duplicates are not permitted.
177179
repeated string fields = 1;
178180
// If true, one of the fields specified _must_ be set.
179181
optional bool required = 2;

0 commit comments

Comments
 (0)