blob: 6ccdc2ebccae3a86ad3c2bdf50b63b5b69bf5228 [file] [log] [blame]
// Copyright 2020 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GRPC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H
#define GRPC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H
#include <grpc/support/port_platform.h>
#include <cstdint>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include "absl/meta/type_traits.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/json/json_args.h"
// Provides a means to load JSON objects into C++ objects, with the aim of
// minimizing object code size.
//
// Usage:
// Given struct Foo:
// struct Foo {
// int a;
// int b;
// };
// We add a static JsonLoader() method to Foo to declare how to load the
// object from JSON, and an optional JsonPostLoad() method to do any
// necessary post-processing:
// struct Foo {
// int a;
// int b;
// static const JsonLoaderInterface* JsonLoader() {
// // Note: Field names must be string constants; they are not copied.
// static const auto* loader = JsonObjectLoader<Foo>()
// .Field("a", &Foo::a)
// .Field("b", &Foo::b)
// .Finish();
// return loader;
// }
// // Optional; omit if no post-processing needed.
// void JsonPostLoad(const Json& source, ErrorList* errors) { ++a; }
// };
// Now we can load Foo objects from JSON:
// absl::StatusOr<Foo> foo = LoadFromJson<Foo>(json);
namespace grpc_core {
// A list of errors that occurred during JSON parsing.
// If a non-empty list occurs during parsing, the parsing failed.
class ErrorList {
public:
// Record that we're reading some field.
void PushField(absl::string_view ext) GPR_ATTRIBUTE_NOINLINE;
// Record that we've finished reading that field.
void PopField() GPR_ATTRIBUTE_NOINLINE;
// Record that we've encountered an error.
void AddError(absl::string_view error) GPR_ATTRIBUTE_NOINLINE;
// Returns true if the current field has errors.
bool FieldHasErrors() const GPR_ATTRIBUTE_NOINLINE;
// Returns the resulting status of parsing.
absl::Status status() const;
// Return true if there are no errors.
bool ok() const { return field_errors_.empty(); }
size_t size() const { return field_errors_.size(); }
private:
// TODO(roth): If we don't actually have any fields for which we
// report more than one error, simplify this data structure.
std::map<std::string /*field_name*/, std::vector<std::string>> field_errors_;
std::vector<std::string> fields_;
};
// Note that we're reading a field, and remove it at the end of the scope.
class ScopedField {
public:
ScopedField(ErrorList* error_list, absl::string_view field_name)
: error_list_(error_list) {
error_list_->PushField(field_name);
}
~ScopedField() { error_list_->PopField(); }
private:
ErrorList* error_list_;
};
namespace json_detail {
// An un-typed JSON loader.
class LoaderInterface {
public:
// Convert json value to whatever type we're loading at dst.
// If errors occur, add them to error_list.
virtual void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const = 0;
protected:
virtual ~LoaderInterface() = default;
};
// Loads a scalar (string or number).
class LoadScalar : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override;
protected:
~LoadScalar() override = default;
private:
// true if we're loading a number, false if we're loading a string.
// We use a virtual function to store this decision in a vtable instead of
// needing an instance variable.
virtual bool IsNumber() const = 0;
virtual void LoadInto(const std::string& json, void* dst,
ErrorList* errors) const = 0;
};
// Load a string.
class LoadString : public LoadScalar {
protected:
~LoadString() override = default;
private:
bool IsNumber() const override;
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override;
};
// Load a Duration.
class LoadDuration : public LoadScalar {
protected:
~LoadDuration() override = default;
private:
bool IsNumber() const override;
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override;
};
// Load a number.
class LoadNumber : public LoadScalar {
protected:
~LoadNumber() override = default;
private:
bool IsNumber() const override;
};
// Load a signed number of type T.
template <typename T>
class TypedLoadSignedNumber : public LoadNumber {
protected:
~TypedLoadSignedNumber() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtoi(value, static_cast<T*>(dst))) {
errors->AddError("failed to parse number");
}
}
};
// Load an unsigned number of type T.
template <typename T>
class TypedLoadUnsignedNumber : public LoadNumber {
protected:
~TypedLoadUnsignedNumber() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtoi(value, static_cast<T*>(dst))) {
errors->AddError("failed to parse non-negative number");
}
}
};
// Load a float.
class LoadFloat : public LoadNumber {
protected:
~LoadFloat() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtof(value, static_cast<float*>(dst))) {
errors->AddError("failed to parse floating-point number");
}
}
};
// Load a double.
class LoadDouble : public LoadNumber {
protected:
~LoadDouble() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtod(value, static_cast<double*>(dst))) {
errors->AddError("failed to parse floating-point number");
}
}
};
// Load a bool.
class LoadBool : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
ErrorList* errors) const override;
};
// Loads an unprocessed JSON object value.
class LoadUnprocessedJsonObject : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
ErrorList* errors) const override;
};
// Load a vector of some type.
class LoadVector : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override;
protected:
~LoadVector() override = default;
private:
virtual void LoadOne(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const = 0;
};
// Load a map of string->some type.
class LoadMap : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override;
protected:
~LoadMap() override = default;
private:
virtual void LoadOne(const Json& json, const JsonArgs& args,
const std::string& name, void* dst,
ErrorList* errors) const = 0;
};
// Fetch a LoaderInterface for some type.
template <typename T>
const LoaderInterface* LoaderForType();
// AutoLoader implements LoaderInterface for a type.
// The default asks the type for its LoaderInterface and then uses that.
// Classes that load from objects should provide a:
// static const JsonLoaderInterface* JsonLoader();
template <typename T>
class AutoLoader final : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
T::JsonLoader(args)->LoadInto(json, args, dst, errors);
}
};
// Specializations of AutoLoader for basic types.
template <>
class AutoLoader<std::string> final : public LoadString {};
template <>
class AutoLoader<Duration> final : public LoadDuration {};
template <>
class AutoLoader<int32_t> final : public TypedLoadSignedNumber<int32_t> {};
template <>
class AutoLoader<int64_t> final : public TypedLoadSignedNumber<int64_t> {};
template <>
class AutoLoader<uint32_t> final : public TypedLoadUnsignedNumber<uint32_t> {};
template <>
class AutoLoader<uint64_t> final : public TypedLoadUnsignedNumber<uint64_t> {};
template <>
class AutoLoader<float> final : public LoadFloat {};
template <>
class AutoLoader<double> final : public LoadDouble {};
template <>
class AutoLoader<bool> final : public LoadBool {};
template <>
class AutoLoader<Json::Object> final : public LoadUnprocessedJsonObject {};
// Specializations of AutoLoader for vectors.
template <typename T>
class AutoLoader<std::vector<T>> final : public LoadVector {
private:
void LoadOne(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const final {
auto* vec = static_cast<std::vector<T>*>(dst);
T value{};
LoaderForType<T>()->LoadInto(json, args, &value, errors);
vec->push_back(std::move(value));
}
};
// Specializations of AutoLoader for maps.
template <typename T>
class AutoLoader<std::map<std::string, T>> final : public LoadMap {
private:
void LoadOne(const Json& json, const JsonArgs& args, const std::string& name,
void* dst, ErrorList* errors) const final {
auto* map = static_cast<std::map<std::string, T>*>(dst);
T value{};
LoaderForType<T>()->LoadInto(json, args, &value, errors);
map->emplace(name, std::move(value));
}
};
// Specializations of AutoLoader for absl::optional<>.
template <typename T>
class AutoLoader<absl::optional<T>> final : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
if (json.type() == Json::Type::JSON_NULL) return;
auto* opt = static_cast<absl::optional<T>*>(dst);
opt->emplace();
LoaderForType<T>()->LoadInto(json, args, &**opt, errors);
}
};
// Implementation of aforementioned LoaderForType.
// Simply keeps a static AutoLoader<T> and returns a pointer to that.
template <typename T>
const LoaderInterface* LoaderForType() {
static const auto* loader = new AutoLoader<T>();
return loader;
}
// Element describes one typed field to be loaded from a JSON object.
struct Element {
Element() = default;
template <typename A, typename B>
Element(const char* name, bool optional, B A::*p,
const LoaderInterface* loader, const char* enable_key)
: loader(loader),
member_offset(static_cast<uint16_t>(
reinterpret_cast<uintptr_t>(&(static_cast<A*>(nullptr)->*p)))),
optional(optional),
name(name),
enable_key(enable_key) {}
// The loader for this field.
const LoaderInterface* loader;
// Offset into the destination object to store the field.
uint16_t member_offset;
// Is this field optional?
bool optional;
// The name of the field.
const char* name;
// The key to use with JsonArgs to see if this field is enabled.
const char* enable_key;
};
// Vec<T, kSize> provides a constant array type that can be appended to by
// copying. It's setup so that most compilers can optimize away all of its
// operations.
template <typename T, size_t kSize>
class Vec {
public:
Vec(const Vec<T, kSize - 1>& other, const T& new_value) {
for (size_t i = 0; i < other.size(); i++) values_[i] = other.data()[i];
values_[kSize - 1] = new_value;
}
const T* data() const { return values_; }
size_t size() const { return kSize; }
private:
T values_[kSize];
};
template <typename T>
class Vec<T, 0> {
public:
const T* data() const { return nullptr; }
size_t size() const { return 0; }
};
// Given a list of elements, and a destination object, load the elements into
// the object from some parsed JSON.
// Returns false if the JSON object was not of type Json::Type::OBJECT.
bool LoadObject(const Json& json, const JsonArgs& args, const Element* elements,
size_t num_elements, void* dst, ErrorList* errors);
// Adaptor type - takes a compile time computed list of elements and implements
// LoaderInterface by calling LoadObject.
template <typename T, size_t kElemCount, typename Hidden = void>
class FinishedJsonObjectLoader final : public LoaderInterface {
public:
explicit FinishedJsonObjectLoader(const Vec<Element, kElemCount>& elements)
: elements_(elements) {}
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
LoadObject(json, args, elements_.data(), elements_.size(), dst, errors);
}
private:
GPR_NO_UNIQUE_ADDRESS Vec<Element, kElemCount> elements_;
};
// Specialization for when the object has a JsonPostLoad function exposed.
template <typename T, size_t kElemCount>
class FinishedJsonObjectLoader<T, kElemCount,
absl::void_t<decltype(&T::JsonPostLoad)>>
final : public LoaderInterface {
public:
explicit FinishedJsonObjectLoader(const Vec<Element, kElemCount>& elements)
: elements_(elements) {}
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
// Call JsonPostLoad() only if json is a JSON object.
if (LoadObject(json, args, elements_.data(), elements_.size(), dst,
errors)) {
static_cast<T*>(dst)->JsonPostLoad(json, args, errors);
}
}
private:
GPR_NO_UNIQUE_ADDRESS Vec<Element, kElemCount> elements_;
};
// Builder type for JSON object loaders.
// Concatenate fields with Field, OptionalField, and then call Finish to obtain
// an object that implements LoaderInterface.
template <typename T, size_t kElemCount = 0>
class JsonObjectLoader final {
public:
JsonObjectLoader() {
static_assert(kElemCount == 0,
"Only initial loader step can have kElemCount==0.");
}
FinishedJsonObjectLoader<T, kElemCount>* Finish() const {
return new FinishedJsonObjectLoader<T, kElemCount>(elements_);
}
template <typename U>
JsonObjectLoader<T, kElemCount + 1> Field(
const char* name, U T::*p, const char* enable_key = nullptr) const {
return Field(name, false, p, enable_key);
}
template <typename U>
JsonObjectLoader<T, kElemCount + 1> OptionalField(
const char* name, U T::*p, const char* enable_key = nullptr) const {
return Field(name, true, p, enable_key);
}
JsonObjectLoader(const Vec<Element, kElemCount - 1>& elements,
Element new_element)
: elements_(elements, new_element) {}
private:
template <typename U>
JsonObjectLoader<T, kElemCount + 1> Field(const char* name, bool optional,
U T::*p,
const char* enable_key) const {
return JsonObjectLoader<T, kElemCount + 1>(
elements_, Element(name, optional, p, LoaderForType<U>(), enable_key));
}
GPR_NO_UNIQUE_ADDRESS Vec<Element, kElemCount> elements_;
};
const Json* GetJsonObjectField(const Json::Object& json,
absl::string_view field, ErrorList* errors,
bool required);
} // namespace json_detail
template <typename T>
using JsonObjectLoader = json_detail::JsonObjectLoader<T>;
using JsonLoaderInterface = json_detail::LoaderInterface;
template <typename T>
absl::StatusOr<T> LoadFromJson(const Json& json,
const JsonArgs& args = JsonArgs()) {
ErrorList error_list;
T result{};
json_detail::LoaderForType<T>()->LoadInto(json, args, &result, &error_list);
if (!error_list.ok()) return error_list.status();
return std::move(result);
}
template <typename T>
T LoadFromJson(const Json& json, const JsonArgs& args, ErrorList* error_list) {
T result{};
json_detail::LoaderForType<T>()->LoadInto(json, args, &result, error_list);
return result;
}
template <typename T>
absl::optional<T> LoadJsonObjectField(const Json::Object& json,
const JsonArgs& args,
absl::string_view field,
ErrorList* errors, bool required = true) {
ScopedField error_field(errors, absl::StrCat(".", field));
const Json* field_json =
json_detail::GetJsonObjectField(json, field, errors, required);
if (field_json == nullptr) return absl::nullopt;
T result{};
size_t starting_error_size = errors->size();
json_detail::LoaderForType<T>()->LoadInto(*field_json, args, &result, errors);
if (errors->size() > starting_error_size) return absl::nullopt;
return std::move(result);
}
} // namespace grpc_core
#endif // GRPC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H