// 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_SRC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H
#define GRPC_SRC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H

#include <grpc/support/port_platform.h>

#include <cstdint>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "absl/meta/type_traits.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/no_destruct.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.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(const JsonArgs& args) {
//       // 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, const JsonArgs& args,
//                       ValidationErrors* errors) {
//       ++a;
//     }
//   };
// Now we can load Foo objects from JSON:
//   absl::StatusOr<Foo> foo = LoadFromJson<Foo>(json);
namespace grpc_core {

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 errors.
  virtual void LoadInto(const Json& json, const JsonArgs& args, void* dst,
                        ValidationErrors* errors) const = 0;

 protected:
  ~LoaderInterface() = default;
};

// Loads a scalar (string or number).
class LoadScalar : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& args, void* dst,
                ValidationErrors* errors) const override;

 protected:
  ~LoadScalar() = 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,
                        ValidationErrors* errors) const = 0;
};

// Load a string.
class LoadString : public LoadScalar {
 protected:
  ~LoadString() = default;

 private:
  bool IsNumber() const override;
  void LoadInto(const std::string& value, void* dst,
                ValidationErrors* errors) const override;
};

// Load a Duration.
class LoadDuration : public LoadScalar {
 protected:
  ~LoadDuration() = default;

 private:
  bool IsNumber() const override;
  void LoadInto(const std::string& value, void* dst,
                ValidationErrors* errors) const override;
};

// Load a number.
class LoadNumber : public LoadScalar {
 protected:
  ~LoadNumber() = default;

 private:
  bool IsNumber() const override;
};

// Load a signed number of type T.
template <typename T>
class TypedLoadSignedNumber : public LoadNumber {
 protected:
  ~TypedLoadSignedNumber() = default;

 private:
  void LoadInto(const std::string& value, void* dst,
                ValidationErrors* 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() = default;

 private:
  void LoadInto(const std::string& value, void* dst,
                ValidationErrors* 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() = default;

 private:
  void LoadInto(const std::string& value, void* dst,
                ValidationErrors* 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() = default;

 private:
  void LoadInto(const std::string& value, void* dst,
                ValidationErrors* 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,
                ValidationErrors* errors) const override;

 protected:
  ~LoadBool() = default;
};

// Loads an unprocessed JSON object value.
class LoadUnprocessedJsonObject : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
                ValidationErrors* errors) const override;

 protected:
  ~LoadUnprocessedJsonObject() = default;
};

// Loads an unprocessed JSON array value.
class LoadUnprocessedJsonArray : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
                ValidationErrors* errors) const override;

 protected:
  ~LoadUnprocessedJsonArray() = default;
};

// Load a vector of some type.
class LoadVector : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& args, void* dst,
                ValidationErrors* errors) const override;

 protected:
  ~LoadVector() = default;

 private:
  virtual void* EmplaceBack(void* dst) const = 0;
  virtual const LoaderInterface* ElementLoader() const = 0;
};

// Load a map of string->some type.
class LoadMap : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& args, void* dst,
                ValidationErrors* errors) const override;

 protected:
  ~LoadMap() = default;

 private:
  virtual void* Insert(const std::string& name, void* dst) const = 0;
  virtual const LoaderInterface* ElementLoader() const = 0;
};

// Load an optional of some type.
class LoadOptional : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& args, void* dst,
                ValidationErrors* errors) const override;

 protected:
  ~LoadOptional() = default;

 private:
  virtual void* Emplace(void* dst) const = 0;
  virtual void Reset(void* dst) const = 0;
  virtual const LoaderInterface* ElementLoader() 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,
                ValidationErrors* errors) const override {
    T::JsonLoader(args)->LoadInto(json, args, dst, errors);
  }

 private:
  ~AutoLoader() = default;
};

