mirror of https://github.com/grpc/grpc.git
Declarative JSON parser (#30442)
* Declarative JSON parser * Automated change: Fix sanity tests * fix * shrinking stuff a little * static vtables * separate fns * simpler? * make maps work * windows fixes * Automated change: Fix sanity tests * simplify code * Automated change: Fix sanity tests * vtable-test * dont always create vec/map impls for every type * comments * make error consistent * move method private * progress * durations! * Automated change: Fix sanity tests * fix * fix * fix * Automated change: Fix sanity tests * post-load * Automated change: Fix sanity tests * document JsonPostLoad() and add static_assert * don't copy field names, to avoid length limitations * use absl::Status * accept either string or number for numeric values * add test for direct data member of another struct type * remove unused method * add support for retaining part of the JSON wirthout processing * update test for changes in Json::Parse() API * add absl::optional support * Automated change: Fix sanity tests * fix tests, improve error messages, and add overload to parse to existing object * remove overload of LoadFromJson() * change special case for Json to instead use Json::Object * fix build * improve error structure, add missing types, and improve tests * clang-format * Automated change: Fix sanity tests * fix build * add LoadJsonObjectField(), add LoadFromJson() overload that takes an ErrorList parameter, and add tests for parsing bare top-level types * fix msan * Automated change: Fix sanity tests * fix error message * Automated change: Fix sanity tests * add mechanism to conditionally disable individual fields * fix build Co-authored-by: Craig Tiller <craig.tiller@gmail.com> Co-authored-by: ctiller <ctiller@users.noreply.github.com> Co-authored-by: Craig Tiller <ctiller@google.com> Co-authored-by: markdroth <markdroth@users.noreply.github.com>pull/30490/head
parent
c752a6b25e
commit
af634e19b4
22 changed files with 1977 additions and 36 deletions
@ -0,0 +1,34 @@ |
||||
// 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_ARGS_H |
||||
#define GRPC_CORE_LIB_JSON_JSON_ARGS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class JsonArgs { |
||||
public: |
||||
JsonArgs() = default; |
||||
virtual ~JsonArgs() = default; |
||||
|
||||
virtual bool IsEnabled(absl::string_view /*key*/) const { return true; } |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_JSON_JSON_ARGS_H
|
@ -0,0 +1,42 @@ |
||||
// Copyright 2022 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_CHANNEL_ARGS_H |
||||
#define GRPC_CORE_LIB_JSON_JSON_CHANNEL_ARGS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/json/json_args.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class JsonChannelArgs : public JsonArgs { |
||||
public: |
||||
explicit JsonChannelArgs(const ChannelArgs& args) : args_(args) {} |
||||
|
||||
bool IsEnabled(absl::string_view key) const override { |
||||
return args_.GetBool(key).value_or(false); |
||||
} |
||||
|
||||
private: |
||||
ChannelArgs args_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_JSON_JSON_CHANNEL_ARGS_H
|
@ -0,0 +1,204 @@ |
||||
// 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.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/json/json_object_loader.h" |
||||
|
||||
#include <algorithm> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/ascii.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/strings/strip.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
void ErrorList::PushField(absl::string_view ext) { |
||||
// Skip leading '.' for top-level field names.
|
||||
if (fields_.empty()) absl::ConsumePrefix(&ext, "."); |
||||
fields_.emplace_back(std::string(ext)); |
||||
} |
||||
|
||||
void ErrorList::PopField() { fields_.pop_back(); } |
||||
|
||||
void ErrorList::AddError(absl::string_view error) { |
||||
field_errors_[absl::StrJoin(fields_, "")].emplace_back(error); |
||||
} |
||||
|
||||
bool ErrorList::FieldHasErrors() const { |
||||
return field_errors_.find(absl::StrJoin(fields_, "")) != field_errors_.end(); |
||||
} |
||||
|
||||
absl::Status ErrorList::status() const { |
||||
if (field_errors_.empty()) return absl::OkStatus(); |
||||
std::vector<std::string> errors; |
||||
for (const auto& p : field_errors_) { |
||||
if (p.second.size() > 1) { |
||||
errors.emplace_back(absl::StrCat("field:", p.first, " errors:[", |
||||
absl::StrJoin(p.second, "; "), "]")); |
||||
} else { |
||||
errors.emplace_back( |
||||
absl::StrCat("field:", p.first, " error:", p.second[0])); |
||||
} |
||||
} |
||||
return absl::InvalidArgumentError(absl::StrCat( |
||||
"errors validating JSON: [", absl::StrJoin(errors, "; "), "]")); |
||||
} |
||||
|
||||
namespace json_detail { |
||||
|
||||
void LoadScalar::LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst, |
||||
ErrorList* errors) const { |
||||
// We accept either STRING or NUMBER for numeric values, as per
|
||||
// https://developers.google.com/protocol-buffers/docs/proto3#json.
|
||||
if (json.type() != Json::Type::STRING && |
||||
(!IsNumber() || json.type() != Json::Type::NUMBER)) { |
||||
errors->AddError( |
||||
absl::StrCat("is not a ", IsNumber() ? "number" : "string")); |
||||
return; |
||||
} |
||||
return LoadInto(json.string_value(), dst, errors); |
||||
} |
||||
|
||||
bool LoadString::IsNumber() const { return false; } |
||||
|
||||
void LoadString::LoadInto(const std::string& value, void* dst, |
||||
ErrorList*) const { |
||||
*static_cast<std::string*>(dst) = value; |
||||
} |
||||
|
||||
bool LoadDuration::IsNumber() const { return false; } |
||||
|
||||
void LoadDuration::LoadInto(const std::string& value, void* dst, |
||||
ErrorList* errors) const { |
||||
absl::string_view buf(value); |
||||
if (!absl::ConsumeSuffix(&buf, "s")) { |
||||
errors->AddError("Not a duration (no s suffix)"); |
||||
return; |
||||
} |
||||
buf = absl::StripAsciiWhitespace(buf); |
||||
auto decimal_point = buf.find('.'); |
||||
int nanos = 0; |
||||
if (decimal_point != absl::string_view::npos) { |
||||
absl::string_view after_decimal = buf.substr(decimal_point + 1); |
||||
buf = buf.substr(0, decimal_point); |
||||
if (!absl::SimpleAtoi(after_decimal, &nanos)) { |
||||
errors->AddError("Not a duration (not a number of nanoseconds)"); |
||||
return; |
||||
} |
||||
if (after_decimal.length() > 9) { |
||||
// We don't accept greater precision than nanos.
|
||||
errors->AddError("Not a duration (too many digits after decimal)"); |
||||
return; |
||||
} |
||||
for (size_t i = 0; i < (9 - after_decimal.length()); ++i) { |
||||
nanos *= 10; |
||||
} |
||||
} |
||||
int seconds; |
||||
if (!absl::SimpleAtoi(buf, &seconds)) { |
||||
errors->AddError("Not a duration (not a number of seconds)"); |
||||
return; |
||||
} |
||||
*static_cast<Duration*>(dst) = |
||||
Duration::FromSecondsAndNanoseconds(seconds, nanos); |
||||
} |
||||
|
||||
bool LoadNumber::IsNumber() const { return true; } |
||||
|
||||
void LoadBool::LoadInto(const Json& json, const JsonArgs&, void* dst, |
||||
ErrorList* errors) const { |
||||
if (json.type() == Json::Type::JSON_TRUE) { |
||||
*static_cast<bool*>(dst) = true; |
||||
} else if (json.type() == Json::Type::JSON_FALSE) { |
||||
*static_cast<bool*>(dst) = false; |
||||
} else { |
||||
errors->AddError("is not a boolean"); |
||||
} |
||||
} |
||||
|
||||
void LoadUnprocessedJsonObject::LoadInto(const Json& json, const JsonArgs&, |
||||
void* dst, ErrorList* errors) const { |
||||
if (json.type() != Json::Type::OBJECT) { |
||||
errors->AddError("is not an object"); |
||||
return; |
||||
} |
||||
*static_cast<Json::Object*>(dst) = json.object_value(); |
||||
} |
||||
|
||||
void LoadVector::LoadInto(const Json& json, const JsonArgs& args, void* dst, |
||||
ErrorList* errors) const { |
||||
if (json.type() != Json::Type::ARRAY) { |
||||
errors->AddError("is not an array"); |
||||
return; |
||||
} |
||||
const auto& array = json.array_value(); |
||||
for (size_t i = 0; i < array.size(); ++i) { |
||||
ScopedField field(errors, absl::StrCat("[", i, "]")); |
||||
LoadOne(array[i], args, dst, errors); |
||||
} |
||||
} |
||||
|
||||
void LoadMap::LoadInto(const Json& json, const JsonArgs& args, void* dst, |
||||
ErrorList* errors) const { |
||||
if (json.type() != Json::Type::OBJECT) { |
||||
errors->AddError("is not an object"); |
||||
return; |
||||
} |
||||
for (const auto& pair : json.object_value()) { |
||||
ScopedField field(errors, absl::StrCat("[\"", pair.first, "\"]")); |
||||
LoadOne(pair.second, args, pair.first, dst, errors); |
||||
} |
||||
} |
||||
|
||||
bool LoadObject(const Json& json, const JsonArgs& args, const Element* elements, |
||||
size_t num_elements, void* dst, ErrorList* errors) { |
||||
if (json.type() != Json::Type::OBJECT) { |
||||
errors->AddError("is not an object"); |
||||
return false; |
||||
} |
||||
for (size_t i = 0; i < num_elements; ++i) { |
||||
const Element& element = elements[i]; |
||||
if (element.enable_key != nullptr && !args.IsEnabled(element.enable_key)) { |
||||
continue; |
||||
} |
||||
ScopedField field(errors, absl::StrCat(".", element.name)); |
||||
const auto& it = json.object_value().find(element.name); |
||||
if (it == json.object_value().end()) { |
||||
if (element.optional) continue; |
||||
errors->AddError("field not present"); |
||||
continue; |
||||
} |
||||
char* field_dst = static_cast<char*>(dst) + element.member_offset; |
||||
element.loader->LoadInto(it->second, args, field_dst, errors); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
const Json* GetJsonObjectField(const Json::Object& json, |
||||
absl::string_view field, ErrorList* errors, |
||||
bool required) { |
||||
auto it = json.find(std::string(field)); |
||||
if (it == json.end()) { |
||||
if (required) errors->AddError("field not present"); |
||||
return nullptr; |
||||
} |
||||
return &it->second; |
||||
} |
||||
|
||||
} // namespace json_detail
|
||||
} // namespace grpc_core
|
@ -0,0 +1,544 @@ |
||||
// 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
|
@ -0,0 +1,984 @@ |
||||
// Copyright 2021 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.
|
||||
|
||||
#include "src/core/lib/json/json_object_loader.h" |
||||
|
||||
#include <cstdint> |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
#include "absl/strings/str_join.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace { |
||||
|
||||
template <typename T> |
||||
absl::StatusOr<T> Parse(absl::string_view json, |
||||
const JsonArgs& args = JsonArgs()) { |
||||
auto parsed = Json::Parse(json); |
||||
if (!parsed.ok()) return parsed.status(); |
||||
return LoadFromJson<T>(*parsed, args); |
||||
} |
||||
|
||||
//
|
||||
// Signed integer tests
|
||||
//
|
||||
|
||||
template <typename T> |
||||
class SignedIntegerTest : public ::testing::Test {}; |
||||
|
||||
TYPED_TEST_SUITE_P(SignedIntegerTest); |
||||
|
||||
TYPED_TEST_P(SignedIntegerTest, IntegerFields) { |
||||
struct TestStruct { |
||||
TypeParam value = 0; |
||||
TypeParam optional_value = 0; |
||||
absl::optional<TypeParam> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Positive number.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": 5}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, 5); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Negative number.
|
||||
test_struct = Parse<TestStruct>("{\"value\": -5}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, -5); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Encoded in a JSON string.
|
||||
test_struct = Parse<TestStruct>("{\"value\": \"5\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, 5); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": 5, \"optional_value\": 7, " |
||||
"\"absl_optional_value\": 9}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, 5); |
||||
EXPECT_EQ(test_struct->optional_value, 7); |
||||
EXPECT_EQ(test_struct->absl_optional_value, 9); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": {}, " |
||||
"\"absl_optional_value\": true}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not a number; " |
||||
"field:optional_value error:is not a number; " |
||||
"field:value error:is not a number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(SignedIntegerTest, IntegerFields); |
||||
|
||||
using IntegerTypes = ::testing::Types<int32_t, int64_t>; |
||||
INSTANTIATE_TYPED_TEST_SUITE_P(My, SignedIntegerTest, IntegerTypes); |
||||
|
||||
//
|
||||
// Unsigned integer tests
|
||||
//
|
||||
|
||||
template <typename T> |
||||
class UnsignedIntegerTest : public ::testing::Test {}; |
||||
|
||||
TYPED_TEST_SUITE_P(UnsignedIntegerTest); |
||||
|
||||
TYPED_TEST_P(UnsignedIntegerTest, IntegerFields) { |
||||
struct TestStruct { |
||||
TypeParam value = 0; |
||||
TypeParam optional_value = 0; |
||||
absl::optional<TypeParam> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Positive number.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": 5}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, 5); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Negative number.
|
||||
test_struct = Parse<TestStruct>("{\"value\": -5}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:value error:failed to parse non-negative number]") |
||||
<< test_struct.status(); |
||||
// Encoded in a JSON string.
|
||||
test_struct = Parse<TestStruct>("{\"value\": \"5\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, 5); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": 5, \"optional_value\": 7, " |
||||
"\"absl_optional_value\": 9}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, 5); |
||||
EXPECT_EQ(test_struct->optional_value, 7); |
||||
ASSERT_TRUE(test_struct->absl_optional_value.has_value()); |
||||
EXPECT_EQ(*test_struct->absl_optional_value, 9); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": {}, " |
||||
"\"absl_optional_value\": true}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not a number; " |
||||
"field:optional_value error:is not a number; " |
||||
"field:value error:is not a number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(UnsignedIntegerTest, IntegerFields); |
||||
|
||||
using UnsignedIntegerTypes = ::testing::Types<uint32_t, uint64_t>; |
||||
INSTANTIATE_TYPED_TEST_SUITE_P(My, UnsignedIntegerTest, UnsignedIntegerTypes); |
||||
|
||||
//
|
||||
// Floating-point tests
|
||||
//
|
||||
|
||||
template <typename T> |
||||
class FloatingPointTest : public ::testing::Test {}; |
||||
|
||||
TYPED_TEST_SUITE_P(FloatingPointTest); |
||||
|
||||
TYPED_TEST_P(FloatingPointTest, FloatFields) { |
||||
struct TestStruct { |
||||
TypeParam value = 0; |
||||
TypeParam optional_value = 0; |
||||
absl::optional<TypeParam> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Positive number.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": 5.2}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_NEAR(test_struct->value, 5.2, 0.0001); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Negative number.
|
||||
test_struct = Parse<TestStruct>("{\"value\": -5.2}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_NEAR(test_struct->value, -5.2, 0.0001); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Encoded in a JSON string.
|
||||
test_struct = Parse<TestStruct>("{\"value\": \"5.2\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_NEAR(test_struct->value, 5.2, 0.0001); |
||||
EXPECT_EQ(test_struct->optional_value, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": 5.2, \"optional_value\": 7.5, " |
||||
"\"absl_optional_value\": 9.8}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_NEAR(test_struct->value, 5.2, 0.0001); |
||||
EXPECT_NEAR(test_struct->optional_value, 7.5, 0.0001); |
||||
ASSERT_TRUE(test_struct->absl_optional_value.has_value()); |
||||
EXPECT_NEAR(*test_struct->absl_optional_value, 9.8, 0.0001); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": {}, " |
||||
"\"absl_optional_value\": true}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not a number; " |
||||
"field:optional_value error:is not a number; " |
||||
"field:value error:is not a number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(FloatingPointTest, FloatFields); |
||||
|
||||
using FloatingPointTypes = ::testing::Types<float, double>; |
||||
INSTANTIATE_TYPED_TEST_SUITE_P(My, FloatingPointTest, FloatingPointTypes); |
||||
|
||||
//
|
||||
// Boolean tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, BooleanFields) { |
||||
struct TestStruct { |
||||
bool value = false; |
||||
bool optional_value = true; |
||||
absl::optional<bool> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// True.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": true}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, true); |
||||
EXPECT_EQ(test_struct->optional_value, true); // Unmodified.
|
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// False.
|
||||
test_struct = Parse<TestStruct>("{\"value\": false}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, false); |
||||
EXPECT_EQ(test_struct->optional_value, true); // Unmodified.
|
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": true, \"optional_value\": false," |
||||
"\"absl_optional_value\": true}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, true); |
||||
EXPECT_EQ(test_struct->optional_value, false); |
||||
EXPECT_EQ(test_struct->absl_optional_value, true); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": {}, " |
||||
"\"absl_optional_value\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not a boolean; " |
||||
"field:optional_value error:is not a boolean; " |
||||
"field:value error:is not a boolean]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
//
|
||||
// String tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, StringFields) { |
||||
struct TestStruct { |
||||
std::string value; |
||||
std::string optional_value; |
||||
absl::optional<std::string> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid string.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": \"foo\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, "foo"); |
||||
EXPECT_EQ(test_struct->optional_value, ""); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": \"foo\", \"optional_value\": \"bar\"," |
||||
"\"absl_optional_value\": \"baz\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, "foo"); |
||||
EXPECT_EQ(test_struct->optional_value, "bar"); |
||||
EXPECT_EQ(test_struct->absl_optional_value, "baz"); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": {}, " |
||||
"\"absl_optional_value\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not a string; " |
||||
"field:optional_value error:is not a string; " |
||||
"field:value error:is not a string]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
//
|
||||
// Duration tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, DurationFields) { |
||||
struct TestStruct { |
||||
Duration value = Duration::Zero(); |
||||
Duration optional_value = Duration::Zero(); |
||||
absl::optional<Duration> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid duration string.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": \"3s\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, Duration::Seconds(3)); |
||||
EXPECT_EQ(test_struct->optional_value, Duration::Zero()); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Invalid duration strings.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": \"3sec\", \"optional_value\": \"foos\"," |
||||
"\"absl_optional_value\": \"1.0123456789s\"}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:" |
||||
"Not a duration (too many digits after decimal); " |
||||
"field:optional_value error:" |
||||
"Not a duration (not a number of seconds); " |
||||
"field:value error:Not a duration (no s suffix)]") |
||||
<< test_struct.status(); |
||||
test_struct = Parse<TestStruct>("{\"value\": \"3.xs\"}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:value error:Not a duration (not a number of nanoseconds)]") |
||||
<< test_struct.status(); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": \"3s\", \"optional_value\": \"3.2s\", " |
||||
"\"absl_optional_value\": \"10s\"}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->value, Duration::Seconds(3)); |
||||
EXPECT_EQ(test_struct->optional_value, Duration::Milliseconds(3200)); |
||||
EXPECT_EQ(test_struct->absl_optional_value, Duration::Seconds(10)); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": {}, " |
||||
"\"absl_optional_value\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not a string; " |
||||
"field:optional_value error:is not a string; " |
||||
"field:value error:is not a string]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
//
|
||||
// Json::Object tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, JsonObjectFields) { |
||||
struct TestStruct { |
||||
Json::Object value; |
||||
Json::Object optional_value; |
||||
absl::optional<Json::Object> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid object.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": {\"a\":1}}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(Json{test_struct->value}.Dump(), "{\"a\":1}"); |
||||
EXPECT_EQ(Json{test_struct->optional_value}.Dump(), "{}"); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": {\"a\":1}, \"optional_value\": {\"b\":2}, " |
||||
"\"absl_optional_value\": {\"c\":3}}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(Json{test_struct->value}.Dump(), "{\"a\":1}"); |
||||
EXPECT_EQ(Json{test_struct->optional_value}.Dump(), "{\"b\":2}"); |
||||
ASSERT_TRUE(test_struct->absl_optional_value.has_value()); |
||||
EXPECT_EQ(Json{*test_struct->absl_optional_value}.Dump(), "{\"c\":3}"); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": true, " |
||||
"\"absl_optional_value\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not an object; " |
||||
"field:optional_value error:is not an object; " |
||||
"field:value error:is not an object]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
//
|
||||
// map<> tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, MapFields) { |
||||
struct TestStruct { |
||||
std::map<std::string, int32_t> value; |
||||
std::map<std::string, std::string> optional_value; |
||||
absl::optional<std::map<std::string, bool>> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid map.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": {\"a\":1}}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_THAT(test_struct->value, |
||||
::testing::ElementsAre(::testing::Pair("a", 1))); |
||||
EXPECT_THAT(test_struct->optional_value, ::testing::ElementsAre()); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": {\"a\":1}, \"optional_value\": {\"b\":\"foo\"}, " |
||||
"\"absl_optional_value\": {\"c\":true}}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_THAT(test_struct->value, |
||||
::testing::ElementsAre(::testing::Pair("a", 1))); |
||||
EXPECT_THAT(test_struct->optional_value, |
||||
::testing::ElementsAre(::testing::Pair("b", "foo"))); |
||||
ASSERT_TRUE(test_struct->absl_optional_value.has_value()); |
||||
EXPECT_THAT(*test_struct->absl_optional_value, |
||||
::testing::ElementsAre(::testing::Pair("c", true))); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [], \"optional_value\": true, " |
||||
"\"absl_optional_value\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not an object; " |
||||
"field:optional_value error:is not an object; " |
||||
"field:value error:is not an object]") |
||||
<< test_struct.status(); |
||||
// Wrong JSON type for map value.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": {\"a\":\"foo\"}, \"optional_value\": {\"b\":true}, " |
||||
"\"absl_optional_value\": {\"c\":1}}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value[\"c\"] error:is not a boolean; " |
||||
"field:optional_value[\"b\"] error:is not a string; " |
||||
"field:value[\"a\"] error:failed to parse number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
//
|
||||
// vector<> tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, VectorFields) { |
||||
struct TestStruct { |
||||
std::vector<int32_t> value; |
||||
std::vector<std::string> optional_value; |
||||
absl::optional<std::vector<bool>> absl_optional_value; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("value", &TestStruct::value) |
||||
.OptionalField("optional_value", &TestStruct::optional_value) |
||||
.OptionalField("absl_optional_value", |
||||
&TestStruct::absl_optional_value) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid map.
|
||||
auto test_struct = Parse<TestStruct>("{\"value\": [1, 2, 3]}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_THAT(test_struct->value, ::testing::ElementsAre(1, 2, 3)); |
||||
EXPECT_THAT(test_struct->optional_value, ::testing::ElementsAre()); |
||||
EXPECT_FALSE(test_struct->absl_optional_value.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:value error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [4, 5, 6], \"optional_value\": [\"foo\", \"bar\"], " |
||||
"\"absl_optional_value\": [true, false, true]}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_THAT(test_struct->value, ::testing::ElementsAre(4, 5, 6)); |
||||
EXPECT_THAT(test_struct->optional_value, |
||||
::testing::ElementsAre("foo", "bar")); |
||||
ASSERT_TRUE(test_struct->absl_optional_value.has_value()); |
||||
EXPECT_THAT(*test_struct->absl_optional_value, |
||||
::testing::ElementsAre(true, false, true)); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": {}, \"optional_value\": true, " |
||||
"\"absl_optional_value\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value error:is not an array; " |
||||
"field:optional_value error:is not an array; " |
||||
"field:value error:is not an array]") |
||||
<< test_struct.status(); |
||||
// Wrong JSON type for map value.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"value\": [\"foo\", \"bar\"], \"optional_value\": [true, false], " |
||||
"\"absl_optional_value\": [1, 2]}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_value[0] error:is not a boolean; " |
||||
"field:absl_optional_value[1] error:is not a boolean; " |
||||
"field:optional_value[0] error:is not a string; " |
||||
"field:optional_value[1] error:is not a string; " |
||||
"field:value[0] error:failed to parse number; " |
||||
"field:value[1] error:failed to parse number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
//
|
||||
// Nested struct tests
|
||||
//
|
||||
|
||||
TEST(JsonObjectLoader, NestedStructFields) { |
||||
struct NestedStruct { |
||||
int32_t inner = 0; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = JsonObjectLoader<NestedStruct>() |
||||
.Field("inner", &NestedStruct::inner) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
struct TestStruct { |
||||
NestedStruct outer; |
||||
NestedStruct optional_outer; |
||||
absl::optional<NestedStruct> absl_optional_outer; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("outer", &TestStruct::outer) |
||||
.OptionalField("optional_outer", &TestStruct::optional_outer) |
||||
.OptionalField("absl_optional_outer", |
||||
&TestStruct::absl_optional_outer) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid nested struct.
|
||||
auto test_struct = Parse<TestStruct>("{\"outer\": {\"inner\": 1}}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->outer.inner, 1); |
||||
EXPECT_EQ(test_struct->optional_outer.inner, 0); |
||||
EXPECT_FALSE(test_struct->absl_optional_outer.has_value()); |
||||
// Fails if required field is not present.
|
||||
test_struct = Parse<TestStruct>("{}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:outer error:field not present]") |
||||
<< test_struct.status(); |
||||
// Fails if inner required field is not present.
|
||||
test_struct = Parse<TestStruct>("{\"outer\": {}}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ( |
||||
test_struct.status().message(), |
||||
"errors validating JSON: [field:outer.inner error:field not present]") |
||||
<< test_struct.status(); |
||||
// Optional fields present.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"outer\": {\"inner\":1}, \"optional_outer\": {\"inner\":2}, " |
||||
"\"absl_optional_outer\": {\"inner\":3}}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->outer.inner, 1); |
||||
EXPECT_EQ(test_struct->optional_outer.inner, 2); |
||||
ASSERT_TRUE(test_struct->absl_optional_outer.has_value()); |
||||
EXPECT_EQ(test_struct->absl_optional_outer->inner, 3); |
||||
// Wrong JSON type.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"outer\": \"foo\", \"optional_outer\": true, " |
||||
"\"absl_optional_outer\": 1}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_outer error:is not an object; " |
||||
"field:optional_outer error:is not an object; " |
||||
"field:outer error:is not an object]") |
||||
<< test_struct.status(); |
||||
// Wrong JSON type for inner value.
|
||||
test_struct = Parse<TestStruct>( |
||||
"{\"outer\": {\"inner\":\"foo\"}, \"optional_outer\": {\"inner\":true}, " |
||||
"\"absl_optional_outer\": {\"inner\":[]}}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [" |
||||
"field:absl_optional_outer.inner error:is not a number; " |
||||
"field:optional_outer.inner error:is not a number; " |
||||
"field:outer.inner error:failed to parse number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareString) { |
||||
auto parsed = Parse<std::string>("\"foo\""); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_EQ(*parsed, "foo"); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareDuration) { |
||||
auto parsed = Parse<Duration>("\"1.5s\""); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_EQ(*parsed, Duration::Milliseconds(1500)); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareSignedInteger) { |
||||
auto parsed = Parse<int32_t>("5"); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_EQ(*parsed, 5); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareUnsignedInteger) { |
||||
auto parsed = Parse<uint32_t>("5"); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_EQ(*parsed, 5); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareFloat) { |
||||
auto parsed = Parse<float>("5.2"); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_NEAR(*parsed, 5.2, 0.001); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareBool) { |
||||
auto parsed = Parse<bool>("true"); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_TRUE(*parsed); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareVector) { |
||||
auto parsed = Parse<std::vector<int32_t>>("[1, 2, 3]"); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_THAT(*parsed, ::testing::ElementsAre(1, 2, 3)); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, BareMap) { |
||||
auto parsed = |
||||
Parse<std::map<std::string, int32_t>>("{\"a\":1, \"b\":2, \"c\":3}"); |
||||
ASSERT_TRUE(parsed.ok()) << parsed.status(); |
||||
EXPECT_THAT(*parsed, ::testing::ElementsAre(::testing::Pair("a", 1), |
||||
::testing::Pair("b", 2), |
||||
::testing::Pair("c", 3))); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, IgnoresUnsupportedFields) { |
||||
struct TestStruct { |
||||
int32_t a = 0; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>().Field("a", &TestStruct::a).Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
auto test_struct = Parse<TestStruct>("{\"a\": 3, \"b\":false}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->a, 3); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, IgnoresDisabledFields) { |
||||
class FakeJsonArgs : public JsonArgs { |
||||
public: |
||||
FakeJsonArgs() = default; |
||||
|
||||
bool IsEnabled(absl::string_view key) const override { |
||||
return key != "disabled"; |
||||
} |
||||
}; |
||||
struct TestStruct { |
||||
int32_t a = 0; |
||||
int32_t b = 0; |
||||
int32_t c = 0; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>() |
||||
.Field("a", &TestStruct::a, "disabled") |
||||
.OptionalField("b", &TestStruct::b, "disabled") |
||||
.OptionalField("c", &TestStruct::c, "enabled") |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Fields "a" and "b" have the wrong types, but we ignore them,
|
||||
// because they're disabled.
|
||||
auto test_struct = |
||||
Parse<TestStruct>("{\"a\":false, \"b\":false, \"c\":1}", FakeJsonArgs()); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->a, 0); |
||||
EXPECT_EQ(test_struct->b, 0); |
||||
EXPECT_EQ(test_struct->c, 1); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, PostLoadHook) { |
||||
struct TestStruct { |
||||
int32_t a = 0; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = JsonObjectLoader<TestStruct>() |
||||
.OptionalField("a", &TestStruct::a) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
void JsonPostLoad(const Json& /*source*/, const JsonArgs& /*args*/, |
||||
ErrorList* /*errors*/) { |
||||
++a; |
||||
} |
||||
}; |
||||
auto test_struct = Parse<TestStruct>("{\"a\": 1}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->a, 2); |
||||
test_struct = Parse<TestStruct>("{}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->a, 1); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, CustomValidationInPostLoadHook) { |
||||
struct TestStruct { |
||||
int32_t a = 0; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>().Field("a", &TestStruct::a).Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
void JsonPostLoad(const Json& /*source*/, const JsonArgs& /*args*/, |
||||
ErrorList* errors) { |
||||
ScopedField field(errors, ".a"); |
||||
if (!errors->FieldHasErrors() && a <= 0) { |
||||
errors->AddError("must be greater than 0"); |
||||
} |
||||
} |
||||
}; |
||||
// Value greater than 0.
|
||||
auto test_struct = Parse<TestStruct>("{\"a\": 1}"); |
||||
ASSERT_TRUE(test_struct.ok()) << test_struct.status(); |
||||
EXPECT_EQ(test_struct->a, 1); |
||||
// Value 0, triggers custom validation.
|
||||
test_struct = Parse<TestStruct>("{\"a\": 0}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:a error:must be greater than 0]") |
||||
<< test_struct.status(); |
||||
// Invalid type, generates built-in parsing error, so custom
|
||||
// validation will not generate a new error.
|
||||
test_struct = Parse<TestStruct>("{\"a\": []}"); |
||||
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(test_struct.status().message(), |
||||
"errors validating JSON: [field:a error:is not a number]") |
||||
<< test_struct.status(); |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, LoadFromJsonWithErrorList) { |
||||
struct TestStruct { |
||||
int32_t a = 0; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestStruct>().Field("a", &TestStruct::a).Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
// Valid.
|
||||
{ |
||||
absl::string_view json_str = "{\"a\":1}"; |
||||
auto json = Json::Parse(json_str); |
||||
ASSERT_TRUE(json.ok()) << json.status(); |
||||
ErrorList errors; |
||||
TestStruct test_struct = |
||||
LoadFromJson<TestStruct>(*json, JsonArgs(), &errors); |
||||
ASSERT_TRUE(errors.ok()) << errors.status(); |
||||
EXPECT_EQ(test_struct.a, 1); |
||||
} |
||||
// Invalid.
|
||||
{ |
||||
absl::string_view json_str = "{\"a\":\"foo\"}"; |
||||
auto json = Json::Parse(json_str); |
||||
ASSERT_TRUE(json.ok()) << json.status(); |
||||
ErrorList errors; |
||||
LoadFromJson<TestStruct>(*json, JsonArgs(), &errors); |
||||
absl::Status status = errors.status(); |
||||
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(status.message(), |
||||
"errors validating JSON: [field:a error:failed to parse number]") |
||||
<< status; |
||||
} |
||||
} |
||||
|
||||
TEST(JsonObjectLoader, LoadJsonObjectField) { |
||||
absl::string_view json_str = "{\"int\":1}"; |
||||
auto json = Json::Parse(json_str); |
||||
ASSERT_TRUE(json.ok()) << json.status(); |
||||
// Load a valid field.
|
||||
{ |
||||
ErrorList errors; |
||||
auto value = LoadJsonObjectField<int32_t>(json->object_value(), JsonArgs(), |
||||
"int", &errors); |
||||
ASSERT_TRUE(value.has_value()) << errors.status(); |
||||
EXPECT_EQ(*value, 1); |
||||
EXPECT_TRUE(errors.ok()); |
||||
} |
||||
// An optional field that is not present.
|
||||
{ |
||||
ErrorList errors; |
||||
auto value = LoadJsonObjectField<int32_t>(json->object_value(), JsonArgs(), |
||||
"not_present", &errors, |
||||
/*required=*/false); |
||||
EXPECT_FALSE(value.has_value()); |
||||
EXPECT_TRUE(errors.ok()); |
||||
} |
||||
// A required field that is not present.
|
||||
{ |
||||
ErrorList errors; |
||||
auto value = LoadJsonObjectField<int32_t>(json->object_value(), JsonArgs(), |
||||
"not_present", &errors); |
||||
EXPECT_FALSE(value.has_value()); |
||||
auto status = errors.status(); |
||||
EXPECT_THAT(status.code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(status.message(), |
||||
"errors validating JSON: [" |
||||
"field:not_present error:field not present]") |
||||
<< status; |
||||
} |
||||
// Value has the wrong type.
|
||||
{ |
||||
ErrorList errors; |
||||
auto value = LoadJsonObjectField<std::string>(json->object_value(), |
||||
JsonArgs(), "int", &errors); |
||||
EXPECT_FALSE(value.has_value()); |
||||
auto status = errors.status(); |
||||
EXPECT_THAT(status.code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(status.message(), |
||||
"errors validating JSON: [field:int error:is not a string]") |
||||
<< status; |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue