| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #pragma once |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <functional> |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <cm/optional> |
| #include <cm/string_view> |
| |
| #include <cm3p/json/value.h> |
| |
| template <typename T, typename E, typename... CallState> |
| using cmJSONHelper = |
| std::function<E(T& out, const Json::Value* value, CallState&&... state)>; |
| |
| template <typename E, typename... CallState> |
| struct cmJSONHelperBuilder |
| { |
| template <typename T> |
| class Object |
| { |
| public: |
| Object(E&& success, E&& fail, bool allowExtra = true) |
| : Success(std::move(success)) |
| , Fail(std::move(fail)) |
| , 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, |
| CallState&&... state) -> E { |
| return func(out.*member, value, |
| std::forward(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, |
| CallState&&... state) -> E { |
| M dummy; |
| return func(dummy, value, |
| std::forward(state)...); |
| }, |
| required); |
| } |
| template <typename F> |
| Object& Bind(const cm::string_view& name, F func, bool required = true) |
| { |
| return this->BindPrivate(name, MemberFunction(func), required); |
| } |
| |
| E operator()(T& out, const Json::Value* value, CallState&&... state) const |
| { |
| if (!value && this->AnyRequired) { |
| return this->Fail; |
| } |
| if (value && !value->isObject()) { |
| return this->Fail; |
| } |
| Json::Value::Members extraFields; |
| if (value) { |
| extraFields = value->getMemberNames(); |
| } |
| |
| for (auto const& m : this->Members) { |
| std::string name(m.Name.data(), m.Name.size()); |
| if (value && value->isMember(name)) { |
| E result = m.Function(out, &(*value)[name], std::forward(state)...); |
| if (result != this->Success) { |
| return result; |
| } |
| extraFields.erase( |
| std::find(extraFields.begin(), extraFields.end(), name)); |
| } else if (!m.Required) { |
| E result = m.Function(out, nullptr, std::forward(state)...); |
| if (result != this->Success) { |
| return result; |
| } |
| } else { |
| return this->Fail; |
| } |
| } |
| |
| return this->AllowExtra || extraFields.empty() ? this->Success |
| : this->Fail; |
| } |
| |
| private: |
| // Not a true cmJSONHelper, it just happens to match the signature |
| using MemberFunction = |
| std::function<E(T& out, const Json::Value* value, CallState&&... state)>; |
| struct Member |
| { |
| cm::string_view Name; |
| MemberFunction Function; |
| bool Required; |
| }; |
| std::vector<Member> Members; |
| bool AnyRequired = false; |
| E Success; |
| E Fail; |
| 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, E, CallState...> String( |
| E success, E fail, const std::string& defval = "") |
| { |
| return [success, fail, defval](std::string& out, const Json::Value* value, |
| CallState&&... /*state*/) -> E { |
| if (!value) { |
| out = defval; |
| return success; |
| } |
| if (!value->isString()) { |
| return fail; |
| } |
| out = value->asString(); |
| return success; |
| }; |
| } |
| |
| static cmJSONHelper<int, E, CallState...> Int(E success, E fail, |
| int defval = 0) |
| { |
| return [success, fail, defval](int& out, const Json::Value* value, |
| CallState&&... /*state*/) -> E { |
| if (!value) { |
| out = defval; |
| return success; |
| } |
| if (!value->isInt()) { |
| return fail; |
| } |
| out = value->asInt(); |
| return success; |
| }; |
| } |
| |
| static cmJSONHelper<unsigned int, E, CallState...> UInt( |
| E success, E fail, unsigned int defval = 0) |
| { |
| return [success, fail, defval](unsigned int& out, const Json::Value* value, |
| CallState&&... /*state*/) -> E { |
| if (!value) { |
| out = defval; |
| return success; |
| } |
| if (!value->isUInt()) { |
| return fail; |
| } |
| out = value->asUInt(); |
| return success; |
| }; |
| } |
| |
| static cmJSONHelper<bool, E, CallState...> Bool(E success, E fail, |
| bool defval = false) |
| { |
| return [success, fail, defval](bool& out, const Json::Value* value, |
| CallState&&... /*state*/) -> E { |
| if (!value) { |
| out = defval; |
| return success; |
| } |
| if (!value->isBool()) { |
| return fail; |
| } |
| out = value->asBool(); |
| return success; |
| }; |
| } |
| |
| template <typename T, typename F, typename Filter> |
| static cmJSONHelper<std::vector<T>, E, CallState...> VectorFilter( |
| E success, E fail, F func, Filter filter) |
| { |
| return [success, fail, func, filter](std::vector<T>& out, |
| const Json::Value* value, |
| CallState&&... state) -> E { |
| if (!value) { |
| out.clear(); |
| return success; |
| } |
| if (!value->isArray()) { |
| return fail; |
| } |
| out.clear(); |
| for (auto const& item : *value) { |
| T t; |
| E result = func(t, &item, std::forward(state)...); |
| if (result != success) { |
| return result; |
| } |
| if (!filter(t)) { |
| continue; |
| } |
| out.push_back(std::move(t)); |
| } |
| return success; |
| }; |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<std::vector<T>, E, CallState...> Vector(E success, |
| E fail, F func) |
| { |
| return VectorFilter<T, F>(success, fail, func, |
| [](const T&) { return true; }); |
| } |
| |
| template <typename T, typename F, typename Filter> |
| static cmJSONHelper<std::map<std::string, T>, E, CallState...> MapFilter( |
| E success, E fail, F func, Filter filter) |
| { |
| return [success, fail, func, filter](std::map<std::string, T>& out, |
| const Json::Value* value, |
| CallState&&... state) -> E { |
| if (!value) { |
| out.clear(); |
| return success; |
| } |
| if (!value->isObject()) { |
| return fail; |
| } |
| out.clear(); |
| for (auto const& key : value->getMemberNames()) { |
| if (!filter(key)) { |
| continue; |
| } |
| T t; |
| E result = func(t, &(*value)[key], std::forward(state)...); |
| if (result != success) { |
| return result; |
| } |
| out[key] = std::move(t); |
| } |
| return success; |
| }; |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<std::map<std::string, T>, E, CallState...> Map(E success, |
| E fail, |
| F func) |
| { |
| return MapFilter<T, F>(success, fail, func, |
| [](const std::string&) { return true; }); |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<cm::optional<T>, E, CallState...> Optional(E success, |
| F func) |
| { |
| return [success, func](cm::optional<T>& out, const Json::Value* value, |
| CallState&&... state) -> E { |
| if (!value) { |
| out.reset(); |
| return success; |
| } |
| out.emplace(); |
| return func(*out, value, std::forward(state)...); |
| }; |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<T, E, CallState...> Required(E fail, F func) |
| { |
| return [fail, func](T& out, const Json::Value* value, |
| CallState&&... state) -> E { |
| if (!value) { |
| return fail; |
| } |
| return func(out, value, std::forward(state)...); |
| }; |
| } |
| }; |