// Specializations of AutoLoader for basic types.
template <>
class AutoLoader<std::string> final : public LoadString {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<Duration> final : public LoadDuration {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<int32_t> final : public TypedLoadSignedNumber<int32_t> {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<int64_t> final : public TypedLoadSignedNumber<int64_t> {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<uint32_t> final : public TypedLoadUnsignedNumber<uint32_t> {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<uint64_t> final : public TypedLoadUnsignedNumber<uint64_t> {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<float> final : public LoadFloat {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<double> final : public LoadDouble {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<bool> final : public LoadBool {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<Json::Object> final : public LoadUnprocessedJsonObject {
 private:
  ~AutoLoader() = default;
};
template <>
class AutoLoader<Json::Array> final : public LoadUnprocessedJsonArray {
 private:
  ~AutoLoader() = default;
};

// Specializations of AutoLoader for vectors.
template <typename T>
class AutoLoader<std::vector<T>> final : public LoadVector {
 private:
  ~AutoLoader() = default;
  void* EmplaceBack(void* dst) const final {
    auto* vec = static_cast<std::vector<T>*>(dst);
    vec->emplace_back();
    return &vec->back();
  }
  const LoaderInterface* ElementLoader() const final {
    return LoaderForType<T>();
  }
};

// Specialization of AutoLoader for vector<bool> - we need a different
// implementation because, as vector<bool> packs bits in its implementation, the
// technique of returning a void* from Emplace() for the generic vector loader
// doesn't work.
template <>
class AutoLoader<std::vector<bool>> final : public LoaderInterface {
 public:
  void LoadInto(const Json& json, const JsonArgs& args, void* dst,
                ValidationErrors* errors) const override;

 private:
  ~AutoLoader() = default;
};

// Specializations of AutoLoader for maps.
template <typename T>
class AutoLoader<std::map<std::string, T>> final : public LoadMap {
 private:
  void* Insert(const std::string& name, void* dst) const final {
    return &static_cast<std::map<std::string, T>*>(dst)
                ->emplace(name, T())
                .first->second;
  };
  const LoaderInterface* ElementLoader() const final {
    return LoaderForType<T>();
  }

  ~AutoLoader() = default;
};

// Specializations of AutoLoader for absl::optional<>.
template <typename T>
class AutoLoader<absl::optional<T>> final : public LoadOptional {
 public:
  void* Emplace(void* dst) const final {
    return &static_cast<absl::optional<T>*>(dst)->emplace();
  }
  void Reset(void* dst) const final {
    static_cast<absl::optional<T>*>(dst)->reset();
  }
  const LoaderInterface* ElementLoader() const final {
    return LoaderForType<T>();
  }

 private:
  ~AutoLoader() = default;
};

// Specializations of AutoLoader for std::unique_ptr<>.
template <typename T>
class AutoLoader<std::unique_ptr<T>> final : public LoadOptional {
 public:
  void* Emplace(void* dst) const final {
    auto& p = *static_cast<std::unique_ptr<T>*>(dst);
    p = std::make_unique<T>();
    return p.get();
  }
  void Reset(void* dst) const final {
    static_cast<std::unique_ptr<T>*>(dst)->reset();
  }
  const LoaderInterface* ElementLoader() const final {
    return LoaderForType<T>();
  }

 private:
  ~AutoLoader() = default;
};

// Implementation of aforementioned LoaderForType.
// Simply keeps a static AutoLoader<T> and returns a pointer to that.
template <typename T>
const LoaderInterface* LoaderForType() {
  return NoDestructSingleton<AutoLoader<T>>::Get();
}

// 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, ValidationErrors* 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,
                ValidationErrors* 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,
                ValidationErrors* 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,
                               ValidationErrors* 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(),
    absl::string_view error_prefix = "errors validating JSON") {
  ValidationErrors errors;
  T result{};
  json_detail::LoaderForType<T>()->LoadInto(json, args, &result, &errors);
  if (!errors.ok()) return errors.status(error_prefix);
  return std::move(result);
}

template <typename T>
absl::StatusOr<RefCountedPtr<T>> LoadRefCountedFromJson(
    const Json& json, const JsonArgs& args = JsonArgs(),
    absl::string_view error_prefix = "errors validating JSON") {
  ValidationErrors errors;
  auto result = MakeRefCounted<T>();
  json_detail::LoaderForType<T>()->LoadInto(json, args, result.get(), &errors);
  if (!errors.ok()) return errors.status(error_prefix);
  return std::move(result);
}

template <typename T>
T LoadFromJson(const Json& json, const JsonArgs& args,
               ValidationErrors* errors) {
  T result{};
  json_detail::LoaderForType<T>()->LoadInto(json, args, &result, errors);
  return result;
}

template <typename T>
absl::optional<T> LoadJsonObjectField(const Json::Object& json,
                                      const JsonArgs& args,
                                      absl::string_view field,
                                      ValidationErrors* errors,
                                      bool required = true) {
  ValidationErrors::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_SRC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H
