diff options
-rw-r--r-- | Source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Source/CTest/cmCTestResourceSpec.cxx | 221 | ||||
-rw-r--r-- | Source/CTest/cmCTestResourceSpec.h | 2 | ||||
-rw-r--r-- | Source/cmJSONHelpers.h | 304 | ||||
-rw-r--r-- | Tests/CMakeLib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Tests/CMakeLib/testJSONHelpers.cxx | 493 |
6 files changed, 931 insertions, 91 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 310ffeb..1be424a 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -336,6 +336,7 @@ set(SRCS cmInstallTargetGenerator.cxx cmInstallDirectoryGenerator.h cmInstallDirectoryGenerator.cxx + cmJSONHelpers.h cmLDConfigLDConfigTool.cxx cmLDConfigLDConfigTool.h cmLDConfigTool.cxx diff --git a/Source/CTest/cmCTestResourceSpec.cxx b/Source/CTest/cmCTestResourceSpec.cxx index 21c97de..101dc2c 100644 --- a/Source/CTest/cmCTestResourceSpec.cxx +++ b/Source/CTest/cmCTestResourceSpec.cxx @@ -2,19 +2,140 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestResourceSpec.h" +#include <functional> #include <map> #include <string> #include <utility> #include <vector> +#include <cmext/string_view> + #include <cm3p/json/reader.h> #include <cm3p/json/value.h> #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" -static const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" }; -static const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" }; +#include "cmJSONHelpers.h" + +namespace { +const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" }; +const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" }; + +struct Version +{ + int Major = 1; + int Minor = 0; +}; + +struct TopVersion +{ + struct Version Version; +}; + +auto const VersionFieldHelper = + cmJSONIntHelper<cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_VERSION); + +auto const VersionHelper = + cmJSONRequiredHelper<Version, cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::NO_VERSION, + cmJSONObjectHelper<Version, cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_VERSION) + .Bind("major"_s, &Version::Major, VersionFieldHelper) + .Bind("minor"_s, &Version::Minor, VersionFieldHelper)); + +auto const RootVersionHelper = + cmJSONObjectHelper<TopVersion, cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_ROOT) + .Bind("version"_s, &TopVersion::Version, VersionHelper, false); + +cmCTestResourceSpec::ReadFileResult ResourceIdHelper(std::string& out, + const Json::Value* value) +{ + auto result = cmJSONStringHelper( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)(out, value); + if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) { + return result; + } + cmsys::RegularExpressionMatch match; + if (!IdRegex.find(out.c_str(), match)) { + return cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE; + } + return cmCTestResourceSpec::ReadFileResult::READ_OK; +} + +auto const ResourceHelper = + cmJSONObjectHelper<cmCTestResourceSpec::Resource, + cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE) + .Bind("id"_s, &cmCTestResourceSpec::Resource::Id, ResourceIdHelper) + .Bind("slots"_s, &cmCTestResourceSpec::Resource::Capacity, + cmJSONUIntHelper( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, 1), + false); + +auto const ResourceListHelper = + cmJSONVectorHelper<cmCTestResourceSpec::Resource, + cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE, + ResourceHelper); + +auto const ResourceMapHelper = + cmJSONMapFilterHelper<std::vector<cmCTestResourceSpec::Resource>, + cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, + ResourceListHelper, [](const std::string& key) -> bool { + cmsys::RegularExpressionMatch match; + return IdentifierRegex.find(key.c_str(), match); + }); + +auto const SocketSetHelper = cmJSONVectorHelper< + std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, ResourceMapHelper); + +cmCTestResourceSpec::ReadFileResult SocketHelper( + cmCTestResourceSpec::Socket& out, const Json::Value* value) +{ + std::vector< + std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>> + sockets; + cmCTestResourceSpec::ReadFileResult result = SocketSetHelper(sockets, value); + if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) { + return result; + } + if (sockets.size() > 1) { + return cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC; + } + if (sockets.empty()) { + out.Resources.clear(); + } else { + out.Resources = std::move(sockets[0]); + } + return cmCTestResourceSpec::ReadFileResult::READ_OK; +} + +auto const LocalRequiredHelper = + cmJSONRequiredHelper<cmCTestResourceSpec::Socket, + cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, SocketHelper); + +auto const RootHelper = + cmJSONObjectHelper<cmCTestResourceSpec, cmCTestResourceSpec::ReadFileResult>( + cmCTestResourceSpec::ReadFileResult::READ_OK, + cmCTestResourceSpec::ReadFileResult::INVALID_ROOT) + .Bind("local", &cmCTestResourceSpec::LocalSocket, LocalRequiredHelper, + false); +} cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile( const std::string& filename) @@ -30,99 +151,17 @@ cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile( return ReadFileResult::JSON_PARSE_ERROR; } - if (!root.isObject()) { - return ReadFileResult::INVALID_ROOT; - } - - int majorVersion = 1; - int minorVersion = 0; - if (root.isMember("version")) { - auto const& version = root["version"]; - if (version.isObject()) { - if (!version.isMember("major") || !version.isMember("minor")) { - return ReadFileResult::INVALID_VERSION; - } - auto const& major = version["major"]; - auto const& minor = version["minor"]; - if (!major.isInt() || !minor.isInt()) { - return ReadFileResult::INVALID_VERSION; - } - majorVersion = major.asInt(); - minorVersion = minor.asInt(); - } else { - return ReadFileResult::INVALID_VERSION; - } - } else { - return ReadFileResult::NO_VERSION; + TopVersion version; + ReadFileResult result; + if ((result = RootVersionHelper(version, &root)) != + ReadFileResult::READ_OK) { + return result; } - - if (majorVersion != 1 || minorVersion != 0) { + if (version.Version.Major != 1 || version.Version.Minor != 0) { return ReadFileResult::UNSUPPORTED_VERSION; } - auto const& local = root["local"]; - if (!local.isArray()) { - return ReadFileResult::INVALID_SOCKET_SPEC; - } - if (local.size() > 1) { - return ReadFileResult::INVALID_SOCKET_SPEC; - } - - if (local.empty()) { - this->LocalSocket.Resources.clear(); - return ReadFileResult::READ_OK; - } - - auto const& localSocket = local[0]; - if (!localSocket.isObject()) { - return ReadFileResult::INVALID_SOCKET_SPEC; - } - std::map<std::string, std::vector<cmCTestResourceSpec::Resource>> resources; - cmsys::RegularExpressionMatch match; - for (auto const& key : localSocket.getMemberNames()) { - if (IdentifierRegex.find(key.c_str(), match)) { - auto const& value = localSocket[key]; - auto& r = resources[key]; - if (value.isArray()) { - for (auto const& item : value) { - if (item.isObject()) { - cmCTestResourceSpec::Resource resource; - - if (!item.isMember("id")) { - return ReadFileResult::INVALID_RESOURCE; - } - auto const& id = item["id"]; - if (!id.isString()) { - return ReadFileResult::INVALID_RESOURCE; - } - resource.Id = id.asString(); - if (!IdRegex.find(resource.Id.c_str(), match)) { - return ReadFileResult::INVALID_RESOURCE; - } - - if (item.isMember("slots")) { - auto const& capacity = item["slots"]; - if (!capacity.isConvertibleTo(Json::uintValue)) { - return ReadFileResult::INVALID_RESOURCE; - } - resource.Capacity = capacity.asUInt(); - } else { - resource.Capacity = 1; - } - - r.push_back(resource); - } else { - return ReadFileResult::INVALID_RESOURCE; - } - } - } else { - return ReadFileResult::INVALID_RESOURCE_TYPE; - } - } - } - - this->LocalSocket.Resources = std::move(resources); - return ReadFileResult::READ_OK; + return RootHelper(*this, &root); } const char* cmCTestResourceSpec::ResultToString(ReadFileResult result) diff --git a/Source/CTest/cmCTestResourceSpec.h b/Source/CTest/cmCTestResourceSpec.h index 1aa279b..72628a3 100644 --- a/Source/CTest/cmCTestResourceSpec.h +++ b/Source/CTest/cmCTestResourceSpec.h @@ -2,6 +2,8 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #pragma once +#include "cmConfigure.h" // IWYU pragma: keep + #include <map> #include <string> #include <vector> diff --git a/Source/cmJSONHelpers.h b/Source/cmJSONHelpers.h new file mode 100644 index 0000000..2da2a03 --- /dev/null +++ b/Source/cmJSONHelpers.h @@ -0,0 +1,304 @@ +/* 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> +using cmJSONHelper = std::function<E(T& out, const Json::Value* value)>; + +template <typename T, typename E> +class cmJSONObjectHelper +{ +public: + cmJSONObjectHelper(E&& success, E&& fail, bool allowExtra = true); + + template <typename U, typename M, typename F> + cmJSONObjectHelper& Bind(const cm::string_view& name, M U::*member, F func, + bool required = true); + template <typename M, typename F> + cmJSONObjectHelper& Bind(const cm::string_view& name, std::nullptr_t, F func, + bool required = true); + + E operator()(T& out, const Json::Value* value) const; + +private: + // Not a true cmJSONHelper, it just happens to match the signature + using MemberFunction = std::function<E(T& out, const Json::Value* value)>; + struct Member + { + cm::string_view Name; + MemberFunction Function; + bool Required; + }; + std::vector<Member> Members; + bool AnyRequired = false; + E Success; + E Fail; + bool AllowExtra; + + cmJSONObjectHelper& BindPrivate(const cm::string_view& name, + MemberFunction&& func, bool required); +}; + +template <typename T, typename E> +cmJSONObjectHelper<T, E>::cmJSONObjectHelper(E&& success, E&& fail, + bool allowExtra) + : Success(std::move(success)) + , Fail(std::move(fail)) + , AllowExtra(allowExtra) +{ +} + +template <typename T, typename E> +template <typename U, typename M, typename F> +cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind( + const cm::string_view& name, M U::*member, F func, bool required) +{ + return this->BindPrivate( + name, + [func, member](T& out, const Json::Value* value) -> E { + return func(out.*member, value); + }, + required); +} + +template <typename T, typename E> +template <typename M, typename F> +cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind( + const cm::string_view& name, std::nullptr_t, F func, bool required) +{ + return this->BindPrivate(name, + [func](T& /*out*/, const Json::Value* value) -> E { + M dummy; + return func(dummy, value); + }, + required); +} + +template <typename T, typename E> +cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::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; +} + +template <typename T, typename E> +E cmJSONObjectHelper<T, E>::operator()(T& out, const Json::Value* value) 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]); + 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); + if (result != this->Success) { + return result; + } + } else { + return this->Fail; + } + } + + return this->AllowExtra || extraFields.empty() ? this->Success : this->Fail; +} + +template <typename E> +cmJSONHelper<std::string, E> cmJSONStringHelper(E success, E fail, + const std::string& defval = "") +{ + return + [success, fail, defval](std::string& out, const Json::Value* value) -> E { + if (!value) { + out = defval; + return success; + } + if (!value->isString()) { + return fail; + } + out = value->asString(); + return success; + }; +} + +template <typename E> +cmJSONHelper<int, E> cmJSONIntHelper(E success, E fail, int defval = 0) +{ + return [success, fail, defval](int& out, const Json::Value* value) -> E { + if (!value) { + out = defval; + return success; + } + if (!value->isInt()) { + return fail; + } + out = value->asInt(); + return success; + }; +} + +template <typename E> +cmJSONHelper<unsigned int, E> cmJSONUIntHelper(E success, E fail, + unsigned int defval = 0) +{ + return + [success, fail, defval](unsigned int& out, const Json::Value* value) -> E { + if (!value) { + out = defval; + return success; + } + if (!value->isUInt()) { + return fail; + } + out = value->asUInt(); + return success; + }; +} + +template <typename E> +cmJSONHelper<bool, E> cmJSONBoolHelper(E success, E fail, bool defval = false) +{ + return [success, fail, defval](bool& out, const Json::Value* value) -> E { + if (!value) { + out = defval; + return success; + } + if (!value->isBool()) { + return fail; + } + out = value->asBool(); + return success; + }; +} + +template <typename T, typename E, typename F, typename Filter> +cmJSONHelper<std::vector<T>, E> cmJSONVectorFilterHelper(E success, E fail, + F func, Filter filter) +{ + return [success, fail, func, filter](std::vector<T>& out, + const Json::Value* value) -> 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); + if (result != success) { + return result; + } + if (!filter(t)) { + continue; + } + out.push_back(t); + } + return success; + }; +} + +template <typename T, typename E, typename F> +cmJSONHelper<std::vector<T>, E> cmJSONVectorHelper(E success, E fail, F func) +{ + return cmJSONVectorFilterHelper<T, E, F>(success, fail, func, + [](const T&) { return true; }); +} + +template <typename T, typename E, typename F, typename Filter> +cmJSONHelper<std::map<std::string, T>, E> cmJSONMapFilterHelper(E success, + E fail, F func, + Filter filter) +{ + return [success, fail, func, filter](std::map<std::string, T>& out, + const Json::Value* value) -> 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]); + if (result != success) { + return result; + } + out[key] = std::move(t); + } + return success; + }; +} + +template <typename T, typename E, typename F> +cmJSONHelper<std::map<std::string, T>, E> cmJSONMapHelper(E success, E fail, + F func) +{ + return cmJSONMapFilterHelper<T, E, F>( + success, fail, func, [](const std::string&) { return true; }); +} + +template <typename T, typename E, typename F> +cmJSONHelper<cm::optional<T>, E> cmJSONOptionalHelper(E success, F func) +{ + return [success, func](cm::optional<T>& out, const Json::Value* value) -> E { + if (!value) { + out.reset(); + return success; + } + out.emplace(); + return func(*out, value); + }; +} + +template <typename T, typename E, typename F> +cmJSONHelper<T, E> cmJSONRequiredHelper(E fail, F func) +{ + return [fail, func](T& out, const Json::Value* value) -> E { + if (!value) { + return fail; + } + return func(out, value); + }; +} diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt index 0b2c8f6..87925bd 100644 --- a/Tests/CMakeLib/CMakeLists.txt +++ b/Tests/CMakeLib/CMakeLists.txt @@ -13,6 +13,7 @@ set(CMakeLib_TESTS testCTestResourceGroups.cxx testGccDepfileReader.cxx testGeneratedFileStream.cxx + testJSONHelpers.cxx testRST.cxx testRange.cxx testOptional.cxx diff --git a/Tests/CMakeLib/testJSONHelpers.cxx b/Tests/CMakeLib/testJSONHelpers.cxx new file mode 100644 index 0000000..78eed5b --- /dev/null +++ b/Tests/CMakeLib/testJSONHelpers.cxx @@ -0,0 +1,493 @@ +#include <functional> +#include <iostream> +#include <map> +#include <string> +#include <vector> + +#include <cm/optional> +#include <cmext/string_view> + +#include <cm3p/json/value.h> + +#include "cmJSONHelpers.h" + +#define ASSERT_TRUE(x) \ + do { \ + if (!(x)) { \ + std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \ + return false; \ + } \ + } while (false) + +namespace { +struct ObjectStruct +{ + std::string Field1; + int Field2; +}; + +struct InheritedStruct : public ObjectStruct +{ + std::string Field3; +}; + +enum class ErrorCode +{ + Success, + InvalidInt, + InvalidBool, + InvalidString, + InvalidObject, + InvalidArray, + MissingRequired, +}; + +auto const IntHelper = + cmJSONIntHelper<ErrorCode>(ErrorCode::Success, ErrorCode::InvalidInt, 1); +auto const RequiredIntHelper = + cmJSONRequiredHelper<int, ErrorCode>(ErrorCode::MissingRequired, IntHelper); +auto const UIntHelper = + cmJSONUIntHelper<ErrorCode>(ErrorCode::Success, ErrorCode::InvalidInt, 1); +auto const BoolHelper = cmJSONBoolHelper<ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidBool, false); +auto const StringHelper = cmJSONStringHelper<ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidString, "default"); +auto const RequiredStringHelper = cmJSONRequiredHelper<std::string, ErrorCode>( + ErrorCode::MissingRequired, StringHelper); +auto const StringVectorHelper = cmJSONVectorHelper<std::string, ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidArray, StringHelper); +auto const StringVectorFilterHelper = + cmJSONVectorFilterHelper<std::string, ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidArray, StringHelper, + [](const std::string& value) { return value != "ignore"; }); +auto const StringMapHelper = cmJSONMapHelper<std::string, ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidObject, StringHelper); +auto const StringMapFilterHelper = + cmJSONMapFilterHelper<std::string, ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidObject, StringHelper, + [](const std::string& key) { return key != "ignore"; }); +auto const OptionalStringHelper = + cmJSONOptionalHelper<std::string>(ErrorCode::Success, StringHelper); + +bool testInt() +{ + Json::Value v(2); + int i = 0; + ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::Success); + ASSERT_TRUE(i == 2); + + i = 0; + v = Json::nullValue; + ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::InvalidInt); + + i = 0; + ASSERT_TRUE(IntHelper(i, nullptr) == ErrorCode::Success); + ASSERT_TRUE(i == 1); + + return true; +} + +bool testUInt() +{ + Json::Value v(2); + unsigned int i = 0; + ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::Success); + ASSERT_TRUE(i == 2); + + i = 0; + v = Json::nullValue; + ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::InvalidInt); + + i = 0; + ASSERT_TRUE(UIntHelper(i, nullptr) == ErrorCode::Success); + ASSERT_TRUE(i == 1); + + return true; +} + +bool testBool() +{ + Json::Value v(true); + bool b = false; + ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success); + ASSERT_TRUE(b); + + b = false; + v = false; + ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success); + ASSERT_TRUE(!b); + + b = false; + v = 4; + ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::InvalidBool); + + b = true; + ASSERT_TRUE(BoolHelper(b, nullptr) == ErrorCode::Success); + ASSERT_TRUE(!b); + + return true; +} + +bool testString() +{ + Json::Value v("str"); + std::string str = ""; + ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::Success); + ASSERT_TRUE(str == "str"); + + str = ""; + v = Json::nullValue; + ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::InvalidString); + + str = ""; + ASSERT_TRUE(StringHelper(str, nullptr) == ErrorCode::Success); + ASSERT_TRUE(str == "default"); + + return true; +} + +bool testObject() +{ + auto const helper = cmJSONObjectHelper<ObjectStruct, ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidObject) + .Bind("field1"_s, &ObjectStruct::Field1, StringHelper) + .Bind("field2"_s, &ObjectStruct::Field2, IntHelper) + .Bind<std::string>("field3"_s, nullptr, StringHelper); + + Json::Value v(Json::objectValue); + v["field1"] = "Hello"; + v["field2"] = 2; + v["field3"] = "world!"; + v["extra"] = "extra"; + + ObjectStruct s1; + ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success); + ASSERT_TRUE(s1.Field1 == "Hello"); + ASSERT_TRUE(s1.Field2 == 2); + + v["field2"] = "wrong"; + ObjectStruct s2; + ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt); + + v.removeMember("field2"); + ObjectStruct s3; + ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject); + + v["field2"] = 2; + v["field3"] = 3; + ObjectStruct s4; + ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString); + + v.removeMember("field3"); + ObjectStruct s5; + ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject); + + v = "Hello"; + ObjectStruct s6; + ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject); + + ObjectStruct s7; + ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject); + + return true; +} + +bool testObjectInherited() +{ + auto const helper = + cmJSONObjectHelper<InheritedStruct, ErrorCode>(ErrorCode::Success, + ErrorCode::InvalidObject) + .Bind("field1"_s, &InheritedStruct::Field1, StringHelper) + .Bind("field2"_s, &InheritedStruct::Field2, IntHelper) + .Bind("field3"_s, &InheritedStruct::Field3, StringHelper); + + Json::Value v(Json::objectValue); + v["field1"] = "Hello"; + v["field2"] = 2; + v["field3"] = "world!"; + v["extra"] = "extra"; + + InheritedStruct s1; + ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success); + ASSERT_TRUE(s1.Field1 == "Hello"); + ASSERT_TRUE(s1.Field2 == 2); + ASSERT_TRUE(s1.Field3 == "world!"); + + v["field2"] = "wrong"; + InheritedStruct s2; + ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt); + + v.removeMember("field2"); + InheritedStruct s3; + ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject); + + v["field2"] = 2; + v["field3"] = 3; + InheritedStruct s4; + ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString); + + v.removeMember("field3"); + InheritedStruct s5; + ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject); + + v = "Hello"; + InheritedStruct s6; + ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject); + + InheritedStruct s7; + ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject); + + return true; +} + +bool testObjectNoExtra() +{ + auto const helper = cmJSONObjectHelper<ObjectStruct, ErrorCode>( + ErrorCode::Success, ErrorCode::InvalidObject, false) + .Bind("field1"_s, &ObjectStruct::Field1, StringHelper) + .Bind("field2"_s, &ObjectStruct::Field2, IntHelper); + + Json::Value v(Json::objectValue); + v["field1"] = "Hello"; + v["field2"] = 2; + + ObjectStruct s1; + ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success); + ASSERT_TRUE(s1.Field1 == "Hello"); + ASSERT_TRUE(s1.Field2 == 2); + + v["extra"] = "world!"; + ObjectStruct s2; + ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidObject); + + return true; +} + +bool testObjectOptional() +{ + auto const helper = + cmJSONObjectHelper<ObjectStruct, ErrorCode>(ErrorCode::Success, + ErrorCode::InvalidObject) + .Bind("field1"_s, &ObjectStruct::Field1, StringHelper, false) + .Bind("field2"_s, &ObjectStruct::Field2, IntHelper, false) + .Bind<std::string>("field3_s", nullptr, StringHelper, false); + + Json::Value v(Json::objectValue); + v["field1"] = "Hello"; + v["field2"] = 2; + v["field3"] = "world!"; + v["extra"] = "extra"; + + ObjectStruct s1; + ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success); + ASSERT_TRUE(s1.Field1 == "Hello"); + ASSERT_TRUE(s1.Field2 == 2); + + v = Json::objectValue; + ObjectStruct s2; + ASSERT_TRUE(helper(s2, &v) == ErrorCode::Success); + ASSERT_TRUE(s2.Field1 == "default"); + ASSERT_TRUE(s2.Field2 == 1); + + ObjectStruct s3; + ASSERT_TRUE(helper(s3, nullptr) == ErrorCode::Success); + ASSERT_TRUE(s3.Field1 == "default"); + ASSERT_TRUE(s3.Field2 == 1); + + return true; +} + +bool testVector() +{ + Json::Value v(Json::arrayValue); + v.append("Hello"); + v.append("world!"); + v.append("ignore"); + + std::vector<std::string> l{ "default" }; + std::vector<std::string> expected{ "Hello", "world!", "ignore" }; + ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::Success); + ASSERT_TRUE(l == expected); + + v[1] = 2; + l = { "default" }; + ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidString); + + v = "Hello"; + l = { "default" }; + ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidArray); + + l = { "default" }; + ASSERT_TRUE(StringVectorHelper(l, nullptr) == ErrorCode::Success); + ASSERT_TRUE(l.empty()); + + return true; +} + +bool testVectorFilter() +{ + Json::Value v(Json::arrayValue); + v.append("Hello"); + v.append("world!"); + v.append("ignore"); + + std::vector<std::string> l{ "default" }; + std::vector<std::string> expected{ + "Hello", + "world!", + }; + ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::Success); + ASSERT_TRUE(l == expected); + + v[1] = 2; + l = { "default" }; + ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidString); + + v = "Hello"; + l = { "default" }; + ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidArray); + + l = { "default" }; + ASSERT_TRUE(StringVectorFilterHelper(l, nullptr) == ErrorCode::Success); + ASSERT_TRUE(l.empty()); + + return true; +} + +bool testMap() +{ + Json::Value v(Json::objectValue); + v["field1"] = "Hello"; + v["field2"] = "world!"; + v["ignore"] = "ignore"; + + std::map<std::string, std::string> m{ { "key", "default" } }; + std::map<std::string, std::string> expected{ { "field1", "Hello" }, + { "field2", "world!" }, + { "ignore", "ignore" } }; + ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::Success); + ASSERT_TRUE(m == expected); + + v = Json::arrayValue; + m = { { "key", "default" } }; + ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::InvalidObject); + + m = { { "key", "default" } }; + ASSERT_TRUE(StringMapHelper(m, nullptr) == ErrorCode::Success); + ASSERT_TRUE(m.empty()); + + return true; +} + +bool testMapFilter() +{ + Json::Value v(Json::objectValue); + v["field1"] = "Hello"; + v["field2"] = "world!"; + v["ignore"] = "ignore"; + + std::map<std::string, std::string> m{ { "key", "default" } }; + std::map<std::string, std::string> expected{ { "field1", "Hello" }, + { "field2", "world!" } }; + ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::Success); + ASSERT_TRUE(m == expected); + + v = Json::arrayValue; + m = { { "key", "default" } }; + ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::InvalidObject); + + m = { { "key", "default" } }; + ASSERT_TRUE(StringMapFilterHelper(m, nullptr) == ErrorCode::Success); + ASSERT_TRUE(m.empty()); + + return true; +} + +bool testOptional() +{ + Json::Value v = "Hello"; + + cm::optional<std::string> str{ "default" }; + ASSERT_TRUE(OptionalStringHelper(str, &v) == ErrorCode::Success); + ASSERT_TRUE(str == "Hello"); + + str.emplace("default"); + ASSERT_TRUE(OptionalStringHelper(str, nullptr) == ErrorCode::Success); + ASSERT_TRUE(str == cm::nullopt); + + return true; +} + +bool testRequired() +{ + Json::Value v = "Hello"; + + std::string str = "default"; + int i = 1; + ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::Success); + ASSERT_TRUE(str == "Hello"); + ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::InvalidInt); + + v = 2; + str = "default"; + i = 1; + ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::InvalidString); + ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::Success); + ASSERT_TRUE(i == 2); + + str = "default"; + i = 1; + ASSERT_TRUE(RequiredStringHelper(str, nullptr) == + ErrorCode::MissingRequired); + ASSERT_TRUE(RequiredIntHelper(i, nullptr) == ErrorCode::MissingRequired); + + return true; +} +} + +int testJSONHelpers(int /*unused*/, char* /*unused*/ []) +{ + if (!testInt()) { + return 1; + } + if (!testUInt()) { + return 1; + } + if (!testBool()) { + return 1; + } + if (!testString()) { + return 1; + } + if (!testObject()) { + return 1; + } + if (!testObjectInherited()) { + return 1; + } + if (!testObjectNoExtra()) { + return 1; + } + if (!testObjectOptional()) { + return 1; + } + if (!testVector()) { + return 1; + } + if (!testVectorFilter()) { + return 1; + } + if (!testMap()) { + return 1; + } + if (!testMapFilter()) { + return 1; + } + if (!testOptional()) { + return 1; + } + if (!testRequired()) { + return 1; + } + return 0; +} |