Merge topic 'json-helpers'

b2f3f831e2 Refactor: Use JSON helpers in CTest resource spec
3f3a30e1e0 JSON: Add helpers

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !5197
This commit is contained in:
Brad King
2020-09-10 14:09:27 +00:00
committed by Kitware Robot
6 changed files with 931 additions and 91 deletions

View File

@@ -336,6 +336,7 @@ set(SRCS
cmInstallTargetGenerator.cxx
cmInstallDirectoryGenerator.h
cmInstallDirectoryGenerator.cxx
cmJSONHelpers.h
cmLDConfigLDConfigTool.cxx
cmLDConfigLDConfigTool.h
cmLDConfigTool.cxx

View File

@@ -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;
TopVersion version;
ReadFileResult result;
if ((result = RootVersionHelper(version, &root)) !=
ReadFileResult::READ_OK) {
return result;
}
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;
}
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)

View File

@@ -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>

304
Source/cmJSONHelpers.h Normal file
View File

@@ -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);
};
}

View File

@@ -13,6 +13,7 @@ set(CMakeLib_TESTS
testCTestResourceGroups.cxx
testGccDepfileReader.cxx
testGeneratedFileStream.cxx
testJSONHelpers.cxx
testRST.cxx
testRange.cxx
testOptional.cxx

View File

@@ -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;
}