C3/Src/ChannelLinter/FormElement.cpp

229 lines
8.1 KiB
C++
Raw Normal View History

2019-11-13 14:39:29 +00:00
#include "StdAfx.h"
#include "FormElement.h"
namespace MWR::C3::Linter
{
namespace
{
2019-11-18 12:57:56 +00:00
/// Validate input length against argument rules
/// @param argument definition
/// @returns input to validate
/// @throws std::invalid_argument if validation fails
inline void CheckLengthConstraint(json const& definition, std::string_view input)
{
if (definition.contains("min") && input.length() < definition["min"].get<size_t>())
throw std::invalid_argument("Input \""s + std::string(input) + "\" too short for argument " + definition.at("name").get<std::string>() + ". (min = " + std::to_string(definition["min"].get<size_t>()) + ", actual = " + std::to_string(input.length()) + ')');
if (definition.contains("max") && input.length() > definition["max"].get<size_t>())
throw std::invalid_argument("Input \""s + std::string(input) + "\" too long for argument " + definition.at("name").get<std::string>() + ". (max = " + std::to_string(definition["max"].get<size_t>()) + ", actual = " + std::to_string(input.length()) + ')');
}
2019-11-18 12:57:56 +00:00
/// Validate numeric value against argument rules
/// @param argument definition
/// @returns value to validate
/// @throws std::invalid_argument if validation fails
template<typename Numeric>
void CheckValueConstraint(json const& definition, Numeric value)
{
if (definition.contains("min") && value < definition["min"])
throw std::invalid_argument("Value too low for argument " + definition.at("name").get<std::string>() + ". (min = " + std::to_string(definition["min"].get<Numeric>()) + ", actual = " + std::to_string(value) + ')');
if (definition.contains("max") && value > definition["max"])
throw std::invalid_argument("Value too high for argument " + definition.at("name").get<std::string>() + ". (min = " + std::to_string(definition["min"].get<Numeric>()) + ", actual = " + std::to_string(value) + ')');
};
2019-11-18 12:57:56 +00:00
/// Concrete implementation for boolean argument type
class BooleanFormElement : public FormElement
{
public:
BooleanFormElement(json& element) : FormElement(element)
{
}
2019-11-18 12:57:56 +00:00
/// Set boolean value from input accepted values interpreted as true are "true", "yes", "y", "1"
/// @param input to validate
void ValidateAndSet(std::string_view input) override
{
std::string lowercase(input);
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
auto trueOptions = { "true", "yes", "y", "1" };
m_Definition["value"] = std::any_of(begin(trueOptions), end(trueOptions), [&lowercase](auto&& trueOption) {return lowercase == trueOption; });
}
};
2019-11-18 12:57:56 +00:00
/// Concrete implementation for string argument type
class StringFormElement : public FormElement
{
public:
StringFormElement(json& element) : FormElement(element)
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set string value from input
/// @param input to validate
/// @throws std::invalid_argument if validation fails
void ValidateAndSet(std::string_view input) override
{
CheckLengthConstraint(m_Definition, input);
m_Definition["value"] = input;
}
};
2019-11-18 12:57:56 +00:00
/// Concrete implementation for ip argument type
class IpFormElement : public FormElement
{
public:
IpFormElement(json& element) : FormElement(element)
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set IP value from input
/// @param input to validate
/// @throws std::invalid_argument if validation fails
void ValidateAndSet(std::string_view input) override
{
if (!IsIpV4(std::string(input)))
throw std::invalid_argument("Failed to IPv4 value from input ("s + std::string(input) + ')');
m_Definition["value"] = input;
}
2019-11-18 12:57:56 +00:00
/// Determine if input is an IPv4
/// @param input to validate
/// @returns true if input is in IPv4 dotted notation
static bool IsIpV4(std::string const& input)
{
sockaddr_in client;
client.sin_family = AF_INET;
switch (InetPtonA(AF_INET, input.c_str(), &client.sin_addr.s_addr))
{
case 1:
return true;
default:
return false;
}
}
};
2019-11-18 12:57:56 +00:00
/// Concrete implementation for binary argument type
class BinaryFormElement : public FormElement
{
public:
BinaryFormElement(json& element) : FormElement(element)
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set binary value from input (in base64 string)
/// @param input to validate
/// @throws std::invalid_argument if validation fails
void ValidateAndSet(std::string_view input) override
{
2019-11-18 12:57:56 +00:00
try
{
auto decoded = base64::decode<std::string>(input);
CheckLengthConstraint(m_Definition, decoded);
m_Definition["value"] = input;
}
catch (std::domain_error&)
{
throw std::invalid_argument("Failed to decode base64 encoded input \""s + std::string(input) + '"');
}
}
};
2019-11-18 12:57:56 +00:00
/// Concrete implementation for binary argument type
/// @tparam Numeric - type of numeric argument
// TODO constrain Numeric template parameter to numeric types only
template<typename Numeric>
class NumericFormElement : public FormElement
{
public:
NumericFormElement(json& element) : FormElement(element)
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set numeric value from input
/// @param input to validate
/// @throws std::invalid_argument if validation fails
void ValidateAndSet(std::string_view input) override
{
2019-11-18 12:57:56 +00:00
Numeric value;
auto x = std::from_chars(input.data(), input.data() + input.size(), value);
if (x.ptr != input.data() + input.size())
throw std::invalid_argument("Failed to read numeric value from input \""s + std::string(input) + '"');
CheckValueConstraint(m_Definition, value);
m_Definition["value"] = value;
}
};
2019-11-13 14:39:29 +00:00
}
FormElement::FormElement(json& definition) :
m_Definition(definition)
{
}
2019-11-18 12:57:56 +00:00
/// Generate FormElement::Type to_json and from_json fuctions to enable serialization
2019-11-13 14:39:29 +00:00
NLOHMANN_JSON_SERIALIZE_ENUM
(
FormElement::Type,
{
2019-11-13 14:39:29 +00:00
{FormElement::Type::Unknown, nullptr},
{FormElement::Type::Uint8, "uint8"},
{FormElement::Type::Uint16, "uint16"},
{FormElement::Type::Uint32, "uint32"},
{FormElement::Type::Uint64, "uint64"},
{FormElement::Type::Int8, "int8"},
{FormElement::Type::Int16, "int16"},
{FormElement::Type::Int32, "int32"},
{FormElement::Type::Int64, "int64"},
{FormElement::Type::Float, "float"},
{FormElement::Type::Boolean, "boolean"},
{FormElement::Type::String, "string"},
{FormElement::Type::Ip, "ip"},
{FormElement::Type::Binary, "binary"},
}
2019-11-13 14:39:29 +00:00
);
2019-11-13 14:39:29 +00:00
std::unique_ptr<FormElement> MakeFormElement(json& element)
{
if (!element.is_object())
throw std::invalid_argument { "Form element must be a json object." };
if (!element.contains("name"))
throw std::invalid_argument{ "Form element must contain 'name' property. \nInvalid element:\n" + element.dump(4) };
2019-11-13 14:39:29 +00:00
if (!element.contains("type"))
throw std::invalid_argument{ "Form element '" + element["name"].get<std::string>() + "' must contain 'type' property." };
2019-11-13 14:39:29 +00:00
switch (element["type"].get<FormElement::Type>())
{
case FormElement::Type::Uint8:
return std::make_unique<NumericFormElement<uint8_t>>(element);
case FormElement::Type::Uint16:
return std::make_unique<NumericFormElement<uint16_t>>(element);
case FormElement::Type::Uint32:
return std::make_unique<NumericFormElement<uint32_t>>(element);
case FormElement::Type::Uint64:
return std::make_unique<NumericFormElement<uint64_t>>(element);
case FormElement::Type::Int8:
return std::make_unique<NumericFormElement<int8_t>>(element);
case FormElement::Type::Int16:
return std::make_unique<NumericFormElement<int16_t>>(element);
case FormElement::Type::Int32:
return std::make_unique<NumericFormElement<int32_t>>(element);
case FormElement::Type::Int64:
return std::make_unique<NumericFormElement<int64_t>>(element);
case FormElement::Type::Float:
return std::make_unique<NumericFormElement<float>>(element);
case FormElement::Type::Boolean:
return std::make_unique<BooleanFormElement>(element);
case FormElement::Type::String:
return std::make_unique<StringFormElement>(element);
case FormElement::Type::Ip:
return std::make_unique<IpFormElement>(element);
case FormElement::Type::Binary:
return std::make_unique<BinaryFormElement>(element);
default:
throw std::runtime_error("Unknown form argument type: \"" + element["type"].get<std::string>() + '"');
}
}
2019-11-13 14:39:29 +00:00
}