blob: 2d08f3a927414689633aa00d40d23832e020d006 [file] [log] [blame]
// 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__