Page MenuHomePhorge

validator.hpp
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

validator.hpp

/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <functional>
#include <types.hpp>
namespace Kazv
{
/**
* Run validator against `input[k]` and add it to `ret`.
*
* A validator is a function taking a Jsonish value and
* return a pair of boolean (the status) and a Jsonish value (the output).
*
* If the status is true, the output will be added to `ret[k]`.
* If the status is false, the output will be ignored.
*
* Example:
* ```
* json j = json::object();
* Kazv::cast(
* j,
* json{{"foo", "1"}},
* "foo",
* [](const json &v) -> std::pair<bool, json> {
* return v.is_string()
* ? {true, v.template get<std::string>() + "x"}
* : {false, v};
* }
* ); // true, j = {{"foo", "1x"}}};
*
* json j2 = json::object();
* Kazv::cast(
* j,
* json{{"foo", 1}},
* "foo",
* [](const json &v) -> std::pair<bool, json> {
* return v.is_string()
* ? {true, v.template get<std::string>() + "x"}
* : {false, v};
* }
* ); // true, j2 = json::object();
* ```
*
* @param input The original json taken from user input.
* @param k The key (usually a property name of a json object) to process on.
* @param f The validator.
* @param ret The read-write reference of the returning json.
*
* @return Whether the cast is successful. The cast is successful if and only if
* the validator returns a status of true.
*/
template<class Jsonish, class Key, class Validator>
bool cast(Jsonish &ret, const Jsonish &input, Key &&k, Validator &&f)
{
if (!input.contains(k)) {
return false;
}
auto [res, output] = std::invoke(std::forward<Validator>(f), input[k]);
if (!res) {
return false;
}
ret[k] = output;
return true;
}
/**
* Make an identity validator.
*
* The returned validator runs a predicate `f` against a value `val`.
* If the return value is true, return a pair of `true` and `val`.
* Otherwise, return a pair of `false` and `val`.
*
* @param f The predicate that judges the validity of a value.
* @return A validator.
*/
template<class Func>
auto identValidate(Func &&f)
{
return [f=std::forward<Func>(f)](auto &&val) mutable {
auto res = std::invoke(std::forward<Func>(f), val);
return std::pair<bool, json>(res, std::forward<decltype(val)>(val));
};
}
/**
* Strategy used for castArray().
*/
enum class CastArrayStrategy
{
/// If any item is invalid, ignore them and add all valid entries into the output.
IgnoreInvalid,
/// If any item is invalid, fail the entire cast.
FailAll,
};
/**
* Cast an array of items.
*
* Run the validator on each item of `input[k]` and accumulate the result
* into `ret`.
*
* Example:
* ```
* json j = json::object();
* Kazv::castArray(
* j,
* json{{"foo", {"1", "2", "3", 4}}},
* "foo",
* [](const json &v) -> std::pair<bool, json> {
* return v.is_string()
* ? {true, v.template get<std::string>() + "x"}
* : {false, v};
* },
* Kazv::CastArrayStrategy::IgnoreInvalid
* ); // true, j = {{"foo", {"1x", "2x", "3x"}}};
*
* json j2 = json::object();
* Kazv::castArray(
* j2,
* json{{"foo", {"1", "2", "3", 4}}},
* "foo",
* [](const json &v) -> std::pair<bool, json> {
* return v.is_string()
* ? {true, v.template get<std::string>() + "x"}
* : {false, v};
* },
* Kazv::CastArrayStrategy::FailAll
* ); // false, j2 = json::object();
* ```
*
* @param input The original json taken from user input.
* @param k The key (usually a property name of a json object) to process on.
* @param f The validator.
* @param ret The read-write reference of the returning json.
* @param strategy The strategy to use when handling invalid items.
* `CastArrayStrategy::IgnoreInvalid` makes the cast always successful
* (providing `input[k]` is an array), and all valid items will be added
* to `ret`.
* `CastArrayStrategy::FailAll` makes the cast successful only when all
* input items can be casted.
*
* @return Whether the cast is successful. If `input[k]` is not an array,
* it be unsuccessful.
*/
template<class Jsonish, class Key, class Validator>
bool castArray(Jsonish &ret, const Jsonish &input, Key &&k, Validator &&f, CastArrayStrategy strategy)
{
using JsonT = std::decay_t<Jsonish>;
if (!(input.contains(k) && input[k].is_array())) {
return false;
}
auto array = JsonT::array();
for (const auto &inputItem : input[k]) {
auto [res, outputItem] = std::invoke(f, inputItem);
if (!res) {
if (strategy == CastArrayStrategy::FailAll) {
return false;
}
} else {
array.push_back(std::move(outputItem));
}
}
ret[k] = std::move(array);
return true;
}
/**
* Strategy used for castObject().
*/
enum class CastObjectStrategy
{
/// If any item is invalid, ignore them and add all valid entries into the output.
IgnoreInvalid,
/// If any item is invalid, fail the entire cast.
FailAll,
};
/**
* Cast an object of items.
*
* Run the validator on each key-value pair (as a json array) of `input[k]`
* and accumulate the result into `ret`.
*
* Example:
* ```
* json j = json::object();
* Kazv::castObject(
* j,
* json{{"foo", {{"1", "2"}, {"3", 4}}}},
* "foo",
* [](const json &kv) -> std::pair<bool, json> {
* const key = kv[0];
* const value = kv[1];
* return value.is_string()
* ? {true, json::array({key, v.template get<std::string>() + "x"})}
* : {false, v};
* },
* Kazv::CastObjectStrategy::IgnoreInvalid
* ); // true, j = {{"foo", {{"1", "2x"}}}};
*
* json j2 = json::object();
* Kazv::castObject(
* j2,
* json{{"foo", {{"1", "2"}, {"3", 4}}}},
* "foo",
* [](const json &v) -> std::pair<bool, json> {
* const key = kv[0];
* const value = kv[1];
* return value.is_string()
* ? {true, json::array({key, v.template get<std::string>() + "x"})}
* : {false, v};
* },
* Kazv::CastObjectStrategy::FailAll
* ); // false, j2 = json::object();
* ```
*
* @param input The original json taken from user input.
* @param k The key (usually a property name of a json object) to process on.
* @param f The validator.
* @param ret The read-write reference of the returning json.
* @param strategy The strategy to use when handling invalid items.
* `CastObjectStrategy::IgnoreInvalid` makes the cast always successful
* (providing `input[k]` is an object), and all valid items will be added
* to `ret`.
* `CastObjectStrategy::FailAll` makes the cast successful only when all
* input items can be casted.
*
* @return Whether the cast is successful. If `input[k]` is not an object,
* it be unsuccessful.
*/
template<class Jsonish, class Key, class Validator>
bool castObject(Jsonish &ret, const Jsonish &input, Key &&k, Validator &&f, CastObjectStrategy strategy)
{
using JsonT = std::decay_t<Jsonish>;
if (!(input.contains(k) && input[k].is_object())) {
return false;
}
auto obj = JsonT::object();
for (const auto &[inputKey, inputValue] : input[k].items()) {
auto inputItem = JsonT::array({JsonT(inputKey), inputValue});
auto [res, outputItem] = std::invoke(f, inputItem);
if (!res) {
if (strategy == CastObjectStrategy::FailAll) {
return false;
}
} else {
auto outputKey = outputItem[0];
auto outputValue = std::move(outputItem)[1];
obj[std::move(outputKey)] = std::move(outputValue);
}
}
ret[k] = std::move(obj);
return true;
}
/**
* Replace a non-existent value with a default one.
*
* If `input` contains `k`, return `input` as-is.
* Otherwise return `input` but with `[k]` replaced with `defaultValue`.
*
* @param input The input value.
* @param k The key to process on.
* @param defaultValue The default value to replace with.
* @return `input` if `input` contains `k`, otherwise, `input` where `[k]`
* is replaced with `defaultValue`.
*/
template<class Jsonish, class Key, class Jsonish2>
std::decay_t<Jsonish> makeDefaultValue(Jsonish &&input, Key &&k, Jsonish2 &&defaultValue)
{
using JsonT = std::decay_t<Jsonish>;
if (input.contains(k)) {
return std::forward<Jsonish>(input);
} else {
JsonT output = std::forward<Jsonish>(input);
output[std::forward<Key>(k)] = std::forward<Jsonish2>(defaultValue);
return output;
}
}
}

File Metadata

Mime Type
text/x-c++
Expires
Sun, Jan 19, 8:35 AM (1 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55032
Default Alt Text
validator.hpp (9 KB)

Event Timeline