/* 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 #include #include #include #include #include #include #include #include #include #include "cmJSONState.h" #include "cmStringAlgorithms.h" template using cmJSONHelper = std::function; using ErrorGenerator = std::function; namespace JsonErrors { enum ObjectError { RequiredMissing, InvalidObject, ExtraField, MissingRequired }; using ErrorGenerator = std::function; using ObjectErrorGenerator = std::function; 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& 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 class Object { public: Object(JsonErrors::ObjectErrorGenerator error = JsonErrors::INVALID_OBJECT, bool allowExtra = true) : Error(std::move(error)) , AllowExtra(allowExtra) { } template 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 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 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; struct Member { cm::string_view Name; MemberFunction Function; bool Required; }; std::vector 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 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 String(const std::string& defval) { return String(JsonErrors::INVALID_STRING, defval); }; static cmJSONHelper 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 defval) { return Int(JsonErrors::INVALID_INT, defval); }; static cmJSONHelper 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 UInt(unsigned int defval) { return UInt(JsonErrors::INVALID_UINT, defval); } static cmJSONHelper 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 defval) { return Bool(JsonErrors::INVALID_BOOL, defval); } template static cmJSONHelper> VectorFilter( const JsonErrors::ErrorGenerator& error, F func, Filter filter) { return [error, func, filter](std::vector& 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 static cmJSONHelper> Vector(JsonErrors::ErrorGenerator error, F func) { return VectorFilter(std::move(error), func, [](const T&) { return true; }); } template static cmJSONHelper> MapFilter( const JsonErrors::ErrorGenerator& error, F func, Filter filter) { return [error, func, filter](std::map& 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 static cmJSONHelper> Map( const JsonErrors::ErrorGenerator& error, F func) { return MapFilter(error, func, [](const std::string&) { return true; }); } template static cmJSONHelper> Optional(F func) { return [func](cm::optional& out, const Json::Value* value, cmJSONState* state) -> bool { if (!value) { out.reset(); return true; } out.emplace(); return func(*out, value, state); }; } template static cmJSONHelper 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); }; } };