Copybara import of the project:

--
3eac250226 by Josh Humphries <jhumphries@buf.build>:

add check for custom JSON name conflicts
- also, include check for default JSON name conflicts even in proto2
  files (but only warn)
- if custom JSON name conflicts with other default name, only a
  warning in proto2

--
b23b387169 by Josh Humphries <jhumphries@buf.build>:

update existing test expectations and add new tests

--
aa34e0ed2f by Josh Humphries <jhumphries@buf.build>:

JSON -> Json

--
fdaa464962 by Josh Humphries <jhumphries@buf.build>:

address review feedback wrt absl string functions
also moves helpers into anonymous namespace

--
6d5f2fc93f by Josh Humphries <jhumphries@buf.build>:

apply clang-format changes; change really long pair type to auto

--
81b5cbe26e by Josh Humphries <jhumphries@buf.build>:

address review feedback

--
8fa8b10e09 by Josh Humphries <jhumphries@buf.build>:

return struct instead of using out param

--
b405717f88 by Josh Humphries <jhumphries@buf.build>:

address review feedback

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/10750 from jhump:jh/custom-json-name-validation b405717f88
PiperOrigin-RevId: 496041006
pull/11352/head
Joshua Humphries 2 years ago committed by Copybara-Service
parent 10f869a63b
commit 535069ec1b
  1. 119
      src/google/protobuf/compiler/parser_unittest.cc
  2. 186
      src/google/protobuf/descriptor.cc
  3. 158
      src/google/protobuf/descriptor_unittest.cc

