| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file LICENSE.rst or https://cmake.org/licensing for details. */ |
| #pragma once |
| |
| #include "cmConfigure.h" // IWYU pragma: keep |
| |
| #include <algorithm> |
| #include <functional> |
| #include <iostream> |
| #include <iterator> |
| #include <map> |
| #include <string> |
| #include <type_traits> |
| #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, Json::Value const* value, cmJSONState* state)>; |
| |
| using ErrorGenerator = std::function<void(Json::Value const*, cmJSONState*)>; |
| |
| namespace JsonErrors { |
| enum ObjectError |
| { |
| RequiredMissing, |
| InvalidObject, |
| ExtraField, |
| MissingRequired |
| }; |
| |
| using ErrorGenerator = std::function<void(Json::Value const*, cmJSONState*)>; |
| using ObjectErrorGenerator = |
| std::function<ErrorGenerator(ObjectError, Json::Value::Members const&)>; |
| |
| ErrorGenerator EXPECTED_TYPE(std::string const& type); |
| |
| void INVALID_STRING(Json::Value const* value, cmJSONState* state); |
| |
| void INVALID_BOOL(Json::Value const* value, cmJSONState* state); |
| |
| void INVALID_INT(Json::Value const* value, cmJSONState* state); |
| |
| void INVALID_UINT(Json::Value const* value, cmJSONState* state); |
| |
| ObjectErrorGenerator INVALID_NAMED_OBJECT( |
| std::function<std::string(Json::Value const*, cmJSONState*)> const& |
| nameGenerator); |
| |
| ErrorGenerator INVALID_OBJECT(ObjectError errorType, |
| Json::Value::Members const& extraFields); |
| |
| ErrorGenerator INVALID_NAMED_OBJECT_KEY( |
| ObjectError errorType, Json::Value::Members const& extraFields); |
| } |
| |
| #if __cplusplus >= 201703L |
| namespace details { |
| // A meta-function to check if a given callable type |
| // can be called with the only string ref arg. |
| template <typename F, typename Enable = void> |
| struct is_bool_filter |
| { |
| static constexpr bool value = false; |
| }; |
| |
| template <typename F> |
| struct is_bool_filter<F, |
| std::enable_if_t<std::is_same_v< |
| std::invoke_result_t<F, std::string const&>, bool>>> |
| { |
| static constexpr bool value = true; |
| }; |
| } |
| #endif |
| |
| 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(cm::string_view const& name, M U::*member, F func, |
| bool required = true) |
| { |
| return this->BindPrivate( |
| name, |
| [func, member](T& out, Json::Value const* value, cmJSONState* state) |
| -> bool { return func(out.*member, value, state); }, |
| required); |
| } |
| template <typename M, typename F> |
| Object& Bind(cm::string_view const& name, std::nullptr_t, F func, |
| bool required = true) |
| { |
| return this->BindPrivate( |
| name, |
| [func](T& /*out*/, Json::Value const* value, |
| cmJSONState* state) -> bool { |
| M dummy; |
| return func(dummy, value, state); |
| }, |
| required); |
| } |
| template <typename F> |
| Object& Bind(cm::string_view const& name, F func, bool required = true) |
| { |
| return this->BindPrivate(name, MemberFunction(func), required); |
| } |
| |
| bool operator()(T& out, Json::Value const* value, cmJSONState* state) const |
| { |
| Json::Value::Members extraFields; |
| 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(); |
| } |
| |
| if (state->allowComments) { |
| extraFields.erase( |
| std::remove(extraFields.begin(), extraFields.end(), "$comment"), |
| extraFields.end()); |
| } |
| |
| bool success = true; |
| 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, Json::Value const* value, |
| cmJSONState* state)>; |
| struct Member |
| { |
| Member(cm::string_view name, MemberFunction func, bool required) |
| : Name{ name } |
| , Function{ std::move(func) } |
| , Required{ required } |
| { |
| } |
| cm::string_view Name; |
| MemberFunction Function; |
| bool Required; |
| }; |
| std::vector<Member> Members; |
| bool AnyRequired = false; |
| JsonErrors::ObjectErrorGenerator Error; |
| bool AllowExtra; |
| |
| Object& BindPrivate(cm::string_view const& name, MemberFunction&& func, |
| bool required) |
| { |
| this->Members.emplace_back(name, std::move(func), required); |
| this->AnyRequired = this->AnyRequired || required; |
| return *this; |
| } |
| }; |
| |
| static cmJSONHelper<std::string> String( |
| JsonErrors::ErrorGenerator const& error = JsonErrors::INVALID_STRING, |
| std::string const& defval = "") |
| { |
| return [error, defval](std::string& out, Json::Value const* 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(std::string const& defval) |
| { |
| return String(JsonErrors::INVALID_STRING, defval); |
| }; |
| |
| static cmJSONHelper<int> Int( |
| JsonErrors::ErrorGenerator const& error = JsonErrors::INVALID_INT, |
| int defval = 0) |
| { |
| return [error, defval](int& out, Json::Value const* 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( |
| JsonErrors::ErrorGenerator const& error = JsonErrors::INVALID_UINT, |
| unsigned int defval = 0) |
| { |
| return [error, defval](unsigned int& out, Json::Value const* 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( |
| JsonErrors::ErrorGenerator const& error = JsonErrors::INVALID_BOOL, |
| bool defval = false) |
| { |
| return [error, defval](bool& out, Json::Value const* 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( |
| JsonErrors::ErrorGenerator const& error, F func, Filter filter) |
| { |
| return [error, func, filter](std::vector<T>& out, Json::Value const* 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, |
| [](T const&) { return true; }); |
| } |
| |
| enum class FilterResult |
| { |
| Continue, ///< A filter has accepted a given key (and value) |
| Skip, ///< A filter has rejected a given key (or value) |
| Error ///< A filter has found and reported an error |
| }; |
| |
| /// Iterate over the object's members and call a filter callable to |
| /// decide what to do with the current key/value. |
| /// A filter returns one of the `FilterResult` values. |
| /// A container type is an associative or a sequence |
| /// container of pairs (key, value). |
| template <typename Container, typename F, typename Filter> |
| static cmJSONHelper<Container> FilteredObject( |
| JsonErrors::ErrorGenerator const& error, F func, Filter filter) |
| { |
| return [error, func, filter](Container& out, Json::Value const* value, |
| cmJSONState* state) -> bool { |
| // NOTE Some compile-time code path don't use `filter` at all. |
| // So, suppress "unused lambda capture" warning is needed. |
| static_cast<void>(filter); |
| |
| if (!value) { |
| out.clear(); |
| return true; |
| } |
| if (!value->isObject()) { |
| error(value, state); |
| return false; |
| } |
| out.clear(); |
| auto outIt = std::inserter(out, out.end()); |
| bool success = true; |
| for (auto const& key : value->getMemberNames()) { |
| state->push_stack(key, &(*value)[key]); |
| #if __cplusplus >= 201703L |
| if constexpr (std::is_same_v<Filter, std::true_type>) { |
| // Filtering functionality isn't needed at all... |
| } else if constexpr (details::is_bool_filter<Filter>::value) { |
| // A given `Filter` is `bool(const std::string&)` callable. |
| if (!filter(key)) { |
| state->pop_stack(); |
| continue; |
| } |
| } else { |
| #endif |
| // A full-featured `Filter` has been given |
| auto res = filter(key, &(*value)[key], state); |
| if (res == FilterResult::Skip) { |
| state->pop_stack(); |
| continue; |
| } |
| if (res == FilterResult::Error) { |
| state->pop_stack(); |
| success = false; |
| break; |
| } |
| #if __cplusplus >= 201703L |
| } |
| #endif |
| typename Container::value_type::second_type t; |
| // ATTENTION Call the function first (for it's side-effects), |
| // then accumulate the result! |
| success = func(t, &(*value)[key], state) && success; |
| outIt = typename Container::value_type{ key, std::move(t) }; |
| state->pop_stack(); |
| } |
| return success; |
| }; |
| } |
| |
| template <typename T, typename F, typename Filter> |
| static cmJSONHelper<std::map<std::string, T>> MapFilter( |
| JsonErrors::ErrorGenerator const& error, F func, Filter filter) |
| { |
| // clang-format off |
| return FilteredObject<std::map<std::string, T>>( |
| error, func, |
| #if __cplusplus >= 201703L |
| // In C++ 17 a filter callable can be passed as is. |
| // Depending on its type `FilteredObject()` will call |
| // it with a key only (backward compatible behavior) |
| // or with 3 args supported by the full-featured |
| // filtering feature. |
| filter |
| #else |
| // For C++14 and below, to keep backward compatibility |
| // with CMake Presets code, `MapFilter()` can accept only |
| // `bool(const std::string&)` callables. |
| [filter](const std::string &key, const Json::Value * /*value*/, |
| cmJSONState * /*state*/) -> FilterResult { |
| // Simple adaptor to translate `bool` to `FilterResult` |
| return filter(key) ? FilterResult::Continue : FilterResult::Skip; |
| } |
| #endif |
| ); |
| // clang-format on |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<std::map<std::string, T>> Map( |
| JsonErrors::ErrorGenerator const& error, F func) |
| { |
| // clang-format off |
| return FilteredObject<std::map<std::string, T>>( |
| error, func, |
| #if __cplusplus >= 201703L |
| // With C++ 17 and above, pass a marker type, that no |
| // filtering is needed at all. |
| std::true_type() |
| #else |
| // In C++ 14 and below, pass an always-true dummy functor. |
| [](const std::string& /*key*/, const Json::Value* /*value*/, |
| cmJSONState* /*state*/) -> FilterResult { |
| return FilterResult::Continue; |
| } |
| #endif |
| ); |
| // clang-format on |
| } |
| |
| template <typename T, typename F> |
| static cmJSONHelper<cm::optional<T>> Optional(F func) |
| { |
| return [func](cm::optional<T>& out, Json::Value const* 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(JsonErrors::ErrorGenerator const& error, |
| F func) |
| { |
| return [error, func](T& out, Json::Value const* value, |
| cmJSONState* state) -> bool { |
| if (!value) { |
| error(value, state); |
| return false; |
| } |
| return func(out, value, state); |
| }; |
| } |
| |
| template <typename T, typename F, typename P> |
| static cmJSONHelper<T> Checked(JsonErrors::ErrorGenerator const& error, |
| F func, P predicate) |
| { |
| return [error, func, predicate](T& out, Json::Value const* value, |
| cmJSONState* state) -> bool { |
| bool result = func(out, value, state); |
| if (result && !predicate(out)) { |
| error(value, state); |
| result = false; |
| } |
| return result; |
| }; |
| } |
| }; |