| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #pragma once |
| |
| #include "cmConfigure.h" // IWYU pragma: keep |
| |
| #include <algorithm> |
| #include <functional> |
| #include <iostream> |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <cm/optional> |
| #include <cm/string_view> |
| |
| #include <cm3p/json/value.h> |
| |
| #include "cmJSONState.h" |
| #include "cmStringAlgorithms.h" |
| |
| template <typename T> |
| using cmJSONHelper = |
| std::function<bool(T& out, const Json::Value* value, cmJSONState* state)>; |
| |
| using ErrorGenerator = std::function<void(const Json::Value*, cmJSONState*)>; |
| |
| namespace JsonErrors { |
| enum ObjectError |
| { |
| RequiredMissing, |
| InvalidObject, |
| ExtraField, |
| MissingRequired |
| }; |
| using ErrorGenerator = std::function<void(const Json::Value*, cmJSONState*)>; |
| using ObjectErrorGenerator = |
| std::function<ErrorGenerator(ObjectError, const Json::Value::Members&)>; |
| ErrorGenerator EXPECTED_TYPE(const std::string& type); |
| |
| void INVALID_STRING(const Json::Value* value, cmJSONState* state); |
| |
| void INVALID_BOOL(const Json::Value* value, cmJSONState* state); |
| |
| void INVALID_INT(const Json::Value* value, cmJSONState* state); |
| |
| void INVALID_UINT(const Json::Value* value, cmJSONState* state); |
| |
| ObjectErrorGenerator INVALID_NAMED_OBJECT( |
| const std::function<std::string(const Json::Value*, cmJSONState*)>& |
| nameGenerator); |
| |
| ErrorGenerator INVALID_OBJECT(ObjectError errorType, |
| const Json::Value::Members& extraFields); |
| |
| ErrorGenerator INVALID_NAMED_OBJECT_KEY( |
| ObjectError errorType, const Json::Value::Members& extraFields); |
| } |
| |
| struct cmJSONHelperBuilder |
| { |
| |
| template <typename T> |
| class Object |
| { |
| public: |
| Object(JsonErrors::ObjectErrorGenerator error = JsonErrors::INVALID_OBJECT, |
| bool allowExtra = true) |
| : Error(std::move(error)) |
| , AllowExtra(allowExtra) |
| { |
| } |
| |
| template <typename U, typename M, typename F> |
| Object& Bind(const cm::string_view& name, M U::*member, F func, |
| bool required = true) |
| { |
| return this->BindPrivate( |
| name, |
| [func, member](T& out, const Json::Value* value, cmJSONState* state) |
| -> bool { return func(out.*member, value, state); }, |
| required); |
| } |
| template <typename M, typename F> |
| Object& Bind(const cm::string_view& name, std::nullptr_t, F func, |
| bool required = true) |
| { |
| return this->BindPrivate( |
| name, |
| [func](T& /*out*/, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| M dummy; |
| return func(dummy, value, state); |
| }, |
| required); |
| } |
| template <typename F> |
| Object& Bind(const cm::string_view& name, F func, bool required = true) |
| { |
| return this->BindPrivate(name, MemberFunction(func), required); |
| } |
| |
| bool operator()(T& out, const Json::Value* value, cmJSONState* state) const |
| { |
| Json::Value::Members extraFields; |
| bool success = true; |
| if (!value && this->AnyRequired) { |
| Error(JsonErrors::ObjectError::RequiredMissing, extraFields)(value, |
| state); |
| return false; |
| } |
| if (value && !value->isObject()) { |
| Error(JsonErrors::ObjectError::InvalidObject, extraFields)(value, |
| state); |
| return false; |
| } |
| if (value) { |
| extraFields = value->getMemberNames(); |
| } |
| |
| for (auto const& m : this->Members) { |
| std::string name(m.Name.data(), m.Name.size()); |
| state->push_stack(name, value); |
| if (value && value->isMember(name)) { |
| if (!m.Function(out, &(*value)[name], state)) { |
| success = false; |
| } |
| extraFields.erase( |
| std::find(extraFields.begin(), extraFields.end(), name)); |
| } else if (!m.Required) { |
| if (!m.Function(out, nullptr, state)) { |
| success = false; |
| } |
| } else { |
| Error(JsonErrors::ObjectError::MissingRequired, extraFields)(value, |
| state); |
| success = false; |
| } |
| state->pop_stack(); |
| } |
| |
| if (!this->AllowExtra && !extraFields.empty()) { |
| Error(JsonErrors::ObjectError::ExtraField, extraFields)(value, state); |
| success = false; |
| } |
| return success; |
| } |
| |
| private: |
| // Not a true cmJSONHelper, it just happens to match the signature |
| using MemberFunction = std::function<bool(T& out, const Json::Value* value, |
| cmJSONState* state)>; |
| struct Member |
| { |
| cm::string_view Name; |
| MemberFunction Function; |
| bool Required; |
| }; |
| std::vector<Member> Members; |
| bool AnyRequired = false; |
| JsonErrors::ObjectErrorGenerator Error; |
| bool AllowExtra; |
| |
| Object& BindPrivate(const cm::string_view& name, MemberFunction&& func, |
| bool required) |
| { |
| Member m; |
| m.Name = name; |
| m.Function = std::move(func); |
| m.Required = required; |
| this->Members.push_back(std::move(m)); |
| if (required) { |
| this->AnyRequired = true; |
| } |
| return *this; |
| } |
| }; |
| |
| static cmJSONHelper<std::string> String( |
| const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_STRING, |
| const std::string& defval = "") |
| { |
| return [error, defval](std::string& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| out = defval; |
| return true; |
| } |
| if (!value->isString()) { |
| error(value, state); |
| ; |
| return false; |
| } |
| out = value->asString(); |
| return true; |
| }; |
| }; |
| |
| static cmJSONHelper<std::string> String(const std::string& defval) |
| { |
| return String(JsonErrors::INVALID_STRING, defval); |
| }; |
| |
| static cmJSONHelper<int> Int( |
| const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_INT, |
| int defval = 0) |
| { |
| return [error, defval](int& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| out = defval; |
| return true; |
| } |
| if (!value->isInt()) { |
| error(value, state); |
| ; |
| return false; |
| } |
| out = value->asInt(); |
| return true; |
| }; |
| } |
| |
| static cmJSONHelper<int> Int(int defval) |
| { |
| return Int(JsonErrors::INVALID_INT, defval); |
| }; |
| |
| static cmJSONHelper<unsigned int> UInt( |
| const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_UINT, |
| unsigned int defval = 0) |
| { |
| return [error, defval](unsigned int& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| out = defval; |
| return true; |
| } |
| if (!value->isUInt()) { |
| error(value, state); |
| ; |
| return false; |
| } |
| out = value->asUInt(); |
| return true; |
| }; |
| } |
| |
| static cmJSONHelper<unsigned int> UInt(unsigned int defval) |
| { |
| return UInt(JsonErrors::INVALID_UINT, defval); |
| } |
| |
| static cmJSONHelper<bool> Bool( |
| const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_BOOL, |
| bool defval = false) |
| { |
| return [error, defval](bool& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| out = defval; |
| return true; |
| } |
| if (!value->isBool()) { |
| error(value, state); |
| ; |
| return false; |
| } |
| out = value->asBool(); |
| return true; |
| }; |
| } |
| |
| static cmJSONHelper<bool> Bool(bool defval) |
| { |
| return Bool(JsonErrors::INVALID_BOOL, defval); |
| } |
| |
| template <typename T, typename F, typename Filter> |
| static cmJSONHelper<std::vector<T>> VectorFilter( |
| const JsonErrors::ErrorGenerator& error, F func, Filter filter) |
| { |
| return [error, func, filter](std::vector<T>& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| bool success = true; |
| if (!value) { |
| out.clear(); |
| return true; |
| } |
| if (!value->isArray()) { |
| error(value, state); |
| return false; |
| } |
| out.clear(); |
| int index = 0; |
| for (auto const& item : *value) { |
| state->push_stack(cmStrCat("$vector_item_", index++), &item); |
| T t; |
| if (!func(t, &item, state)) { |
| success = false; |
| } |
| if (!filter(t)) { |
| state->pop_stack(); |
| continue; |
| } |
| out.push_back(std::move(t)); |
| state->pop_stack(); |
| } |
| return success; |
| }; |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<std::vector<T>> Vector(JsonErrors::ErrorGenerator error, |
| F func) |
| { |
| return VectorFilter<T, F>(std::move(error), func, |
| [](const T&) { return true; }); |
| } |
| |
| template <typename T, typename F, typename Filter> |
| static cmJSONHelper<std::map<std::string, T>> MapFilter( |
| const JsonErrors::ErrorGenerator& error, F func, Filter filter) |
| { |
| return [error, func, filter](std::map<std::string, T>& out, |
| const Json::Value* value, |
| cmJSONState* state) -> bool { |
| bool success = true; |
| if (!value) { |
| out.clear(); |
| return true; |
| } |
| if (!value->isObject()) { |
| error(value, state); |
| ; |
| return false; |
| } |
| out.clear(); |
| for (auto const& key : value->getMemberNames()) { |
| state->push_stack(cmStrCat(key, ""), &(*value)[key]); |
| if (!filter(key)) { |
| state->pop_stack(); |
| continue; |
| } |
| T t; |
| if (!func(t, &(*value)[key], state)) { |
| success = false; |
| } |
| out[key] = std::move(t); |
| state->pop_stack(); |
| } |
| return success; |
| }; |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<std::map<std::string, T>> Map( |
| const JsonErrors::ErrorGenerator& error, F func) |
| { |
| return MapFilter<T, F>(error, func, |
| [](const std::string&) { return true; }); |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<cm::optional<T>> Optional(F func) |
| { |
| return [func](cm::optional<T>& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| out.reset(); |
| return true; |
| } |
| out.emplace(); |
| return func(*out, value, state); |
| }; |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<T> Required(const JsonErrors::ErrorGenerator& error, |
| F func) |
| { |
| return [error, func](T& out, const Json::Value* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| error(value, state); |
| ; |
| return false; |
| } |
| return func(out, value, state); |
| }; |
| } |
| }; |