Expand google.protobuf.Type handling to cover editions.

PiperOrigin-RevId: 514783686
pull/12178/head
Mike Kruskal 2 years ago committed by Copybara-Service
parent ff5007f929
commit 4a2f566ff0
  1. 1
      .github/workflows/test_cpp.yml
  2. 11
      objectivec/GPBType.pbobjc.h
  3. 27
      objectivec/GPBType.pbobjc.m
  4. 6
      src/google/protobuf/type.proto
  5. 511
      src/google/protobuf/util/type_resolver_util.cc
  6. 10
      src/google/protobuf/util/type_resolver_util.h
  7. 428
      src/google/protobuf/util/type_resolver_util_test.cc

@ -80,7 +80,6 @@ jobs:
with:
image: us-docker.pkg.dev/protobuf-build/containers/test/linux/emulation:${{ matrix.arch }}-3af05275178e16af30961976af126eabbbb2c733
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
skip-staleness-check: true
entrypoint: bash
command: >
-c "set -ex;

@ -43,6 +43,9 @@ typedef GPB_ENUM(GPBSyntax) {
/** Syntax `proto3`. */
GPBSyntax_SyntaxProto3 = 1,
/** Syntax `editions`. */
GPBSyntax_SyntaxEditions = 2,
};
GPBEnumDescriptor *GPBSyntax_EnumDescriptor(void);
@ -184,6 +187,7 @@ typedef GPB_ENUM(GPBType_FieldNumber) {
GPBType_FieldNumber_OptionsArray = 4,
GPBType_FieldNumber_SourceContext = 5,
GPBType_FieldNumber_Syntax = 6,
GPBType_FieldNumber_Edition = 7,
};
/**
@ -217,6 +221,9 @@ GPB_FINAL @interface GPBType : GPBMessage
/** The source syntax. */
@property(nonatomic, readwrite) GPBSyntax syntax;
/** The source edition string, only valid when syntax is SYNTAX_EDITIONS. */
@property(nonatomic, readwrite, copy, null_resettable) NSString *edition;
@end
/**
@ -323,6 +330,7 @@ typedef GPB_ENUM(GPBEnum_FieldNumber) {
GPBEnum_FieldNumber_OptionsArray = 3,
GPBEnum_FieldNumber_SourceContext = 4,
GPBEnum_FieldNumber_Syntax = 5,
GPBEnum_FieldNumber_Edition = 6,
};
/**
@ -351,6 +359,9 @@ GPB_FINAL @interface GPBEnum : GPBMessage
/** The source syntax. */
@property(nonatomic, readwrite) GPBSyntax syntax;
/** The source edition string, only valid when syntax is SYNTAX_EDITIONS. */
@property(nonatomic, readwrite, copy, null_resettable) NSString *edition;
@end
/**

@ -54,10 +54,12 @@ GPBEnumDescriptor *GPBSyntax_EnumDescriptor(void) {
if (!descriptor) {
GPB_DEBUG_CHECK_RUNTIME_VERSIONS();
static const char *valueNames =
"SyntaxProto2\000SyntaxProto3\000";
"SyntaxProto2\000SyntaxProto3\000SyntaxEditions"
"\000";
static const int32_t values[] = {
GPBSyntax_SyntaxProto2,
GPBSyntax_SyntaxProto3,
GPBSyntax_SyntaxEditions,
};
GPBEnumDescriptor *worker =
[GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GPBSyntax)
@ -78,6 +80,7 @@ BOOL GPBSyntax_IsValidValue(int32_t value__) {
switch (value__) {
case GPBSyntax_SyntaxProto2:
case GPBSyntax_SyntaxProto3:
case GPBSyntax_SyntaxEditions:
return YES;
default:
return NO;
@ -212,6 +215,7 @@ BOOL GPBField_Cardinality_IsValidValue(int32_t value__) {
@dynamic optionsArray, optionsArray_Count;
@dynamic hasSourceContext, sourceContext;
@dynamic syntax;
@dynamic edition;
typedef struct GPBType__storage_ {
uint32_t _has_storage_[1];
@ -221,6 +225,7 @@ typedef struct GPBType__storage_ {
NSMutableArray *oneofsArray;
NSMutableArray *optionsArray;
GPBSourceContext *sourceContext;
NSString *edition;
} GPBType__storage_;
// This method is threadsafe because it is initially called
@ -284,6 +289,15 @@ typedef struct GPBType__storage_ {
.flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor | GPBFieldClearHasIvarOnZero),
.dataType = GPBDataTypeEnum,
},
{
.name = "edition",
.dataTypeSpecific.clazz = Nil,
.number = GPBType_FieldNumber_Edition,
.hasIndex = 3,
.offset = (uint32_t)offsetof(GPBType__storage_, edition),
.flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldClearHasIvarOnZero),
.dataType = GPBDataTypeString,
},
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:GPBObjCClass(GPBType)
@ -497,6 +511,7 @@ void SetGPBField_Cardinality_RawValue(GPBField *message, int32_t value) {
@dynamic optionsArray, optionsArray_Count;
@dynamic hasSourceContext, sourceContext;
@dynamic syntax;
@dynamic edition;
typedef struct GPBEnum__storage_ {
uint32_t _has_storage_[1];
@ -505,6 +520,7 @@ typedef struct GPBEnum__storage_ {
NSMutableArray *enumvalueArray;
NSMutableArray *optionsArray;
GPBSourceContext *sourceContext;
NSString *edition;
} GPBEnum__storage_;
// This method is threadsafe because it is initially called
@ -559,6 +575,15 @@ typedef struct GPBEnum__storage_ {
.flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor | GPBFieldClearHasIvarOnZero),
.dataType = GPBDataTypeEnum,
},
{
.name = "edition",
.dataTypeSpecific.clazz = Nil,
.number = GPBEnum_FieldNumber_Edition,
.hasIndex = 3,
.offset = (uint32_t)offsetof(GPBEnum__storage_, edition),
.flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldClearHasIvarOnZero),
.dataType = GPBDataTypeString,
},
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:GPBObjCClass(GPBEnum)

@ -57,6 +57,8 @@ message Type {
SourceContext source_context = 5;
// The source syntax.
Syntax syntax = 6;
// The source edition string, only valid when syntax is SYNTAX_EDITIONS.
string edition = 7;
}
// A single field of a message type.
@ -151,6 +153,8 @@ message Enum {
SourceContext source_context = 4;
// The source syntax.
Syntax syntax = 5;
// The source edition string, only valid when syntax is SYNTAX_EDITIONS.
string edition = 6;
}
// Enum value definition.
@ -184,4 +188,6 @@ enum Syntax {
SYNTAX_PROTO2 = 0;
// Syntax `proto3`.
SYNTAX_PROTO3 = 1;
// Syntax `editions`.
SYNTAX_EDITIONS = 2;
}

@ -30,10 +30,13 @@
#include "google/protobuf/util/type_resolver_util.h"
#include <string>
#include <vector>
#include "google/protobuf/source_context.pb.h"
#include "google/protobuf/type.pb.h"
#include "google/protobuf/wrappers.pb.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/descriptor.h"
#include "absl/log/absl_log.h"
#include "absl/status/status.h"
#include "absl/strings/escaping.h"
@ -63,10 +66,255 @@ using google::protobuf::Int32Value;
using google::protobuf::Int64Value;
using google::protobuf::Option;
using google::protobuf::StringValue;
using google::protobuf::Syntax;
using google::protobuf::Type;
using google::protobuf::UInt32Value;
using google::protobuf::UInt64Value;
template <typename WrapperT, typename T>
static WrapperT WrapValue(T value) {
WrapperT wrapper;
wrapper.set_value(value);
return wrapper;
}
void ConvertOptionField(const Reflection* reflection, const Message& options,
const FieldDescriptor* field, int index, Option* out) {
out->set_name(field->is_extension() ? field->full_name() : field->name());
Any* value = out->mutable_value();
switch (field->cpp_type()) {
case FieldDescriptor::CPPTYPE_MESSAGE:
value->PackFrom(
field->is_repeated()
? reflection->GetRepeatedMessage(options, field, index)
: reflection->GetMessage(options, field));
return;
case FieldDescriptor::CPPTYPE_DOUBLE:
value->PackFrom(WrapValue<DoubleValue>(
field->is_repeated()
? reflection->GetRepeatedDouble(options, field, index)
: reflection->GetDouble(options, field)));
return;
case FieldDescriptor::CPPTYPE_FLOAT:
value->PackFrom(WrapValue<FloatValue>(
field->is_repeated()
? reflection->GetRepeatedFloat(options, field, index)
: reflection->GetFloat(options, field)));
return;
case FieldDescriptor::CPPTYPE_INT64:
value->PackFrom(WrapValue<Int64Value>(
field->is_repeated()
? reflection->GetRepeatedInt64(options, field, index)
: reflection->GetInt64(options, field)));
return;
case FieldDescriptor::CPPTYPE_UINT64:
value->PackFrom(WrapValue<UInt64Value>(
field->is_repeated()
? reflection->GetRepeatedUInt64(options, field, index)
: reflection->GetUInt64(options, field)));
return;
case FieldDescriptor::CPPTYPE_INT32:
value->PackFrom(WrapValue<Int32Value>(
field->is_repeated()
? reflection->GetRepeatedInt32(options, field, index)
: reflection->GetInt32(options, field)));
return;
case FieldDescriptor::CPPTYPE_UINT32:
value->PackFrom(WrapValue<UInt32Value>(
field->is_repeated()
? reflection->GetRepeatedUInt32(options, field, index)
: reflection->GetUInt32(options, field)));
return;
case FieldDescriptor::CPPTYPE_BOOL:
value->PackFrom(WrapValue<BoolValue>(
field->is_repeated()
? reflection->GetRepeatedBool(options, field, index)
: reflection->GetBool(options, field)));
return;
case FieldDescriptor::CPPTYPE_STRING: {
const std::string& val =
field->is_repeated()
? reflection->GetRepeatedString(options, field, index)
: reflection->GetString(options, field);
if (field->type() == FieldDescriptor::TYPE_STRING) {
value->PackFrom(WrapValue<StringValue>(val));
} else {
value->PackFrom(WrapValue<BytesValue>(val));
}
return;
}
case FieldDescriptor::CPPTYPE_ENUM: {
const EnumValueDescriptor* val =
field->is_repeated()
? reflection->GetRepeatedEnum(options, field, index)
: reflection->GetEnum(options, field);
value->PackFrom(WrapValue<Int32Value>(val->number()));
return;
}
}
}
// Implementation details for Convert*Options.
void ConvertOptionsInternal(const Message& options,
RepeatedPtrField<Option>& output) {
const Reflection* reflection = options.GetReflection();
std::vector<const FieldDescriptor*> fields;
reflection->ListFields(options, &fields);
for (const FieldDescriptor* field : fields) {
if (field->is_repeated()) {
const int size = reflection->FieldSize(options, field);
for (int i = 0; i < size; ++i) {
ConvertOptionField(reflection, options, field, i, output.Add());
}
} else {
ConvertOptionField(reflection, options, field, -1, output.Add());
}
}
}
void ConvertMessageOptions(const MessageOptions& options,
RepeatedPtrField<Option>& output) {
return ConvertOptionsInternal(options, output);
}
void ConvertFieldOptions(const FieldOptions& options,
RepeatedPtrField<Option>& output) {
return ConvertOptionsInternal(options, output);
}
void ConvertEnumOptions(const EnumOptions& options,
RepeatedPtrField<Option>& output) {
return ConvertOptionsInternal(options, output);
}
void ConvertEnumValueOptions(const EnumValueOptions& options,
RepeatedPtrField<Option>& output) {
return ConvertOptionsInternal(options, output);
}
std::string DefaultValueAsString(const FieldDescriptor& descriptor) {
switch (descriptor.cpp_type()) {
case FieldDescriptor::CPPTYPE_INT32:
return absl::StrCat(descriptor.default_value_int32());
break;
case FieldDescriptor::CPPTYPE_INT64:
return absl::StrCat(descriptor.default_value_int64());
break;
case FieldDescriptor::CPPTYPE_UINT32:
return absl::StrCat(descriptor.default_value_uint32());
break;
case FieldDescriptor::CPPTYPE_UINT64:
return absl::StrCat(descriptor.default_value_uint64());
break;
case FieldDescriptor::CPPTYPE_FLOAT:
return io::SimpleFtoa(descriptor.default_value_float());
break;
case FieldDescriptor::CPPTYPE_DOUBLE:
return io::SimpleDtoa(descriptor.default_value_double());
break;
case FieldDescriptor::CPPTYPE_BOOL:
return descriptor.default_value_bool() ? "true" : "false";
break;
case FieldDescriptor::CPPTYPE_STRING:
if (descriptor.type() == FieldDescriptor::TYPE_BYTES) {
return absl::CEscape(descriptor.default_value_string());
} else {
return descriptor.default_value_string();
}
break;
case FieldDescriptor::CPPTYPE_ENUM:
return descriptor.default_value_enum()->name();
break;
case FieldDescriptor::CPPTYPE_MESSAGE:
ABSL_DLOG(FATAL) << "Messages can't have default values!";
break;
}
return "";
}
template <typename T>
std::string GetTypeUrl(absl::string_view url_prefix, const T& descriptor) {
return absl::StrCat(url_prefix, "/", descriptor.full_name());
}
void ConvertFieldDescriptor(absl::string_view url_prefix,
const FieldDescriptor& descriptor, Field* field) {
field->set_kind(static_cast<Field::Kind>(descriptor.type()));
switch (descriptor.label()) {
case FieldDescriptor::LABEL_OPTIONAL:
field->set_cardinality(Field::CARDINALITY_OPTIONAL);
break;
case FieldDescriptor::LABEL_REPEATED:
field->set_cardinality(Field::CARDINALITY_REPEATED);
break;
case FieldDescriptor::LABEL_REQUIRED:
field->set_cardinality(Field::CARDINALITY_REQUIRED);
break;
}
field->set_number(descriptor.number());
field->set_name(descriptor.name());
field->set_json_name(descriptor.json_name());
if (descriptor.has_default_value()) {
field->set_default_value(DefaultValueAsString(descriptor));
}
if (descriptor.type() == FieldDescriptor::TYPE_MESSAGE ||
descriptor.type() == FieldDescriptor::TYPE_GROUP) {
field->set_type_url(GetTypeUrl(url_prefix, *descriptor.message_type()));
} else if (descriptor.type() == FieldDescriptor::TYPE_ENUM) {
field->set_type_url(GetTypeUrl(url_prefix, *descriptor.enum_type()));
}
if (descriptor.containing_oneof() != nullptr) {
field->set_oneof_index(descriptor.containing_oneof()->index() + 1);
}
if (descriptor.is_packed()) {
field->set_packed(true);
}
ConvertFieldOptions(descriptor.options(), *field->mutable_options());
}
Syntax ConvertSyntax(FileDescriptor::Syntax syntax) {
switch (syntax) {
default:
return Syntax::SYNTAX_PROTO2;
}
}
void ConvertEnumDescriptor(const EnumDescriptor& descriptor, Enum* enum_type) {
enum_type->Clear();
enum_type->set_syntax(ConvertSyntax(descriptor.file()->syntax()));
enum_type->set_name(descriptor.full_name());
enum_type->mutable_source_context()->set_file_name(descriptor.file()->name());
for (int i = 0; i < descriptor.value_count(); ++i) {
const EnumValueDescriptor& value_descriptor = *descriptor.value(i);
EnumValue* value = enum_type->mutable_enumvalue()->Add();
value->set_name(value_descriptor.name());
value->set_number(value_descriptor.number());
ConvertEnumValueOptions(value_descriptor.options(),
*value->mutable_options());
}
ConvertEnumOptions(descriptor.options(), *enum_type->mutable_options());
}
void ConvertDescriptor(absl::string_view url_prefix,
const Descriptor& descriptor, Type* type) {
type->Clear();
type->set_name(descriptor.full_name());
type->set_syntax(ConvertSyntax(descriptor.file()->syntax()));
for (int i = 0; i < descriptor.field_count(); ++i) {
ConvertFieldDescriptor(url_prefix, *descriptor.field(i),
type->add_fields());
}
for (int i = 0; i < descriptor.oneof_decl_count(); ++i) {
type->add_oneofs(descriptor.oneof_decl(i)->name());
}
type->mutable_source_context()->set_file_name(descriptor.file()->name());
ConvertMessageOptions(descriptor.options(), *type->mutable_options());
}
class DescriptorPoolTypeResolver : public TypeResolver {
public:
DescriptorPoolTypeResolver(absl::string_view url_prefix,
@ -82,11 +330,11 @@ class DescriptorPoolTypeResolver : public TypeResolver {
}
const Descriptor* descriptor = pool_->FindMessageTypeByName(type_name);
if (descriptor == NULL) {
if (descriptor == nullptr) {
return absl::NotFoundError(
absl::StrCat("Invalid type URL, unknown type: ", type_name));
}
ConvertDescriptor(descriptor, type);
ConvertDescriptor(url_prefix_, *descriptor, type);
return absl::Status();
}
@ -99,213 +347,15 @@ class DescriptorPoolTypeResolver : public TypeResolver {
}
const EnumDescriptor* descriptor = pool_->FindEnumTypeByName(type_name);
if (descriptor == NULL) {
if (descriptor == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid type URL, unknown type: ", type_name));
}
ConvertEnumDescriptor(descriptor, enum_type);
ConvertEnumDescriptor(*descriptor, enum_type);
return absl::Status();
}
private:
void ConvertDescriptor(const Descriptor* descriptor, Type* type) {
type->Clear();
type->set_name(descriptor->full_name());
for (int i = 0; i < descriptor->field_count(); ++i) {
ConvertFieldDescriptor(descriptor->field(i), type->add_fields());
}
for (int i = 0; i < descriptor->oneof_decl_count(); ++i) {
type->add_oneofs(descriptor->oneof_decl(i)->name());
}
type->mutable_source_context()->set_file_name(descriptor->file()->name());
ConvertMessageOptions(descriptor->options(), type->mutable_options());
}
void ConvertMessageOptions(const MessageOptions& options,
RepeatedPtrField<Option>* output) {
return ConvertOptionsInternal(options, output);
}
void ConvertFieldOptions(const FieldOptions& options,
RepeatedPtrField<Option>* output) {
return ConvertOptionsInternal(options, output);
}
void ConvertEnumOptions(const EnumOptions& options,
RepeatedPtrField<Option>* output) {
return ConvertOptionsInternal(options, output);
}
void ConvertEnumValueOptions(const EnumValueOptions& options,
RepeatedPtrField<Option>* output) {
return ConvertOptionsInternal(options, output);
}
// Implementation details for Convert*Options.
void ConvertOptionsInternal(const Message& options,
RepeatedPtrField<Option>* output) {
const Reflection* reflection = options.GetReflection();
std::vector<const FieldDescriptor*> fields;
reflection->ListFields(options, &fields);
for (const FieldDescriptor* field : fields) {
if (field->is_repeated()) {
const int size = reflection->FieldSize(options, field);
for (int i = 0; i < size; i++) {
ConvertOptionField(reflection, options, field, i, output->Add());
}
} else {
ConvertOptionField(reflection, options, field, -1, output->Add());
}
}
}
static void ConvertOptionField(const Reflection* reflection,
const Message& options,
const FieldDescriptor* field, int index,
Option* out) {
out->set_name(field->is_extension() ? field->full_name() : field->name());
Any* value = out->mutable_value();
switch (field->cpp_type()) {
case FieldDescriptor::CPPTYPE_MESSAGE:
value->PackFrom(
field->is_repeated()
? reflection->GetRepeatedMessage(options, field, index)
: reflection->GetMessage(options, field));
return;
case FieldDescriptor::CPPTYPE_DOUBLE:
value->PackFrom(WrapValue<DoubleValue>(
field->is_repeated()
? reflection->GetRepeatedDouble(options, field, index)
: reflection->GetDouble(options, field)));
return;
case FieldDescriptor::CPPTYPE_FLOAT:
value->PackFrom(WrapValue<FloatValue>(
field->is_repeated()
? reflection->GetRepeatedFloat(options, field, index)
: reflection->GetFloat(options, field)));
return;
case FieldDescriptor::CPPTYPE_INT64:
value->PackFrom(WrapValue<Int64Value>(
field->is_repeated()
? reflection->GetRepeatedInt64(options, field, index)
: reflection->GetInt64(options, field)));
return;
case FieldDescriptor::CPPTYPE_UINT64:
value->PackFrom(WrapValue<UInt64Value>(
field->is_repeated()
? reflection->GetRepeatedUInt64(options, field, index)
: reflection->GetUInt64(options, field)));
return;
case FieldDescriptor::CPPTYPE_INT32:
value->PackFrom(WrapValue<Int32Value>(
field->is_repeated()
? reflection->GetRepeatedInt32(options, field, index)
: reflection->GetInt32(options, field)));
return;
case FieldDescriptor::CPPTYPE_UINT32:
value->PackFrom(WrapValue<UInt32Value>(
field->is_repeated()
? reflection->GetRepeatedUInt32(options, field, index)
: reflection->GetUInt32(options, field)));
return;
case FieldDescriptor::CPPTYPE_BOOL:
value->PackFrom(WrapValue<BoolValue>(
field->is_repeated()
? reflection->GetRepeatedBool(options, field, index)
: reflection->GetBool(options, field)));
return;
case FieldDescriptor::CPPTYPE_STRING: {
const std::string& val =
field->is_repeated()
? reflection->GetRepeatedString(options, field, index)
: reflection->GetString(options, field);
if (field->type() == FieldDescriptor::TYPE_STRING) {
value->PackFrom(WrapValue<StringValue>(val));
} else {
value->PackFrom(WrapValue<BytesValue>(val));
}
return;
}
case FieldDescriptor::CPPTYPE_ENUM: {
const EnumValueDescriptor* val =
field->is_repeated()
? reflection->GetRepeatedEnum(options, field, index)
: reflection->GetEnum(options, field);
value->PackFrom(WrapValue<Int32Value>(val->number()));
return;
}
}
}
template <typename WrapperT, typename T>
static WrapperT WrapValue(T value) {
WrapperT wrapper;
wrapper.set_value(value);
return wrapper;
}
void ConvertFieldDescriptor(const FieldDescriptor* descriptor, Field* field) {
field->set_kind(static_cast<Field::Kind>(descriptor->type()));
switch (descriptor->label()) {
case FieldDescriptor::LABEL_OPTIONAL:
field->set_cardinality(Field::CARDINALITY_OPTIONAL);
break;
case FieldDescriptor::LABEL_REPEATED:
field->set_cardinality(Field::CARDINALITY_REPEATED);
break;
case FieldDescriptor::LABEL_REQUIRED:
field->set_cardinality(Field::CARDINALITY_REQUIRED);
break;
}
field->set_number(descriptor->number());
field->set_name(descriptor->name());
field->set_json_name(descriptor->json_name());
if (descriptor->has_default_value()) {
field->set_default_value(DefaultValueAsString(descriptor));
}
if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE ||
descriptor->type() == FieldDescriptor::TYPE_GROUP) {
field->set_type_url(GetTypeUrl(descriptor->message_type()));
} else if (descriptor->type() == FieldDescriptor::TYPE_ENUM) {
field->set_type_url(GetTypeUrl(descriptor->enum_type()));
}
if (descriptor->containing_oneof() != NULL) {
field->set_oneof_index(descriptor->containing_oneof()->index() + 1);
}
if (descriptor->is_packed()) {
field->set_packed(true);
}
ConvertFieldOptions(descriptor->options(), field->mutable_options());
}
void ConvertEnumDescriptor(const EnumDescriptor* descriptor,
Enum* enum_type) {
enum_type->Clear();
enum_type->set_name(descriptor->full_name());
enum_type->mutable_source_context()->set_file_name(
descriptor->file()->name());
for (int i = 0; i < descriptor->value_count(); ++i) {
const EnumValueDescriptor* value_descriptor = descriptor->value(i);
EnumValue* value = enum_type->mutable_enumvalue()->Add();
value->set_name(value_descriptor->name());
value->set_number(value_descriptor->number());
ConvertEnumValueOptions(value_descriptor->options(),
value->mutable_options());
}
ConvertEnumOptions(descriptor->options(), enum_type->mutable_options());
}
std::string GetTypeUrl(const Descriptor* descriptor) {
return absl::StrCat(url_prefix_, "/", descriptor->full_name());
}
std::string GetTypeUrl(const EnumDescriptor* descriptor) {
return absl::StrCat(url_prefix_, "/", descriptor->full_name());
}
absl::Status ParseTypeUrl(absl::string_view type_url,
std::string* type_name) {
absl::string_view stripped = type_url;
@ -319,46 +369,6 @@ class DescriptorPoolTypeResolver : public TypeResolver {
return absl::Status();
}
std::string DefaultValueAsString(const FieldDescriptor* descriptor) {
switch (descriptor->cpp_type()) {
case FieldDescriptor::CPPTYPE_INT32:
return absl::StrCat(descriptor->default_value_int32());
break;
case FieldDescriptor::CPPTYPE_INT64:
return absl::StrCat(descriptor->default_value_int64());
break;
case FieldDescriptor::CPPTYPE_UINT32:
return absl::StrCat(descriptor->default_value_uint32());
break;
case FieldDescriptor::CPPTYPE_UINT64:
return absl::StrCat(descriptor->default_value_uint64());
break;
case FieldDescriptor::CPPTYPE_FLOAT:
return io::SimpleFtoa(descriptor->default_value_float());
break;
case FieldDescriptor::CPPTYPE_DOUBLE:
return io::SimpleDtoa(descriptor->default_value_double());
break;
case FieldDescriptor::CPPTYPE_BOOL:
return descriptor->default_value_bool() ? "true" : "false";
break;
case FieldDescriptor::CPPTYPE_STRING:
if (descriptor->type() == FieldDescriptor::TYPE_BYTES) {
return absl::CEscape(descriptor->default_value_string());
} else {
return descriptor->default_value_string();
}
break;
case FieldDescriptor::CPPTYPE_ENUM:
return descriptor->default_value_enum()->name();
break;
case FieldDescriptor::CPPTYPE_MESSAGE:
ABSL_DLOG(FATAL) << "Messages can't have default values!";
break;
}
return "";
}
std::string url_prefix_;
const DescriptorPool* pool_;
};
@ -370,6 +380,21 @@ TypeResolver* NewTypeResolverForDescriptorPool(absl::string_view url_prefix,
return new DescriptorPoolTypeResolver(url_prefix, pool);
}
// Performs a direct conversion from a descriptor to a type proto.
Type ConvertDescriptorToType(absl::string_view url_prefix,
const Descriptor& descriptor) {
Type type;
ConvertDescriptor(url_prefix, descriptor, &type);
return type;
}
// Performs a direct conversion from an enum descriptor to a type proto.
Enum ConvertDescriptorToType(const EnumDescriptor& descriptor) {
Enum enum_type;
ConvertEnumDescriptor(descriptor, &enum_type);
return enum_type;
}
} // namespace util
} // namespace protobuf
} // namespace google

@ -33,7 +33,9 @@
#ifndef GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_UTIL_H__
#define GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_UTIL_H__
#include "google/protobuf/type.pb.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/descriptor.h"
// Must be included last.
#include "google/protobuf/port_def.inc"
@ -49,6 +51,14 @@ class TypeResolver;
PROTOBUF_EXPORT TypeResolver* NewTypeResolverForDescriptorPool(
absl::string_view url_prefix, const DescriptorPool* pool);
// Performs a direct conversion from a descriptor to a type proto.
PROTOBUF_EXPORT google::protobuf::Type ConvertDescriptorToType(
absl::string_view url_prefix, const Descriptor& descriptor);
// Performs a direct conversion from an enum descriptor to a type proto.
PROTOBUF_EXPORT google::protobuf::Enum ConvertDescriptorToType(
const EnumDescriptor& descriptor);
} // namespace util
} // namespace protobuf
} // namespace google

@ -36,16 +36,19 @@
#include <string>
#include <vector>
#include "google/protobuf/any.pb.h"
#include "google/protobuf/type.pb.h"
#include "google/protobuf/wrappers.pb.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/util/type_resolver.h"
#include "google/protobuf/testing/googletest.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "google/protobuf/descriptor.h"
#include "google/protobuf/util/json_format_proto3.pb.h"
#include "google/protobuf/map_unittest.pb.h"
#include "google/protobuf/test_util.h"
#include "google/protobuf/unittest.pb.h"
#include "google/protobuf/unittest_custom_options.pb.h"
#include "google/protobuf/unittest_import.pb.h"
namespace google {
namespace protobuf {
@ -57,115 +60,118 @@ using google::protobuf::EnumValue;
using google::protobuf::Field;
using google::protobuf::Int32Value;
using google::protobuf::Option;
using google::protobuf::Syntax;
using google::protobuf::Type;
using google::protobuf::UInt64Value;
static const char kUrlPrefix[] = "type.googleapis.com";
class DescriptorPoolTypeResolverTest : public testing::Test {
public:
DescriptorPoolTypeResolverTest() {
resolver_.reset(NewTypeResolverForDescriptorPool(
kUrlPrefix, DescriptorPool::generated_pool()));
}
const Field* FindField(const Type& type, const std::string& name) {
for (int i = 0; i < type.fields_size(); ++i) {
const Field& field = type.fields(i);
if (field.name() == name) {
return &field;
}
const Field* FindField(const Type& type, absl::string_view name) {
for (const Field& field : type.fields()) {
if (field.name() == name) {
return &field;
}
return nullptr;
}
return nullptr;
}
bool HasField(const Type& type, Field::Cardinality cardinality,
Field::Kind kind, const std::string& name, int number) {
const Field* field = FindField(type, name);
if (field == nullptr) {
return false;
}
return field->cardinality() == cardinality && field->kind() == kind &&
field->number() == number;
bool HasField(const Type& type, Field::Cardinality cardinality,
Field::Kind kind, absl::string_view name, int number) {
const Field* field = FindField(type, name);
if (field == nullptr) {
return false;
}
return field->cardinality() == cardinality && field->kind() == kind &&
field->number() == number;
}
bool CheckFieldTypeUrl(const Type& type, const std::string& name,
const std::string& type_url) {
const Field* field = FindField(type, name);
if (field == nullptr) {
return false;
}
return field->type_url() == type_url;
bool CheckFieldTypeUrl(const Type& type, absl::string_view name,
absl::string_view type_url) {
const Field* field = FindField(type, name);
if (field == nullptr) {
return false;
}
return field->type_url() == type_url;
}
bool FieldInOneof(const Type& type, const std::string& name,
const std::string& oneof_name) {
const Field* field = FindField(type, name);
if (field == nullptr || field->oneof_index() <= 0 ||
field->oneof_index() > type.oneofs_size()) {
return false;
}
return type.oneofs(field->oneof_index() - 1) == oneof_name;
bool FieldInOneof(const Type& type, absl::string_view name,
absl::string_view oneof_name) {
const Field* field = FindField(type, name);
if (field == nullptr || field->oneof_index() <= 0 ||
field->oneof_index() > type.oneofs_size()) {
return false;
}
return type.oneofs(field->oneof_index() - 1) == oneof_name;
}
bool IsPacked(const Type& type, const std::string& name) {
const Field* field = FindField(type, name);
if (field == nullptr) {
return false;
}
return field->packed();
bool IsPacked(const Type& type, absl::string_view name) {
const Field* field = FindField(type, name);
if (field == nullptr) {
return false;
}
return field->packed();
}
const EnumValue* FindEnumValue(const Enum& type, const std::string& name) {
for (const EnumValue& value : type.enumvalue()) {
if (value.name() == name) {
return &value;
}
const EnumValue* FindEnumValue(const Enum& type, absl::string_view name) {
for (const EnumValue& value : type.enumvalue()) {
if (value.name() == name) {
return &value;
}
return nullptr;
}
return nullptr;
}
bool EnumHasValue(const Enum& type, const std::string& name, int number) {
const EnumValue* value = FindEnumValue(type, name);
return value != nullptr && value->number() == number;
bool EnumHasValue(const Enum& type, absl::string_view name, int number) {
const EnumValue* value = FindEnumValue(type, name);
if (value == nullptr) {
return false;
}
return value->number() == number;
}
bool HasBoolOption(const RepeatedPtrField<Option>& options,
const std::string& name, bool value) {
return HasOption<BoolValue>(options, name, value);
template <typename WrapperT, typename T>
bool HasOption(const RepeatedPtrField<Option>& options, absl::string_view name,
T value) {
for (const Option& option : options) {
if (option.name() == name) {
WrapperT wrapper;
if (option.value().UnpackTo(&wrapper) && wrapper.value() == value) {
return true;
}
}
}
return false;
}
bool HasInt32Option(const RepeatedPtrField<Option>& options,
const std::string& name, int32_t value) {
return HasOption<Int32Value>(options, name, value);
}
bool HasBoolOption(const RepeatedPtrField<Option>& options,
absl::string_view name, bool value) {
return HasOption<BoolValue>(options, name, value);
}
bool HasUInt64Option(const RepeatedPtrField<Option>& options,
const std::string& name, uint64_t value) {
return HasOption<UInt64Value>(options, name, value);
}
bool HasInt32Option(const RepeatedPtrField<Option>& options,
absl::string_view name, int32_t value) {
return HasOption<Int32Value>(options, name, value);
}
template <typename WrapperT, typename T>
bool HasOption(const RepeatedPtrField<Option>& options,
const std::string& name, T value) {
for (const Option& option : options) {
if (option.name() == name) {
WrapperT wrapper;
if (option.value().UnpackTo(&wrapper) && wrapper.value() == value) {
return true;
}
}
}
return false;
}
bool HasUInt64Option(const RepeatedPtrField<Option>& options,
absl::string_view name, uint64_t value) {
return HasOption<UInt64Value>(options, name, value);
}
std::string GetTypeUrl(std::string full_name) {
return kUrlPrefix + std::string("/") + full_name;
}
std::string GetTypeUrl(std::string full_name) {
return kUrlPrefix + std::string("/") + full_name;
}
template <typename T>
std::string GetTypeUrl() {
return GetTypeUrl(T::descriptor()->full_name());
template <typename T>
std::string GetTypeUrl() {
return GetTypeUrl(T::descriptor()->full_name());
}
class DescriptorPoolTypeResolverTest : public testing::Test {
public:
DescriptorPoolTypeResolverTest() {
resolver_.reset(NewTypeResolverForDescriptorPool(
kUrlPrefix, DescriptorPool::generated_pool()));
}
protected:
@ -432,6 +438,260 @@ TEST_F(DescriptorPoolTypeResolverTest, TestJsonName) {
EXPECT_EQ("@value", FindField(type, "value")->json_name());
}
class DescriptorPoolTypeResolverSyntaxTest : public testing::Test {
protected:
DescriptorPoolTypeResolverSyntaxTest()
: resolver_(NewTypeResolverForDescriptorPool(kUrlPrefix, &pool_)) {}
const FileDescriptor* BuildFile(
absl::string_view syntax,
absl::optional<absl::string_view> edition = absl::nullopt) {
FileDescriptorProto proto;
proto.set_package("test");
proto.set_name("foo");
proto.set_syntax(syntax);
if (edition.has_value()) {
proto.set_edition(*edition);
}
DescriptorProto* message = proto.add_message_type();
message->set_name("MyMessage");
const FileDescriptor* file = pool_.BuildFile(proto);
ABSL_CHECK(file != nullptr);
return file;
}
DescriptorPool pool_;
std::unique_ptr<TypeResolver> resolver_;
};
TEST_F(DescriptorPoolTypeResolverSyntaxTest, SyntaxProto2) {
const FileDescriptor* file = BuildFile("proto2");
ASSERT_EQ(FileDescriptor::SYNTAX_PROTO2, file->syntax());
Type type;
ASSERT_TRUE(
resolver_->ResolveMessageType(GetTypeUrl("test.MyMessage"), &type).ok());
EXPECT_EQ(type.syntax(), Syntax::SYNTAX_PROTO2);
EXPECT_EQ(type.edition(), "");
}
TEST_F(DescriptorPoolTypeResolverSyntaxTest, SyntaxProto3) {
const FileDescriptor* file = BuildFile("proto3");
ASSERT_EQ(FileDescriptor::SYNTAX_PROTO3, file->syntax());
Type type;
ASSERT_TRUE(
resolver_->ResolveMessageType(GetTypeUrl("test.MyMessage"), &type).ok());
// TODO(b/271206501) This should be proto3.
EXPECT_EQ(type.syntax(), Syntax::SYNTAX_PROTO2);
EXPECT_EQ(type.edition(), "");
}
TEST(ConvertDescriptorToTypeTest, TestAllTypes) {
Type type = ConvertDescriptorToType(
kUrlPrefix, *protobuf_unittest::TestAllTypes::GetDescriptor());
// Check all optional fields.
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_INT32,
"optional_int32", 1));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_INT64,
"optional_int64", 2));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_UINT32,
"optional_uint32", 3));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_UINT64,
"optional_uint64", 4));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SINT32,
"optional_sint32", 5));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SINT64,
"optional_sint64", 6));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_FIXED32,
"optional_fixed32", 7));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_FIXED64,
"optional_fixed64", 8));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SFIXED32,
"optional_sfixed32", 9));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_SFIXED64,
"optional_sfixed64", 10));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_FLOAT,
"optional_float", 11));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_DOUBLE,
"optional_double", 12));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_BOOL,
"optional_bool", 13));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_STRING,
"optional_string", 14));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_BYTES,
"optional_bytes", 15));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_GROUP,
"optionalgroup", 16));
EXPECT_TRUE(CheckFieldTypeUrl(
type, "optionalgroup",
GetTypeUrl<protobuf_unittest::TestAllTypes::OptionalGroup>()));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_MESSAGE,
"optional_nested_message", 18));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_MESSAGE,
"optional_foreign_message", 19));
EXPECT_TRUE(CheckFieldTypeUrl(
type, "optional_nested_message",
GetTypeUrl<protobuf_unittest::TestAllTypes::NestedMessage>()));
EXPECT_TRUE(CheckFieldTypeUrl(type, "optional_foreign_message",
GetTypeUrl<protobuf_unittest::ForeignMessage>()));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_ENUM,
"optional_nested_enum", 21));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_ENUM,
"optional_foreign_enum", 22));
EXPECT_TRUE(
CheckFieldTypeUrl(type, "optional_nested_enum",
GetTypeUrl("protobuf_unittest.TestAllTypes.NestedEnum")));
EXPECT_TRUE(CheckFieldTypeUrl(type, "optional_foreign_enum",
GetTypeUrl("protobuf_unittest.ForeignEnum")));
// Check all repeated fields.
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_INT32,
"repeated_int32", 31));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_INT64,
"repeated_int64", 32));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_UINT32,
"repeated_uint32", 33));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_UINT64,
"repeated_uint64", 34));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SINT32,
"repeated_sint32", 35));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SINT64,
"repeated_sint64", 36));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_FIXED32,
"repeated_fixed32", 37));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_FIXED64,
"repeated_fixed64", 38));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SFIXED32,
"repeated_sfixed32", 39));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_SFIXED64,
"repeated_sfixed64", 40));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_FLOAT,
"repeated_float", 41));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_DOUBLE,
"repeated_double", 42));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_BOOL,
"repeated_bool", 43));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_STRING,
"repeated_string", 44));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_BYTES,
"repeated_bytes", 45));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_GROUP,
"repeatedgroup", 46));
EXPECT_TRUE(CheckFieldTypeUrl(
type, "repeatedgroup",
GetTypeUrl<protobuf_unittest::TestAllTypes::RepeatedGroup>()));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_MESSAGE,
"repeated_nested_message", 48));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_MESSAGE,
"repeated_foreign_message", 49));
EXPECT_TRUE(CheckFieldTypeUrl(
type, "repeated_nested_message",
GetTypeUrl<protobuf_unittest::TestAllTypes::NestedMessage>()));
EXPECT_TRUE(CheckFieldTypeUrl(type, "repeated_foreign_message",
GetTypeUrl<protobuf_unittest::ForeignMessage>()));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_ENUM,
"repeated_nested_enum", 51));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_ENUM,
"repeated_foreign_enum", 52));
EXPECT_TRUE(
CheckFieldTypeUrl(type, "repeated_nested_enum",
GetTypeUrl("protobuf_unittest.TestAllTypes.NestedEnum")));
EXPECT_TRUE(CheckFieldTypeUrl(type, "repeated_foreign_enum",
GetTypeUrl("protobuf_unittest.ForeignEnum")));
}
TEST(ConvertDescriptorToTypeTest, ConvertDescriptorToTypePacked) {
Type type = ConvertDescriptorToType(
kUrlPrefix, *protobuf_unittest::TestPackedTypes::GetDescriptor());
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_INT32,
"packed_int32", 90));
EXPECT_TRUE(IsPacked(type, "packed_int32"));
}
TEST(ConvertDescriptorToTypeTest, TestOneof) {
Type type = ConvertDescriptorToType(
kUrlPrefix, *protobuf_unittest::TestAllTypes::GetDescriptor());
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_UINT32,
"oneof_uint32", 111));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_MESSAGE,
"oneof_nested_message", 112));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_STRING,
"oneof_string", 113));
EXPECT_TRUE(HasField(type, Field::CARDINALITY_OPTIONAL, Field::TYPE_BYTES,
"oneof_bytes", 114));
EXPECT_TRUE(FieldInOneof(type, "oneof_uint32", "oneof_field"));
EXPECT_TRUE(FieldInOneof(type, "oneof_nested_message", "oneof_field"));
EXPECT_TRUE(FieldInOneof(type, "oneof_string", "oneof_field"));
EXPECT_TRUE(FieldInOneof(type, "oneof_bytes", "oneof_field"));
}
TEST(ConvertDescriptorToTypeTest, TestMap) {
Type type = ConvertDescriptorToType(
kUrlPrefix, *protobuf_unittest::TestMap::GetDescriptor());
EXPECT_TRUE(HasField(type, Field::CARDINALITY_REPEATED, Field::TYPE_MESSAGE,
"map_int32_int32", 1));
EXPECT_TRUE(CheckFieldTypeUrl(
type, "map_int32_int32",
GetTypeUrl("protobuf_unittest.TestMap.MapInt32Int32Entry")));
}
TEST(ConvertDescriptorToTypeTest, TestCustomOptions) {
Type type = ConvertDescriptorToType(
kUrlPrefix,
*protobuf_unittest::TestMessageWithCustomOptions::GetDescriptor());
EXPECT_TRUE(
HasInt32Option(type.options(), "protobuf_unittest.message_opt1", -56));
const Field* field = FindField(type, "field1");
ASSERT_TRUE(field != nullptr);
EXPECT_TRUE(HasUInt64Option(field->options(), "protobuf_unittest.field_opt1",
8765432109));
}
TEST(ConvertDescriptorToTypeTest, TestJsonName) {
Type type = ConvertDescriptorToType(
kUrlPrefix, *protobuf_unittest::TestAllTypes::GetDescriptor());
EXPECT_EQ("optionalInt32", FindField(type, "optional_int32")->json_name());
type = ConvertDescriptorToType(kUrlPrefix,
*proto3::TestCustomJsonName::GetDescriptor());
EXPECT_EQ("@value", FindField(type, "value")->json_name());
}
TEST(ConvertDescriptorToTypeTest, TestEnum) {
Enum type = ConvertDescriptorToType(
*protobuf_unittest::TestAllTypes::NestedEnum_descriptor());
EnumHasValue(type, "FOO", 1);
EnumHasValue(type, "BAR", 2);
EnumHasValue(type, "BAZ", 3);
EnumHasValue(type, "NEG", -1);
}
TEST(ConvertDescriptorToTypeTest, TestCustomEnumOptions) {
Enum type = ConvertDescriptorToType(
*protobuf_unittest::TestMessageWithCustomOptions::AnEnum_descriptor());
ASSERT_TRUE(
HasInt32Option(type.options(), "protobuf_unittest.enum_opt1", -789));
const EnumValue* value = FindEnumValue(type, "ANENUM_VAL2");
ASSERT_TRUE(value != nullptr);
ASSERT_TRUE(
HasInt32Option(value->options(), "protobuf_unittest.enum_value_opt1", 123));
}
} // namespace
} // namespace util
} // namespace protobuf

Loading…
Cancel
Save