| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #ifndef GOOGLE_PROTOBUF_JSON_INTERNAL_DESCRIPTOR_TRAITS_H__ |
| #define GOOGLE_PROTOBUF_JSON_INTERNAL_DESCRIPTOR_TRAITS_H__ |
| |
| #include <array> |
| #include <cfloat> |
| #include <cmath> |
| #include <cstdint> |
| #include <cstring> |
| #include <limits> |
| #include <string> |
| #include <utility> |
| |
| #include "google/protobuf/type.pb.h" |
| #include "google/protobuf/descriptor.h" |
| #include "google/protobuf/dynamic_message.h" |
| #include "google/protobuf/message.h" |
| #include "absl/algorithm/container.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "google/protobuf/json/internal/lexer.h" |
| #include "google/protobuf/json/internal/untyped_message.h" |
| #include "google/protobuf/stubs/status_macros.h" |
| |
| |
| // Must be included last. |
| #include "google/protobuf/port_def.inc" |
| |
| // Traits for working with descriptor.proto and type.proto generically. |
| |
| namespace google { |
| namespace protobuf { |
| namespace json_internal { |
| enum class MessageType { |
| kNotWellKnown, |
| kAny, |
| kWrapper, |
| kStruct, |
| kList, |
| kValue, |
| kNull, |
| kTimestamp, |
| kDuration, |
| kFieldMask, |
| }; |
| |
| inline MessageType ClassifyMessage(absl::string_view name) { |
| constexpr absl::string_view kWellKnownPkg = "google.protobuf."; |
| if (!absl::StartsWith(name, kWellKnownPkg)) { |
| return MessageType::kNotWellKnown; |
| } |
| name = name.substr(kWellKnownPkg.size()); |
| |
| switch (name.size()) { |
| case 3: |
| if (name == "Any") { |
| return MessageType::kAny; |
| } |
| break; |
| case 5: |
| if (name == "Value") { |
| return MessageType::kValue; |
| } |
| break; |
| case 6: |
| if (name == "Struct") { |
| return MessageType::kStruct; |
| } |
| break; |
| case 8: |
| if (name == "Duration") { |
| return MessageType::kDuration; |
| } |
| break; |
| case 9: |
| if (name == "BoolValue") { |
| return MessageType::kWrapper; |
| } |
| if (name == "NullValue") { |
| return MessageType::kNull; |
| } |
| if (name == "ListValue") { |
| return MessageType::kList; |
| } |
| if (name == "Timestamp") { |
| return MessageType::kTimestamp; |
| } |
| if (name == "FieldMask") { |
| return MessageType::kFieldMask; |
| } |
| break; |
| case 10: |
| if (name == "BytesValue" || name == "FloatValue" || |
| name == "Int32Value" || name == "Int64Value") { |
| return MessageType::kWrapper; |
| } |
| break; |
| case 11: |
| if (name == "DoubleValue" || name == "StringValue" || |
| name == "UInt32Value" || name == "UInt64Value") { |
| return MessageType::kWrapper; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return MessageType::kNotWellKnown; |
| } |
| |
| // Helper alias templates to avoid needing to write `typename` in function |
| // signatures. |
| template <typename Traits> |
| using Field = typename Traits::Field; |
| template <typename Traits> |
| using Desc = typename Traits::Desc; |
| |
| // Traits for proto2-ish descriptors. |
| struct Proto2Descriptor { |
| // A descriptor for introspecting the fields of a message type. |
| // |
| // Desc<Traits> needs to be handled through a const Desc& in most (but not |
| // all, in the case of ResolverTraits) cases, so we do not include the const* |
| // annotation on this type. |
| using Desc = Descriptor; |
| |
| // A field descriptor for introspecting a single field. |
| // |
| // Field<Traits> is always copyable, so this can be a pointer directly. |
| using Field = const FieldDescriptor*; |
| |
| /// Functions for working with descriptors. /// |
| |
| static absl::string_view TypeName(const Desc& d) { return d.full_name(); } |
| |
| static absl::optional<Field> FieldByNumber(const Desc& d, int32_t number) { |
| if (const auto* field = d.FindFieldByNumber(number)) { |
| return field; |
| } |
| return absl::nullopt; |
| } |
| |
| static Field MustHaveField(const Desc& d, int32_t number, |
| JsonLocation::SourceLocation loc = |
| JsonLocation::SourceLocation::current()) { |
| auto f = FieldByNumber(d, number); |
| if (!f.has_value()) { |
| GOOGLE_LOG(FATAL) |
| << absl::StrFormat( |
| "%s has, by definition, a field numbered %d, but it could not " |
| "be " |
| "looked up; this is a bug", |
| TypeName(d), number); |
| } |
| return *f; |
| } |
| |
| static absl::optional<Field> FieldByName(const Desc& d, |
| absl::string_view name) { |
| if (const auto* field = d.FindFieldByCamelcaseName(name)) { |
| return field; |
| } |
| |
| if (const auto* field = d.FindFieldByName(name)) { |
| return field; |
| } |
| |
| for (int i = 0; i < d.field_count(); ++i) { |
| const auto* field = d.field(i); |
| if (field->has_json_name() && field->json_name() == name) { |
| return field; |
| } |
| } |
| |
| return absl::nullopt; |
| } |
| |
| static Field KeyField(const Desc& d) { return d.map_key(); } |
| |
| static Field ValueField(const Desc& d) { return d.map_value(); } |
| |
| static size_t FieldCount(const Desc& d) { return d.field_count(); } |
| |
| static Field FieldByIndex(const Desc& d, size_t idx) { return d.field(idx); } |
| |
| static absl::optional<Field> ExtensionByName(const Desc& d, |
| absl::string_view name) { |
| auto* field = d.file()->pool()->FindExtensionByName(name); |
| if (field == nullptr) { |
| return absl::nullopt; |
| } |
| return field; |
| } |
| |
| /// Functions for introspecting fields. /// |
| |
| static absl::string_view FieldName(Field f) { return f->name(); } |
| static absl::string_view FieldJsonName(Field f) { |
| return f->has_json_name() ? f->json_name() : f->camelcase_name(); |
| } |
| static absl::string_view FieldFullName(Field f) { return f->full_name(); } |
| |
| static absl::string_view FieldTypeName(Field f) { |
| if (f->type() == FieldDescriptor::TYPE_MESSAGE) { |
| return f->message_type()->full_name(); |
| } |
| if (f->type() == FieldDescriptor::TYPE_ENUM) { |
| return f->enum_type()->full_name(); |
| } |
| return ""; |
| } |
| |
| static FieldDescriptor::Type FieldType(Field f) { return f->type(); } |
| |
| static int32_t FieldNumber(Field f) { return f->number(); } |
| |
| static bool Is32Bit(Field f) { |
| switch (f->cpp_type()) { |
| case FieldDescriptor::CPPTYPE_UINT32: |
| case FieldDescriptor::CPPTYPE_INT32: |
| case FieldDescriptor::CPPTYPE_ENUM: |
| case FieldDescriptor::CPPTYPE_FLOAT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static const Desc& ContainingType(Field f) { return *f->containing_type(); } |
| |
| static bool IsMap(Field f) { return f->is_map(); } |
| |
| static bool IsRepeated(Field f) { return f->is_repeated(); } |
| |
| static bool IsOptional(Field f) { return f->has_presence(); } |
| |
| static bool IsExtension(Field f) { return f->is_extension(); } |
| |
| static bool IsOneof(Field f) { return f->containing_oneof() != nullptr; } |
| |
| static absl::StatusOr<int32_t> EnumNumberByName(Field f, |
| absl::string_view name, |
| bool case_insensitive) { |
| if (case_insensitive) { |
| for (int i = 0; i < f->enum_type()->value_count(); ++i) { |
| const auto* ev = f->enum_type()->value(i); |
| if (absl::EqualsIgnoreCase(name, ev->name())) { |
| return ev->number(); |
| } |
| } |
| return absl::InvalidArgumentError( |
| absl::StrFormat("unknown enum value: '%s'", name)); |
| } |
| |
| if (const auto* ev = f->enum_type()->FindValueByName(name)) { |
| return ev->number(); |
| } |
| |
| return absl::InvalidArgumentError( |
| absl::StrFormat("unknown enum value: '%s'", name)); |
| } |
| |
| static absl::StatusOr<std::string> EnumNameByNumber(Field f, int32_t number) { |
| if (const auto* ev = f->enum_type()->FindValueByNumber(number)) { |
| return ev->name(); |
| } |
| return absl::InvalidArgumentError( |
| absl::StrFormat("unknown enum number: '%d'", number)); |
| } |
| |
| // Looks up the corresponding Desc for `f`'s type, if there is one, and |
| // calls `body` with it. |
| // |
| // This needs to have this funny callback API since whether or not the |
| // Descriptor equivalent is an owning type depends on the trait. |
| template <typename F> |
| static absl::Status WithFieldType(Field f, F body) { |
| return body(*f->message_type()); |
| } |
| |
| // Like WithFieldType, but using dynamic lookup by type URL. |
| template <typename F> |
| static absl::Status WithDynamicType(const Desc& desc, |
| const std::string& type_url, F body) { |
| size_t slash = type_url.rfind('/'); |
| if (slash == absl::string_view::npos || slash == 0) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "@type must contain at least one / and a nonempty host; got: ", |
| type_url)); |
| } |
| absl::string_view type_name(type_url); |
| type_name = type_name.substr(slash + 1); |
| |
| const Descriptor* dyn_desc = |
| desc.file()->pool()->FindMessageTypeByName(type_name); |
| if (dyn_desc == nullptr) { |
| return absl::InvalidArgumentError( |
| absl::StrFormat("could not find @type '%s'", type_url)); |
| } |
| |
| return body(*dyn_desc); |
| } |
| }; |
| |
| // Traits for proto3-ish deserialization. |
| // |
| // See Proto2Descriptor for API docs. |
| struct Proto3Type { |
| using Desc = ResolverPool::Message; |
| using Field = const ResolverPool::Field*; |
| |
| /// Functions for working with descriptors. /// |
| static absl::string_view TypeName(const Desc& d) { return d.proto().name(); } |
| |
| static absl::optional<Field> FieldByNumber(const Desc& d, int32_t number) { |
| const auto* f = d.FindField(number); |
| return f == nullptr ? absl::nullopt : absl::make_optional(f); |
| } |
| |
| static Field MustHaveField(const Desc& d, int32_t number, |
| JsonLocation::SourceLocation loc = |
| JsonLocation::SourceLocation::current()) { |
| auto f = FieldByNumber(d, number); |
| if (!f.has_value()) { |
| GOOGLE_LOG(FATAL) |
| << absl::StrFormat( |
| "%s has, by definition, a field numbered %d, but it could not " |
| "be " |
| "looked up; this is a bug", |
| TypeName(d), number); |
| } |
| return *f; |
| } |
| |
| static absl::optional<Field> FieldByName(const Desc& d, |
| absl::string_view name) { |
| const auto* f = d.FindField(name); |
| return f == nullptr ? absl::nullopt : absl::make_optional(f); |
| } |
| |
| static Field KeyField(const Desc& d) { return &d.FieldsByIndex()[0]; } |
| |
| static Field ValueField(const Desc& d) { return &d.FieldsByIndex()[1]; } |
| |
| static size_t FieldCount(const Desc& d) { return d.proto().fields_size(); } |
| |
| static Field FieldByIndex(const Desc& d, size_t idx) { |
| return &d.FieldsByIndex()[idx]; |
| } |
| |
| static absl::optional<Field> ExtensionByName(const Desc& d, |
| absl::string_view name) { |
| // type.proto cannot represent extensions, so this function always |
| // fails. |
| return absl::nullopt; |
| } |
| |
| /// Functions for introspecting fields. /// |
| |
| static absl::string_view FieldName(Field f) { return f->proto().name(); } |
| static absl::string_view FieldJsonName(Field f) { |
| return f->proto().json_name(); |
| } |
| static absl::string_view FieldFullName(Field f) { return f->proto().name(); } |
| |
| static absl::string_view FieldTypeName(Field f) { |
| absl::string_view url = f->proto().type_url(); |
| |
| // If there is no slash, `slash` is string_view::npos, which is guaranteed |
| // to be -1. |
| size_t slash = url.rfind('/'); |
| return url.substr(slash + 1); |
| } |
| |
| static FieldDescriptor::Type FieldType(Field f) { |
| // The descriptor.proto and type.proto field type enums are required to be |
| // the same, so we leverage this. |
| return static_cast<FieldDescriptor::Type>(f->proto().kind()); |
| } |
| |
| static int32_t FieldNumber(Field f) { return f->proto().number(); } |
| |
| static bool Is32Bit(Field f) { |
| switch (f->proto().kind()) { |
| case google::protobuf::Field::TYPE_INT32: |
| case google::protobuf::Field::TYPE_SINT32: |
| case google::protobuf::Field::TYPE_UINT32: |
| case google::protobuf::Field::TYPE_FIXED32: |
| case google::protobuf::Field::TYPE_SFIXED32: |
| case google::protobuf::Field::TYPE_FLOAT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static const Desc& ContainingType(Field f) { return f->parent(); } |
| static bool IsMap(Field f) { |
| if (f->proto().kind() != google::protobuf::Field::TYPE_MESSAGE) { |
| return false; |
| } |
| |
| bool value = false; |
| (void)WithFieldType(f, [&value](const Desc& desc) { |
| value = absl::c_any_of(desc.proto().options(), [&](auto& option) { |
| return option.name() == "map_entry"; |
| }); |
| return absl::OkStatus(); |
| }); |
| return value; |
| } |
| |
| static bool IsRepeated(Field f) { |
| return f->proto().cardinality() == |
| google::protobuf::Field::CARDINALITY_REPEATED; |
| } |
| |
| static bool IsOptional(Field f) { |
| return f->proto().cardinality() == |
| google::protobuf::Field::CARDINALITY_OPTIONAL; |
| } |
| |
| static bool IsExtension(Field f) { return false; } |
| |
| static bool IsOneof(Field f) { return f->proto().oneof_index() != 0; } |
| |
| static absl::StatusOr<int32_t> EnumNumberByName(Field f, |
| absl::string_view name, |
| bool case_insensitive) { |
| auto e = f->EnumType(); |
| RETURN_IF_ERROR(e.status()); |
| |
| for (const auto& ev : (**e).proto().enumvalue()) { |
| if (case_insensitive) { |
| // Two ifs to avoid doing operator== twice if the names are not equal. |
| if (absl::EqualsIgnoreCase(ev.name(), name)) { |
| return ev.number(); |
| } |
| } else if (ev.name() == name) { |
| return ev.number(); |
| } |
| } |
| return absl::InvalidArgumentError( |
| absl::StrFormat("unknown enum value: '%s'", name)); |
| } |
| |
| static absl::StatusOr<std::string> EnumNameByNumber(Field f, int32_t number) { |
| auto e = f->EnumType(); |
| RETURN_IF_ERROR(e.status()); |
| |
| for (const auto& ev : (**e).proto().enumvalue()) { |
| if (ev.number() == number) { |
| return ev.name(); |
| } |
| } |
| return absl::InvalidArgumentError( |
| absl::StrFormat("unknown enum number: '%d'", number)); |
| } |
| |
| template <typename F> |
| static absl::Status WithFieldType(Field f, F body) { |
| auto m = f->MessageType(); |
| RETURN_IF_ERROR(m.status()); |
| return body(**m); |
| } |
| |
| template <typename F> |
| static absl::Status WithDynamicType(const Desc& desc, |
| const std::string& type_url, F body) { |
| auto dyn_desc = desc.pool()->FindMessage(type_url); |
| RETURN_IF_ERROR(dyn_desc.status()); |
| return body(**dyn_desc); |
| } |
| }; |
| } // namespace json_internal |
| } // namespace protobuf |
| } // namespace google |
| |
| #include "google/protobuf/port_undef.inc" |
| #endif // GOOGLE_PROTOBUF_JSON_INTERNAL_DESCRIPTOR_TRAITS_INTERNAL_H__ |