blob: 48decbcd63b887d6aa16197d2dbf2d1ab410de66 [file] [log] [blame]
/* 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, typename... CallState>
using cmJSONHelper =
std::function<E(T& out, const Json::Value* value, CallState&&... state)>;
template <typename E, typename... CallState>
struct cmJSONHelperBuilder
{
template <typename T>
class Object
{
public:
Object(E&& success, E&& fail, bool allowExtra = true)
: Success(std::move(success))
, Fail(std::move(fail))
, AllowExtra(allowExtra)
{
}
template <typename U, typename M, typename F>
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,
CallState&&... state) -> E {
return func(out.*member, value,
std::forward(state)...);
},
required);
}
template <typename M, typename F>
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,
CallState&&... state) -> E {
M dummy;
return func(dummy, value,
std::forward(state)...);
},
required);
}
template <typename F>
Object& Bind(const cm::string_view& name, F func, bool required = true)
{
return this->BindPrivate(name, MemberFunction(func), required);
}
E operator()(T& out, const Json::Value* value, CallState&&... state) 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], std::forward(state)...);
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, std::forward(state)...);
if (result != this->Success) {
return result;
}
} else {
return this->Fail;
}
}
return this->AllowExtra || extraFields.empty() ? this->Success
: this->Fail;
}
private:
// Not a true cmJSONHelper, it just happens to match the signature
using MemberFunction =
std::function<E(T& out, const Json::Value* value, CallState&&... state)>;
struct Member
{
cm::string_view Name;
MemberFunction Function;
bool Required;
};
std::vector<Member> Members;
bool AnyRequired = false;
E Success;
E Fail;
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<std::string, E, CallState...> String(
E success, E fail, const std::string& defval = "")
{
return [success, fail, defval](std::string& out, const Json::Value* value,
CallState&&... /*state*/) -> E {
if (!value) {
out = defval;
return success;
}
if (!value->isString()) {
return fail;
}
out = value->asString();
return success;
};
}
static cmJSONHelper<int, E, CallState...> Int(E success, E fail,
int defval = 0)
{
return [success, fail, defval](int& out, const Json::Value* value,
CallState&&... /*state*/) -> E {
if (!value) {
out = defval;
return success;
}
if (!value->isInt()) {
return fail;
}
out = value->asInt();
return success;
};
}
static cmJSONHelper<unsigned int, E, CallState...> UInt(
E success, E fail, unsigned int defval = 0)
{
return [success, fail, defval](unsigned int& out, const Json::Value* value,
CallState&&... /*state*/) -> E {
if (!value) {
out = defval;
return success;
}
if (!value->isUInt()) {
return fail;
}
out = value->asUInt();
return success;
};
}
static cmJSONHelper<bool, E, CallState...> Bool(E success, E fail,
bool defval = false)
{
return [success, fail, defval](bool& out, const Json::Value* value,
CallState&&... /*state*/) -> E {
if (!value) {
out = defval;
return success;
}
if (!value->isBool()) {
return fail;
}
out = value->asBool();
return success;
};
}
template <typename T, typename F, typename Filter>
static cmJSONHelper<std::vector<T>, E, CallState...> VectorFilter(
E success, E fail, F func, Filter filter)
{
return [success, fail, func, filter](std::vector<T>& out,
const Json::Value* value,
CallState&&... state) -> 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, std::forward(state)...);
if (result != success) {
return result;
}
if (!filter(t)) {
continue;
}
out.push_back(std::move(t));
}
return success;
};
}
template <typename T, typename F>
static cmJSONHelper<std::vector<T>, E, CallState...> Vector(E success,
E fail, F func)
{
return VectorFilter<T, F>(success, fail, func,
[](const T&) { return true; });
}
template <typename T, typename F, typename Filter>
static cmJSONHelper<std::map<std::string, T>, E, CallState...> MapFilter(
E success, E fail, F func, Filter filter)
{
return [success, fail, func, filter](std::map<std::string, T>& out,
const Json::Value* value,
CallState&&... state) -> 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], std::forward(state)...);
if (result != success) {
return result;
}
out[key] = std::move(t);
}
return success;
};
}
template <typename T, typename F>
static cmJSONHelper<std::map<std::string, T>, E, CallState...> Map(E success,
E fail,
F func)
{
return MapFilter<T, F>(success, fail, func,
[](const std::string&) { return true; });
}
template <typename T, typename F>
static cmJSONHelper<cm::optional<T>, E, CallState...> Optional(E success,
F func)
{
return [success, func](cm::optional<T>& out, const Json::Value* value,
CallState&&... state) -> E {
if (!value) {
out.reset();
return success;
}
out.emplace();
return func(*out, value, std::forward(state)...);
};
}
template <typename T, typename F>
static cmJSONHelper<T, E, CallState...> Required(E fail, F func)
{
return [fail, func](T& out, const Json::Value* value,
CallState&&... state) -> E {
if (!value) {
return fail;
}
return func(out, value, std::forward(state)...);
};
}
};