@ -2073,11 +2073,124 @@ TEST_F(ParserValidationErrorTest, Proto3JsonConflictError) {
ExpectHasValidationErrors(
"syntax = 'proto3';\n"
"message TestMessage {\n"
" uint32 foo = 1;\n"
" uint32 _foo = 1;\n"
" uint32 Foo = 2;\n"
"}\n",
"3:9: The JSON camel-case name of field \"Foo\" conflicts with field "
"\"foo\". This is not allowed in proto3.\n");
"3:9: The default JSON name of field \"Foo\" (\"Foo\") conflicts "
"with the default JSON name of field \"_foo\".\n");
}
TEST_F(ParserValidationErrorTest, Proto2JsonConflictError) {
ExpectParsesTo(
"syntax = 'proto2';\n"
"message TestMessage {\n"
" optional uint32 _foo = 1;\n"
" optional uint32 Foo = 2;\n"
"}\n",
"syntax: 'proto2'\n"
"message_type {\n"
" name: 'TestMessage'\n"
" field {\n"
" label: LABEL_OPTIONAL type: TYPE_UINT32 name: '_foo' number: 1\n"
" }\n"
" field {\n"
" label: LABEL_OPTIONAL type: TYPE_UINT32 name: 'Foo' number: 2\n"
" }\n"
"}\n");
}
TEST_F(ParserValidationErrorTest, Proto3CustomJsonConflictWithDefaultError) {
ExpectHasValidationErrors(
"syntax = 'proto3';\n"
"message TestMessage {\n"
" uint32 foo = 1 [json_name='bar'];\n"
" uint32 bar = 2;\n"
"}\n",
"3:9: The default JSON name of field \"bar\" (\"bar\") conflicts "
"with the custom JSON name of field \"foo\".\n");
}
TEST_F(ParserValidationErrorTest, Proto2CustomJsonConflictWithDefaultError) {
ExpectParsesTo(
"syntax = 'proto2';\n"
"message TestMessage {\n"
" optional uint32 foo = 1 [json_name='bar'];\n"
" optional uint32 bar = 2;\n"
"}\n",
"syntax: 'proto2'\n"
"message_type {\n"
" name: 'TestMessage'\n"
" field {\n"
" label: LABEL_OPTIONAL type: TYPE_UINT32 name: 'foo' number: 1 "
"json_name: 'bar'\n"
" }\n"
" field {\n"
" label: LABEL_OPTIONAL type: TYPE_UINT32 name: 'bar' number: 2\n"
" }\n"
"}\n");
}
TEST_F(ParserValidationErrorTest, Proto3CustomJsonConflictError) {
ExpectHasValidationErrors(
"syntax = 'proto3';\n"
"message TestMessage {\n"
" uint32 foo = 1 [json_name='baz'];\n"
" uint32 bar = 2 [json_name='baz'];\n"
"}\n",
"3:9: The custom JSON name of field \"bar\" (\"baz\") conflicts "
"with the custom JSON name of field \"foo\".\n");
}
TEST_F(ParserValidationErrorTest, Proto2CustomJsonConflictError) {
ExpectHasValidationErrors(
"syntax = 'proto2';\n"
"message TestMessage {\n"
" optional uint32 foo = 1 [json_name='baz'];\n"
" optional uint32 bar = 2 [json_name='baz'];\n"
"}\n",
"3:18: The custom JSON name of field \"bar\" (\"baz\") conflicts "
"with the custom JSON name of field \"foo\".\n");
}
TEST_F(ParserValidationErrorTest, Proto3JsonConflictLegacy) {
ExpectHasValidationErrors(
"syntax = 'proto3';\n"
"message TestMessage {\n"
" option deprecated_legacy_json_field_conflicts = true;\n"
" uint32 fooBar = 1;\n"
" uint32 foo_bar = 2;\n"
"}\n",
"4:9: The default JSON name of field \"foo_bar\" (\"fooBar\") conflicts "
"with the default JSON name of field \"fooBar\".\n");
}
TEST_F(ParserValidationErrorTest, Proto2JsonConflictLegacy) {
ExpectParsesTo(
"syntax = 'proto2';\n"
"message TestMessage {\n"
" option deprecated_legacy_json_field_conflicts = true;\n"
" optional uint32 fooBar = 1;\n"
" optional uint32 foo_bar = 2;\n"
"}\n",
"syntax: 'proto2'\n"
"message_type {\n"
" name: 'TestMessage'\n"
" field {\n"
" label: LABEL_OPTIONAL type: TYPE_UINT32 name: 'fooBar' number: 1\n"
" }\n"
" field {\n"
" label: LABEL_OPTIONAL type: TYPE_UINT32 name: 'foo_bar' number: 2\n"
" }\n"
" options {\n"
" uninterpreted_option {\n"
" name {\n"
" name_part: 'deprecated_legacy_json_field_conflicts'\n"
" is_extension: false\n"
" }\n"
" identifier_value: 'true'\n"
" }\n"
" }\n"
"}\n");
}
TEST_F(ParserValidationErrorTest, EnumNameError) {

@ -130,6 +130,18 @@ std::string ToJsonName(const std::string& input) {
return result;
}
template <typename OptionsT>
bool IsLegacyJsonFieldConflictEnabled(const OptionsT& options) {
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
return options.deprecated_legacy_json_field_conflicts();
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}
// Backport of fold expressions for the comma operator to C++11.
// Usage: Fold({expr...});
// Guaranteed to evaluate left-to-right
@ -3870,8 +3882,6 @@ class DescriptorBuilder {
internal::FlatAllocator& alloc);
void BuildOneof(const OneofDescriptorProto& proto, Descriptor* parent,
OneofDescriptor* result, internal::FlatAllocator& alloc);
void CheckEnumValueUniqueness(const EnumDescriptorProto& proto,
const EnumDescriptor* result);
void BuildEnum(const EnumDescriptorProto& proto, const Descriptor* parent,
EnumDescriptor* result, internal::FlatAllocator& alloc);
void BuildEnumValue(const EnumValueDescriptorProto& proto,
@ -3883,6 +3893,15 @@ class DescriptorBuilder {
const ServiceDescriptor* parent, MethodDescriptor* result,
internal::FlatAllocator& alloc);
void CheckFieldJsonNameUniqueness(const DescriptorProto& proto,
const Descriptor* result);
void CheckFieldJsonNameUniqueness(const std::string& message_name,
const DescriptorProto& message,
FileDescriptor::Syntax syntax,
bool use_custom_names);
void CheckEnumValueUniqueness(const EnumDescriptorProto& proto,
const EnumDescriptor* result);
void LogUnusedDependency(const FileDescriptorProto& proto,
const FileDescriptor* result);
@ -5439,6 +5458,8 @@ void DescriptorBuilder::BuildMessage(const DescriptorProto& proto,
}
// Check that fields aren't using reserved names or numbers and that they
// aren't using extension numbers.
for (int i = 0; i < result->field_count(); i++) {
const FieldDescriptor* field = result->field(i);
for (int j = 0; j < result->extension_range_count(); j++) {
@ -5503,6 +5524,103 @@ void DescriptorBuilder::BuildMessage(const DescriptorProto& proto,
}
}
void DescriptorBuilder::CheckFieldJsonNameUniqueness(
const DescriptorProto& proto, const Descriptor* result) {
FileDescriptor::Syntax syntax = result->file()->syntax();
std::string message_name = result->full_name();
if (IsLegacyJsonFieldConflictEnabled(result->options())) {
if (syntax == FileDescriptor::SYNTAX_PROTO3) {
// Only check default JSON names for conflicts in proto3. This is legacy
// behavior that will be removed in a later version.
CheckFieldJsonNameUniqueness(message_name, proto, syntax, false);
}
} else {
// Check both with and without taking json_name into consideration. This is
// needed for field masks, which don't use json_name.
CheckFieldJsonNameUniqueness(message_name, proto, syntax, false);
CheckFieldJsonNameUniqueness(message_name, proto, syntax, true);
}
}
namespace {
// Helpers for function below
struct JsonNameDetails {
const FieldDescriptorProto* field;
std::string orig_name;
bool is_custom;
};
JsonNameDetails GetJsonNameDetails(const FieldDescriptorProto* field,
bool use_custom) {
std::string default_json_name = ToJsonName(field->name());
if (use_custom && field->has_json_name() &&
field->json_name() != default_json_name) {
return {field, field->json_name(), true};
}
return {field, default_json_name, false};
}
bool JsonNameLooksLikeExtension(std::string name) {
return !name.empty() && name.front() == '[' && name.back() == ']';
}
} // namespace
void DescriptorBuilder::CheckFieldJsonNameUniqueness(
const std::string& message_name, const DescriptorProto& message,
FileDescriptor::Syntax syntax, bool use_custom_names) {
absl::flat_hash_map<std::string, JsonNameDetails> name_to_field;
for (const FieldDescriptorProto& field : message.field()) {
JsonNameDetails details = GetJsonNameDetails(&field, use_custom_names);
if (details.is_custom && JsonNameLooksLikeExtension(details.orig_name)) {
std::string error_message = absl::StrFormat(
"The custom JSON name of field \"%s\" (\"%s\") is invalid: "
"JSON names may not start with '[' and end with ']'.",
field.name(), details.orig_name);
AddError(message_name, field, DescriptorPool::ErrorCollector::NAME,
error_message);
continue;
}
auto it_inserted = name_to_field.try_emplace(details.orig_name, details);
if (it_inserted.second) {
continue;
}
JsonNameDetails& match = it_inserted.first->second;
if (use_custom_names && !details.is_custom && !match.is_custom) {
// if this pass is considering custom JSON names, but neither of the
// names involved in the conflict are custom, don't bother with a
// message. That will have been reported from other pass (non-custom
// JSON names).
continue;
}
absl::string_view this_type = details.is_custom ? "custom" : "default";
absl::string_view existing_type = match.is_custom ? "custom" : "default";
// If the matched name differs (which it can only differ in case), include
// it in the error message, for maximum clarity to user.
std::string name_suffix = "";
if (details.orig_name != match.orig_name) {
name_suffix = absl::StrCat(" (\"", match.orig_name, "\")");
}
std::string error_message = absl::StrFormat(
"The %s JSON name of field \"%s\" (\"%s\") conflicts "
"with the %s JSON name of field \"%s\"%s.",
this_type, field.name(), details.orig_name, existing_type,
match.field->name(), name_suffix);
bool involves_default = !details.is_custom || !match.is_custom;
if (syntax == FileDescriptor::SYNTAX_PROTO2 && involves_default) {
// TODO(b/261750676) Upgrade this to an error once downstream proto2 files
// have been fixed.
AddWarning(message_name, field, DescriptorPool::ErrorCollector::NAME,
error_message);
} else {
AddError(message_name, field, DescriptorPool::ErrorCollector::NAME,
error_message);
}
}
}
namespace {
bool IsAllowedReservedField(const FieldDescriptorProto& field) {
return false;
@ -5917,13 +6035,12 @@ void DescriptorBuilder::CheckEnumValueUniqueness(
// NAME_TYPE_LAST_NAME = 2,
// }
PrefixRemover remover(result->name());
std::map<std::string, const EnumValueDescriptor*> values;
absl::flat_hash_map<std::string, const EnumValueDescriptor*> values;
for (int i = 0; i < result->value_count(); i++) {
const EnumValueDescriptor* value = result->value(i);
std::string stripped =
EnumValueToPascalCase(remover.MaybeRemove(value->name()));
std::pair<std::map<std::string, const EnumValueDescriptor*>::iterator, bool>
insert_result = values.insert(std::make_pair(stripped, value));
auto insert_result = values.try_emplace(stripped, value);
bool inserted = insert_result.second;
// We don't throw the error if the two conflicting symbols are identical, or
@ -5934,25 +6051,24 @@ void DescriptorBuilder::CheckEnumValueUniqueness(
// stripping should de-dup the labels in this case).
if (!inserted && insert_result.first->second->name() != value->name() &&
insert_result.first->second->number() != value->number()) {
std::string error_message =
"Enum name " + value->name() + " has the same name as " +
values[stripped]->name() +
" if you ignore case and strip out the enum name prefix (if any). "
"This is error-prone and can lead to undefined behavior. "
"Please avoid doing this. If you are using allow_alias, please "
"assign the same numeric value to both enums.";
std::string error_message = absl::StrFormat(
"Enum name %s has the same name as %s if you ignore case and strip "
"out the enum name prefix (if any). (If you are using allow_alias, "
"please assign the same numeric value to both enums.)",
value->name(), values[stripped]->name());
// There are proto2 enums out there with conflicting names, so to preserve
// compatibility we issue only a warning for proto2.
if (result->file()->syntax() == FileDescriptor::SYNTAX_PROTO2) {
if (IsLegacyJsonFieldConflictEnabled(result->options()) &&
result->file()->syntax() == FileDescriptor::SYNTAX_PROTO2) {
AddWarning(value->full_name(), proto.value(i),
DescriptorPool::ErrorCollector::NAME, error_message);
} else {
continue;
}
AddError(value->full_name(), proto.value(i),
DescriptorPool::ErrorCollector::NAME, error_message);
}
}
}
}
void DescriptorBuilder::BuildEnum(const EnumDescriptorProto& proto,
const Descriptor* parent,
@ -6003,8 +6119,6 @@ void DescriptorBuilder::BuildEnum(const EnumDescriptorProto& proto,
alloc.AllocateStrings(proto.reserved_name(i));
}
CheckEnumValueUniqueness(proto, result);
// Copy options.
result->options_ = nullptr; // Set to default_instance later if necessary.
if (proto.has_options()) {
@ -6813,20 +6927,6 @@ void DescriptorBuilder::ValidateProto3(FileDescriptor* file,
}
}
static std::string ToLowercaseWithoutUnderscores(const std::string& name) {
std::string result;
for (char character : name) {
if (character != '_') {
if (character >= 'A' && character <= 'Z') {
result.push_back(character - 'A' + 'a');
} else {
result.push_back(character);
}
}
}
return result;
}
void DescriptorBuilder::ValidateProto3Message(Descriptor* message,
const DescriptorProto& proto) {
for (int i = 0; i < message->nested_type_count(); ++i) {
@ -6851,25 +6951,6 @@ void DescriptorBuilder::ValidateProto3Message(Descriptor* message,
AddError(message->full_name(), proto, DescriptorPool::ErrorCollector::NAME,
"MessageSet is not supported in proto3.");
}
// In proto3, we reject field names if they conflict in camelCase.
// Note that we currently enforce a stricter rule: Field names must be
// unique after being converted to lowercase with underscores removed.
std::map<std::string, const FieldDescriptor*> name_to_field;
for (int i = 0; i < message->field_count(); ++i) {
std::string lowercase_name =
ToLowercaseWithoutUnderscores(message->field(i)->name());
if (name_to_field.find(lowercase_name) != name_to_field.end()) {
AddError(message->full_name(), proto.field(i),
DescriptorPool::ErrorCollector::NAME,
"The JSON camel-case name of field \"" +
message->field(i)->name() + "\" conflicts with field \"" +
name_to_field[lowercase_name]->name() + "\". This is not " +
"allowed in proto3.");
} else {
name_to_field[lowercase_name] = message->field(i);
}
}
}
void DescriptorBuilder::ValidateProto3Field(FieldDescriptor* field,
@ -6923,6 +7004,8 @@ void DescriptorBuilder::ValidateMessageOptions(Descriptor* message,
VALIDATE_OPTIONS_FROM_ARRAY(message, enum_type, Enum);
VALIDATE_OPTIONS_FROM_ARRAY(message, extension, Field);
CheckFieldJsonNameUniqueness(proto, message);
const int64_t max_extension_range =
static_cast<int64_t>(message->options().message_set_wire_format()
? std::numeric_limits<int32_t>::max()
@ -7024,6 +7107,9 @@ void DescriptorBuilder::ValidateFieldOptions(
void DescriptorBuilder::ValidateEnumOptions(EnumDescriptor* enm,
const EnumDescriptorProto& proto) {
VALIDATE_OPTIONS_FROM_ARRAY(enm, value, EnumValue);
CheckEnumValueUniqueness(proto, enm);
if (!enm->options().has_allow_alias() || !enm->options().allow_alias()) {
std::map<int, std::string> used_values;
for (int i = 0; i < enm->value_count(); ++i) {

@ -6474,7 +6474,7 @@ TEST_F(ValidationErrorTest, MapEntryConflictsWithEnum) {
"with an existing enum type.\n");
}
TEST_F(ValidationErrorTest, EnumValuesConflictWithDifferentCasing) {
TEST_F(ValidationErrorTest, Proto3EnumValuesConflictWithDifferentCasing) {
BuildFileWithErrors(
"syntax: 'proto3'"
"name: 'foo.proto' "
@ -6485,9 +6485,21 @@ TEST_F(ValidationErrorTest, EnumValuesConflictWithDifferentCasing) {
"}",
"foo.proto: bar: NAME: Enum name bar has the same name as BAR "
"if you ignore case and strip out the enum name prefix (if any). "
"This is error-prone and can lead to undefined behavior. "
"Please avoid doing this. If you are using allow_alias, please assign "
"the same numeric value to both enums.\n");
"(If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
BuildFileWithErrors(
"syntax: 'proto2'"
"name: 'foo.proto' "
"enum_type {"
" name: 'FooEnum' "
" value { name: 'BAR' number: 0 }"
" value { name: 'bar' number: 1 }"
"}",
"foo.proto: bar: NAME: Enum name bar has the same name as BAR "
"if you ignore case and strip out the enum name prefix (if any). "
"(If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
// Not an error because both enums are mapped to the same value.
BuildFile(
@ -6513,9 +6525,8 @@ TEST_F(ValidationErrorTest, EnumValuesConflictWhenPrefixesStripped) {
"}",
"foo.proto: BAZ: NAME: Enum name BAZ has the same name as FOO_ENUM_BAZ "
"if you ignore case and strip out the enum name prefix (if any). "
"This is error-prone and can lead to undefined behavior. "
"Please avoid doing this. If you are using allow_alias, please assign "
"the same numeric value to both enums.\n");
"(If you are using allow_alias, please assign the same numeric value "
"to both enums.)\n");
BuildFileWithErrors(
"syntax: 'proto3'"
@ -6527,9 +6538,8 @@ TEST_F(ValidationErrorTest, EnumValuesConflictWhenPrefixesStripped) {
"}",
"foo.proto: BAZ: NAME: Enum name BAZ has the same name as FOOENUM_BAZ "
"if you ignore case and strip out the enum name prefix (if any). "
"This is error-prone and can lead to undefined behavior. "
"Please avoid doing this. If you are using allow_alias, please assign "
"the same numeric value to both enums.\n");
"(If you are using allow_alias, please assign the same numeric value "
"to both enums.)\n");
BuildFileWithErrors(
"syntax: 'proto3'"
@ -6541,9 +6551,8 @@ TEST_F(ValidationErrorTest, EnumValuesConflictWhenPrefixesStripped) {
"}",
"foo.proto: BAR__BAZ: NAME: Enum name BAR__BAZ has the same name as "
"FOO_ENUM_BAR_BAZ if you ignore case and strip out the enum name prefix "
"(if any). This is error-prone and can lead to undefined behavior. "
"Please avoid doing this. If you are using allow_alias, please assign "
"the same numeric value to both enums.\n");
"(if any). (If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
BuildFileWithErrors(
"syntax: 'proto3'"
@ -6555,9 +6564,21 @@ TEST_F(ValidationErrorTest, EnumValuesConflictWhenPrefixesStripped) {
"}",
"foo.proto: BAR_BAZ: NAME: Enum name BAR_BAZ has the same name as "
"FOO_ENUM__BAR_BAZ if you ignore case and strip out the enum name prefix "
"(if any). This is error-prone and can lead to undefined behavior. "
"Please avoid doing this. If you are using allow_alias, please assign "
"the same numeric value to both enums.\n");
"(if any). (If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
BuildFileWithErrors(
"syntax: 'proto2'"
"name: 'foo.proto' "
"enum_type {"
" name: 'FooEnum' "
" value { name: 'FOO_ENUM__BAR_BAZ' number: 0 }"
" value { name: 'BAR_BAZ' number: 1 }"
"}",
"foo.proto: BAR_BAZ: NAME: Enum name BAR_BAZ has the same name as "
"FOO_ENUM__BAR_BAZ if you ignore case and strip out the enum name prefix "
"(if any). (If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
// This isn't an error because the underscore will cause the PascalCase to
// differ by case (BarBaz vs. Barbaz).
@ -6571,6 +6592,52 @@ TEST_F(ValidationErrorTest, EnumValuesConflictWhenPrefixesStripped) {
"}");
}
TEST_F(ValidationErrorTest, EnumValuesConflictLegacyBehavior) {
BuildFileWithErrors(
"syntax: 'proto3'"
"name: 'foo.proto' "
"enum_type {"
" name: 'FooEnum' "
" options { deprecated_legacy_json_field_conflicts: true }"
" value { name: 'BAR' number: 0 }"
" value { name: 'bar' number: 1 }"
"}",
"foo.proto: bar: NAME: Enum name bar has the same name as BAR "
"if you ignore case and strip out the enum name prefix (if any). "
"(If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
BuildFileWithErrors(
"syntax: 'proto3'"
"name: 'foo.proto' "
"enum_type {"
" name: 'FooEnum' "
" options { deprecated_legacy_json_field_conflicts: true }"
" value { name: 'FOO_ENUM__BAR_BAZ' number: 0 }"
" value { name: 'BAR_BAZ' number: 1 }"
"}",
"foo.proto: BAR_BAZ: NAME: Enum name BAR_BAZ has the same name as "
"FOO_ENUM__BAR_BAZ if you ignore case and strip out the enum name "
"prefix "
"(if any). (If you are using allow_alias, please assign the same "
"numeric "
"value to both enums.)\n");
BuildFileWithWarnings(
"syntax: 'proto2'"
"name: 'foo.proto' "
"enum_type {"
" name: 'FooEnum' "
" options { deprecated_legacy_json_field_conflicts: true }"
" value { name: 'BAR' number: 0 }"
" value { name: 'bar' number: 1 }"
"}",
"foo.proto: bar: NAME: Enum name bar has the same name as BAR "
"if you ignore case and strip out the enum name prefix (if any). "
"(If you are using allow_alias, please assign the same numeric "
"value to both enums.)\n");
}
TEST_F(ValidationErrorTest, MapEntryConflictsWithOneof) {
FileDescriptorProto file_proto;
FillValidMapEntry(&file_proto);
@ -6892,29 +6959,74 @@ TEST_F(ValidationErrorTest, ValidateProto3Extension) {
}
// Test that field names that may conflict in JSON is not allowed by protoc.
TEST_F(ValidationErrorTest, ValidateProto3JsonName) {
TEST_F(ValidationErrorTest, ValidateJsonNameConflictProto3) {
// The comparison is case-insensitive.
BuildFileWithErrors(
"name: 'foo.proto' "
"syntax: 'proto3' "
"message_type {"
" name: 'Foo'"
" field { name:'name' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'_name' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'Name' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
"}",
"foo.proto: Foo: NAME: The JSON camel-case name of field \"Name\" "
"conflicts with field \"name\". This is not allowed in proto3.\n");
"foo.proto: Foo: NAME: The default JSON name of field \"Name\" "
"(\"Name\") "
"conflicts with the default JSON name of field \"_name\".\n");
// Underscores are ignored.
BuildFileWithErrors(
"name: 'foo.proto' "
"syntax: 'proto3' "
"message_type {"
" name: 'Foo'"
" field { name:'ab' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'AB' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'_a__b_' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
"}",
"foo.proto: Foo: NAME: The default JSON name of field \"_a__b_\" "
"(\"AB\") "
"conflicts with the default JSON name of field \"AB\".\n");
}
TEST_F(ValidationErrorTest, ValidateJsonNameConflictProto2) {
BuildFileWithWarnings(
"name: 'foo.proto' "
"syntax: 'proto2' "
"message_type {"
" name: 'Foo'"
" field { name:'AB' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'_a__b_' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
"}",
"foo.proto: Foo: NAME: The default JSON name of field \"_a__b_\" "
"(\"AB\") "
"conflicts with the default JSON name of field \"AB\".\n");
}
// Test that field names that may conflict in JSON is not allowed by protoc.
TEST_F(ValidationErrorTest, ValidateJsonNameConflictProto3Legacy) {
BuildFileWithErrors(
"name: 'foo.proto' "
"syntax: 'proto3' "
"message_type {"
" name: 'Foo'"
" options { deprecated_legacy_json_field_conflicts: true }"
" field { name:'AB' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'_a__b_' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
"}",
"foo.proto: Foo: NAME: The JSON camel-case name of field \"_a__b_\" "
"conflicts with field \"ab\". This is not allowed in proto3.\n");
"foo.proto: Foo: NAME: The default JSON name of field \"_a__b_\" "
"(\"AB\") "
"conflicts with the default JSON name of field \"AB\".\n");
}
TEST_F(ValidationErrorTest, ValidateJsonNameConflictProto2Legacy) {
BuildFile(
"name: 'foo.proto' "
"syntax: 'proto2' "
"message_type {"
" name: 'Foo'"
" options { deprecated_legacy_json_field_conflicts: true }"
" field { name:'AB' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
" field { name:'_a__b_' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
"}");
}

Loading…
Cancel
Save