/* 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" 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; const auto EXPECTED_TYPE = [](const std::string& type) { return [type](const Json::Value* value, cmJSONState* state) -> void { #if !defined(CMAKE_BOOTSTRAP) if (state->key().empty()) { state->AddErrorAtValue(cmStrCat("Expected ", type), value); return; } std::string errMsg = cmStrCat("\"", state->key(), "\" expected ", type); if (value && value->isConvertibleTo(Json::ValueType::stringValue)) { errMsg = cmStrCat(errMsg, ", got: ", value->asString()); } state->AddErrorAtValue(errMsg, value); #endif }; }; const auto INVALID_STRING = [](const Json::Value* value, cmJSONState* state) -> void { JsonErrors::EXPECTED_TYPE("a string")(value, state); }; const auto INVALID_BOOL = [](const Json::Value* value, cmJSONState* state) -> void { JsonErrors::EXPECTED_TYPE("a bool")(value, state); }; const auto INVALID_INT = [](const Json::Value* value, cmJSONState* state) -> void { JsonErrors::EXPECTED_TYPE("an integer")(value, state); }; const auto INVALID_UINT = [](const Json::Value* value, cmJSONState* state) -> void { JsonErrors::EXPECTED_TYPE("an unsigned integer")(value, state); }; const auto INVALID_NAMED_OBJECT = [](const std::function& nameGenerator) -> ObjectErrorGenerator { return [nameGenerator]( ObjectError errorType, const Json::Value::Members& extraFields) -> ErrorGenerator { return [nameGenerator, errorType, extraFields]( const Json::Value* value, cmJSONState* state) -> void { #if !defined(CMAKE_BOOTSTRAP) std::string name = nameGenerator(value, state); switch (errorType) { case ObjectError::RequiredMissing: state->AddErrorAtValue(cmStrCat("Invalid Required ", name), value); break; case ObjectError::InvalidObject: state->AddErrorAtValue(cmStrCat("Invalid ", name), value); break; case ObjectError::ExtraField: { for (auto const& member : extraFields) { if (value) { state->AddErrorAtValue( cmStrCat("Invalid extra field \"", member, "\" in ", name), &(*value)[member]); } else { state->AddError( cmStrCat("Invalid extra field \"", member, "\" in ", name)); } } } break; case ObjectError::MissingRequired: state->AddErrorAtValue(cmStrCat("Missing required field \"", state->key(), "\" in ", name), value); break; } #endif }; }; }; const auto INVALID_OBJECT = [](ObjectError errorType, const Json::Value::Members& extraFields) -> ErrorGenerator { return INVALID_NAMED_OBJECT( [](const Json::Value*, cmJSONState*) -> std::string { return "Object"; })( errorType, extraFields); }; const auto INVALID_NAMED_OBJECT_KEY = [](ObjectError errorType, const Json::Value::Members& extraFields) -> ErrorGenerator { return INVALID_NAMED_OBJECT( [](const Json::Value*, cmJSONState* state) -> std::string { for (auto it = state->parseStack.rbegin(); it != state->parseStack.rend(); ++it) { if (it->first.rfind("$vector_item_", 0) == 0) { continue; } return cmStrCat("\"", it->first, "\""); } return "root"; })(errorType, 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); }; } };