From: Philippe Proulx Date: Thu, 28 Mar 2024 17:53:31 +0000 (-0400) Subject: Add `bt2c::ValReq` class template system X-Git-Url: http://drtracing.org/?a=commitdiff_plain;h=7d525492122eca394137cf656382ac3123cfe72b;p=babeltrace.git Add `bt2c::ValReq` class template system This set of class templates makes it possible to get basic requirement classes to validate JSON-like value objects, that is, a system of null, boolean, unsigned/signed integer, real, string, array, and object value objects. All the class templates accept a `ValT` parameter which is the base type of the objects to validate, as well as `ValOpsT`, a structure which defines specific value operations. See the `bt2c::ValReq` class template comment to learn the requirements of `ValOpsT`. Having class templates will make it possible to create both: • JSON value requirements, to validate a CTF2‑SPEC‑2.0 [1] metadata stream within the `ctf` plugin. • libbabeltrace2 value object (`bt2::ConstValue`) requirements, to validate component initialization parameters, for example. A value requirement (`bt2c::ValReq` instance; omitting the namespace for the rest of this message) is an object which offers the validate() method to validate some value object. The current concrete requirement class templates are (validating some value V): `ValHasTypeReq`: `AnyIntValReq`: `UIntValReq`: Validates the type of V. `SIntValReq`: Validates the type of V (unsigned or signed integer) and that the raw value of V is between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807. `IntValInRangeReq`: Validates the type of V and that the raw value of the integer value object V is within a given range. `ScalarValInSetReq`: Validates the type of V and that the raw value of the scalar value object V is an element of a given set. `ArrayValReq`: Validates the type of V, that the size of V is within a given range, and, optionally, that all the elements of V satisfy a given value requirement. `ObjValReq`: Validates the type of V and that the properties of V satisfy a given set of property requirements (no missing mandatory property, no unknown property if not allowed, valid property value). ValReq::validate() throws `TextParseError` using the text location of the value (provided by `ValOpsT`) to validate when it fails. `ArrayValReq` and `ObjValReq` accept zero or more shared value requirements: a user of this API may extend `ValReq` to create a custom value requirement (just implement the virtual _validate() method). A custom value requirement may reuse existing value requirements internally. Each value requirement class C has its static shared() method which accepts the same parameters as the constructor(s) of C and returns a `ValReq::SP` instance. Those shared() methods are helpers to make the usage site clearer, for example (assuming some custom aliases for specific value object types): MyUIntValInRangeReq::shared(0, 255) // vs. std::make_shared(0, 255) Of course this system is not meant to semantically validate some value. Even JSON Schema [2], which barectf pushes to its limit for example [3], cannot do that. But those value requirement classes can certainly remove a lot of redundant code. Here are a few examples of value requirement object construction and corresponding error messages to grasp how to use this API (assume some custom aliases starting with `Json`): Simple type check: Code: JsonValHasTypeReq {ValType::Str} Error example: [1:1] Expecting a string. Exactly `true`: Code: JsonBoolValInSetReq {true} Error examples: [1:1] Expecting a boolean. [1:1] Unexpected value `false`: expecting `true`. Byte: Code: JsonUIntValInRangeReq {0, 255} Error examples: [1:1] Expecting an unsigned integer. [1:1] Integer 256 is too large: expecting at most 255. Display base: Code: JsonUIntValInSetReq {{2, 8, 10, 16}} Error examples: [1:1] Expecting an unsigned integer. [1:1] Unexpected value 5: expecting 2, 8, 10, or 16. Choice amongst three strings: Code: JsonStrValInSetReq {JsonStrValInSetReq::Set { "Patrick", "Alec", "Annie" }} Error examples: [1:1] Expecting a string. [1:1] Unexpected value `Yves`: expecting `Alec`, `Annie`, or `Patrick`. CTF 2 UUID: Code: JsonArrayValReq { 16, JsonUIntValInRangeReq::shared(0, 255) } Error examples: [1:1] Expecting an array. [1:1] Size of array (2) is too small: expecting at least 16 elements. [1:1] Size of array (19) is too large: expecting at most 16 elements. [1:36] In array element #11: [1:36] Expecting an unsigned integer. [1:42] In array element #13: [1:42] Integer 257 is too large: expecting at most 255. CTF2-SPECRC-5.0 field location: Code: JsonArrayValReq { 2, nonstd::nullopt, JsonValHasTypeReq::shared(ValType::Str) } Error examples: [1:1] Expecting an array. [1:1] Size of array (1) is too small: expecting at least 2 elements. [1:11] In array element #2: [1:11] Expecting a string. CTF2-SPECRC-5.0 clock class fragment: Code: JsonObjValReq { { {"type", { JsonStrValInSetReq::shared("clock-class"), true }}, {"frequency", { JsonUIntValInRangeReq::shared(1, nonstd::nullopt), true }}, {"name", { JsonValHasTypeReq::shared(ValType::Str) }}, {"description", { JsonValHasTypeReq::shared(ValType::Str) }}, {"uuid", { JsonArrayValReq::shared( 16, JsonUIntValInRangeReq::shared(0, 255) ) }}, {"origin-is-unix-epoch", { JsonValHasTypeReq::shared(ValType::Bool) }}, {"offset", { JsonObjValReq::shared({ {"seconds", { JsonValHasTypeReq::shared(ValType::UInt) }}, {"cycles", { JsonValHasTypeReq::shared(ValType::UInt) }}, }) }}, {"precision", { JsonValHasTypeReq::shared(ValType::UInt) }}, {"user-attributes", { JsonValHasTypeReq::shared(ValType::Obj) }}, {"extensions", { JsonValHasTypeReq::shared(ValType::Obj) }}, } } Error examples: [1:1] Expecting an object. [1:1] Missing mandatory object property `type`. [1:1] Missing mandatory object property `frequency`. [1:22] Unknown object property `meow`. [1:10] In object property `type`: [1:10] Unexpected value `salut`: expecting `clock-class`. [1:38] In object property `frequency`: [1:32] Expecting an unsigned integer. [1:54] In object property `offset`: [1:63] Unknown object property `meow`. [1:54] In object property `offset`: [1:66] In object property `seconds`: [1:66] Expecting an unsigned integer. [1:50] In object property `uuid`: [1:63] In array element #5: [1:63] Integer 301 is too large: expecting at most 255. [1]: https://diamon.org/ctf/CTF2-SPEC-2.0.html [2]: https://json-schema.org/ [3]: https://github.com/efficios/barectf/tree/stable-3.0/barectf/schemas/config Signed-off-by: Philippe Proulx Change-Id: I71630fc51dafb79dd5ece10efbcf86f3f5933199 Reviewed-on: https://review.lttng.org/c/babeltrace/+/8207 Reviewed-on: https://review.lttng.org/c/babeltrace/+/12696 --- diff --git a/src/Makefile.am b/src/Makefile.am index b38d88d2..e6f39e69 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -191,6 +191,7 @@ cpp_common_libcpp_common_la_SOURCES = \ cpp-common/bt2c/text-loc-str.hpp \ cpp-common/bt2c/type-traits.hpp \ cpp-common/bt2c/uuid.hpp \ + cpp-common/bt2c/val-req.hpp \ cpp-common/bt2c/vector.hpp \ cpp-common/bt2s/make-unique.hpp \ cpp-common/bt2s/optional.hpp \ diff --git a/src/cpp-common/bt2c/val-req.hpp b/src/cpp-common/bt2c/val-req.hpp new file mode 100644 index 00000000..d70e43bd --- /dev/null +++ b/src/cpp-common/bt2c/val-req.hpp @@ -0,0 +1,946 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_CPP_COMMON_BT2C_VAL_REQ_HPP +#define BABELTRACE_CPP_COMMON_BT2C_VAL_REQ_HPP + +#include +#include +#include +#include +#include +#include + +#include "logging.hpp" + +#include "exc.hpp" +#include "text-loc.hpp" + +namespace bt2c { + +/* + * This set of class templates makes it possible to get basic + * requirement classes to validate JSON-like value objects, that is, a + * system of null, boolean, unsigned/signed integer, real, string, + * array, and object value objects. + * + * All the class templates accept a `ValT` parameter which is the base + * type of the objects to validate, as well as `ValOpsT`, a structure + * which defines specific value operations. + * + * The requirements of `ValOptsT` are: + * + * static ValType valType(const ValT& val): + * Returns the type of `val` as a `ValType` value. + * + * static const char *typeDetStr(ValType type): + * Returns the determiner (lowercase) to use for the value type + * `type`. + * + * This is required to generate an error message. + * + * static const char *typeStr(ValType type): + * Returns the name (lowercase) of the value type `type`. + * + * This is required to generate an error message. + * + * static constexpr const char *objValPropName: + * Name (lowercase) of an object value object property. + * + * static const TextLoc& valLoc(const ValT& val): + * Returns the location of the value `val`. + * + * This is required to build a text parse error (`TextParseError`). + * + * static const SomeUIntVal& asUInt(const ValT& val): + * Returns `val` as an unsigned integer value object. + * + * static const SomeStrVal& asStr(const ValT& val): + * Returns `val` as a string value object. + * + * static const SomeArrayVal& asArray(const ValT& val): + * Returns `val` as an array value object. + * + * static const SomeObjVal& asObj(const ValT& val): + * Returns `val` as an object value object. + * + * template using ScalarValRawValT = ...: + * Raw value type of the scalar value object type `ScalarValT`. + * + * static unsigned long long scalarValRawVal(const SomeUIntVal& val): + * Returns the raw value of the unsigned value object `val`. + * + * static long long scalarValRawVal(const SomeSIntVal& val): + * Returns the raw value of the signed value object `val`. + * + * static double scalarValRawVal(const SomeRealVal& val): + * Returns the raw value of the real value object `val`. + * + * static const std::string& scalarValRawVal(const SomeStrVal& val): + * Returns the raw value of the string value object `val`. + * + * static std::size_t arrayValSize(const SomeArrayVal& val): + * Returns the size of the array value object `val`. + * + * static const ValT& arrayValElem(const SomeArrayVal& val, std::size_t index): + * Returns the element of the array value object `val` at the index + * `index`. + * + * static const ValT *objValVal(const SomeObjVal& val, const std::string& key): + * Returns the value of the object value object `val` having the key + * `key`, or `nullptr` if there's none. + * + * static SomeIterator objValBegin(const SomeObjVal& val): + * Returns an iterator at the beginning of the object value object + * `val`. + * + * static SomeIterator objValEnd(const SomeObjVal& val): + * Returns an iterator at the end of the object value object `val`. + * + * static const std::string& objValItKey(const SomeIterator& it): + * Returns the key of the object value object iterator `it`. + * + * static const ValT& objValItVal(const SomeIterator& it): + * Returns the value object of the object value object iterator + * `it`. + */ + +/* + * Value requirement abstract base class. + */ +template +class ValReq +{ +public: + /* Shared pointer to constant value requirement */ + using SP = std::shared_ptr; + +protected: + /* + * Builds a value requirement. + */ + explicit ValReq(const Logger& parentLogger) noexcept : _mLogger {parentLogger, "VAL-REQ"} + { + } + +public: + /* Deleted copy/move operations to simplify */ + ValReq(const ValReq&) = delete; + ValReq(ValReq&&) = delete; + ValReq& operator=(const ValReq&) = delete; + ValReq& operator=(ValReq&&) = delete; + + virtual ~ValReq() = default; + + /* + * Validates that `val` satisfies this requirement. + */ + void validate(const ValT& val) const + { + this->_validate(val); + } + +protected: + static const TextLoc& _loc(const ValT& val) noexcept + { + return ValOpsT::valLoc(val); + } + + const Logger& _logger() const noexcept + { + return _mLogger; + } + +private: + /* + * Requirement-specific validation. + */ + virtual void _validate(const ValT&) const + { + } + +protected: + Logger _mLogger; +}; + +/* + * Value type. + */ +enum class ValType +{ + Null, + Bool, + SInt, + UInt, + Real, + Str, + Array, + Obj, +}; + +/* + * "Value has type" requirement. + * + * An instance of this class validates that a value has a given type. + */ +template +class ValHasTypeReq : public ValReq +{ +public: + /* + * Builds a "value has type" requirement: _validate() validates that + * the type of the value is `type`. + */ + explicit ValHasTypeReq(const ValType type, const Logger& parentLogger) noexcept : + ValReq {parentLogger}, _mType {type} + { + } + + /* + * Returns a shared pointer to "value has type" requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(const ValType type, const Logger& parentLogger) + { + return std::make_shared(type, parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + if (ValOpsT::valType(val) != _mType) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(val), "Expecting {} {}.", + ValOpsT::typeDetStr(_mType), ValOpsT::typeStr(_mType)); + } + } + +private: + /* Required value type */ + ValType _mType; +}; + +/* + * Any integer value requirement. + * + * An instance of this class validates that a value is an integer value + * (unsigned or signed). + */ +template +class AnyIntValReq : public ValReq +{ +public: + explicit AnyIntValReq(const Logger& parentLogger) noexcept : + ValReq {parentLogger} + { + } + + /* + * Returns a shared pointer to any integer value requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(const Logger& parentLogger) + { + return std::make_shared(parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + if (!val.isUInt() && !val.isSInt()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(this->_logger(), Error, this->_loc(val), + "Expecting an integer."); + } + } +}; + +/* + * Unsigned integer (range) value requirement. + * + * An instance of this class validates that a value is an unsigned + * integer value. + */ +template +class UIntValReq : public ValHasTypeReq +{ +public: + /* + * Builds an unsigned integer value: _validate() validates that the + * integer value is an unsigned integer type. + */ + explicit UIntValReq(const Logger& parentLogger) noexcept : + ValHasTypeReq {ValType::UInt, parentLogger} + { + } + + /* + * Returns a shared pointer to unsigned integer value requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(const Logger& parentLogger) + { + return std::make_shared(parentLogger); + } +}; + +/* + * Signed integer value (range) requirement. + * + * An instance of this class validates that a value is an integer value + * (unsigned or signed) and that its raw value is between + * -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807. + */ +template +class SIntValReq : public AnyIntValReq +{ +public: + explicit SIntValReq(const Logger& parentLogger) noexcept : + AnyIntValReq {parentLogger} + { + } + + /* + * Returns a shared pointer to signed value requirement, forwarding + * the parameters to the constructor. + */ + static typename ValReq::SP shared(const Logger& parentLogger) + { + return std::make_shared(parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + /* Validate that it's an integer value */ + AnyIntValReq::_validate(val); + + if (ValOpsT::valType(val) == ValType::SInt) { + /* Always correct */ + return; + } + + /* Validate the raw value */ + static constexpr auto llMaxAsUll = + static_cast(std::numeric_limits::max()); + + const auto rawVal = ValOpsT::scalarValRawVal(ValOpsT::asUInt(val)); + + if (rawVal > llMaxAsUll) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(val), + "Expecting a signed integer: {} is greater than {}.", rawVal, llMaxAsUll); + } + } +}; + +/* + * "Integer value in range" requirement template. + * + * An instance of this class validates that, given a value V of type + * `IntValT`: + * + * • V has the type enumerator `TypeV`. + * • The raw value of V is within a given range. + */ +template +class IntValInRangeReq : public ValHasTypeReq +{ +private: + /* Raw value type */ + using _RawVal = typename ValOpsT::template ScalarValRawValT; + +public: + /* + * Builds an "integer value in range" requirement: _validate() + * validates that the raw value of the integer value is: + * + * • If `minVal` is set: greater than or equal to `*minVal`. + * • If `maxVal` is set: less than or equal to `*maxVal`. + */ + explicit IntValInRangeReq(const bt2s::optional<_RawVal>& minVal, + const bt2s::optional<_RawVal>& maxVal, + const Logger& parentLogger) noexcept : + ValHasTypeReq {TypeV, parentLogger}, + _mMinVal {minVal ? *minVal : std::numeric_limits<_RawVal>::min()}, + _mMaxVal {maxVal ? *maxVal : std::numeric_limits<_RawVal>::max()} + { + } + + /* + * Builds an "integer value in range" requirement: _validate() + * validates that the raw value of the integer value is exactly + * `exactVal`. + */ + explicit IntValInRangeReq(const _RawVal exactVal, const Logger& parentLogger) noexcept : + IntValInRangeReq {exactVal, exactVal, parentLogger} + { + } + + /* + * Returns a shared pointer to "integer value in range" requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(const bt2s::optional<_RawVal>& minVal, + const bt2s::optional<_RawVal>& maxVal, + const Logger& parentLogger) + { + return std::make_shared(minVal, maxVal, parentLogger); + } + + /* + * Returns a shared pointer to "integer value in range" requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(const _RawVal exactVal, + const Logger& parentLogger) + { + return std::make_shared(exactVal, parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + ValHasTypeReq::_validate(val); + + auto& intVal = static_cast(val); + const auto rawVal = ValOpsT::scalarValRawVal(intVal); + + if (rawVal < _mMinVal) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(intVal), + "Integer {} is too small: expecting at least {}.", rawVal, _mMinVal); + } + + if (rawVal > _mMaxVal) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(intVal), + "Integer {} is too large: expecting at most {}.", rawVal, _mMaxVal); + } + } + +private: + /* Minimum raw value */ + _RawVal _mMinVal; + + /* Maximum raw value */ + _RawVal _mMaxVal; +}; + +namespace internal { + +template +std::string rawValStr(const RawValT& rawVal) +{ + return fmt::to_string(rawVal); +} + +template <> +inline std::string rawValStr(const std::string& val) +{ + return fmt::format("`{}`", val); +} + +template <> +inline std::string rawValStr(const bool& val) +{ + return val ? "true" : "false"; +} + +} /* namespace internal */ + +/* + * "Scalar value in set" requirement template. + * + * An instance of this class validates that, given a value V of type + * `ScalarValT`: + * + * • V has the type enumerator `TypeV`. + * • The raw value of V is an element of a given set. + */ +template +class ScalarValInSetReq : public ValHasTypeReq +{ +private: + /* Raw value type */ + using _RawVal = typename ValOpsT::template ScalarValRawValT; + +public: + /* + * Raw value set type. + * + * Using `std::set` instead of `std::unordered_set` because + * _setStr() needs the elements in order. + */ + using Set = std::set<_RawVal>; + + /* + * Builds a "scalar value in set" requirement: _validate() validates + * that the raw value of the scalar value is an element of `set`. + */ + explicit ScalarValInSetReq(Set set, const Logger& parentLogger) : + ValHasTypeReq {TypeV, parentLogger}, _mSet {std::move(set)} + { + } + + /* + * Builds a "scalar value in set" requirement: _validate() validates + * that the raw value of the scalar value is exactly `rawVal`. + */ + explicit ScalarValInSetReq(_RawVal rawVal, const Logger& parentLogger) : + ScalarValInSetReq {Set {std::move(rawVal)}, parentLogger} + { + } + + /* + * Returns a shared pointer to "scalar value in set" requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(Set set, const Logger& parentLogger) + { + return std::make_shared(std::move(set), parentLogger); + } + + /* + * Returns a shared pointer to "scalar value in set" requirement, + * forwarding the parameters to the constructor. + */ + static typename ValReq::SP shared(_RawVal rawVal, const Logger& parentLogger) + { + return std::make_shared(std::move(rawVal), parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + ValHasTypeReq::_validate(val); + + auto& scalarVal = static_cast(val); + const auto rawVal = ValOpsT::scalarValRawVal(scalarVal); + + if (_mSet.find(rawVal) == _mSet.end()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(val), "Unexpected value {}: expecting {}.", + internal::rawValStr(rawVal), this->_setStr()); + } + } + +private: + /* + * Serializes the raw values of `_mSet` and returns the resulting + * string. + */ + std::string _setStr() const + { + if (_mSet.size() == 1) { + /* Special case: direct value */ + return internal::rawValStr(*_mSet.begin()); + } else if (_mSet.size() == 2) { + /* Special case: "or" word without any comma */ + return fmt::format("{} or {}", internal::rawValStr(*_mSet.begin()), + internal::rawValStr(*std::next(_mSet.begin()))); + } + + /* Enumeration with at least one comma */ + std::ostringstream ss; + + { + const auto lastIt = std::prev(_mSet.end()); + + for (auto it = _mSet.begin(); it != lastIt; ++it) { + ss << internal::rawValStr(*it) << ", "; + } + + ss << "or " << internal::rawValStr(*lastIt); + } + + return ss.str(); + } + + /* Set of expected raw values */ + Set _mSet; +}; + +/* + * Array value requirement. + * + * An instance of this class validates that, given a value V: + * + * • V is an array value. + * • The size of V is within a given range. + * • All the elements of V satisfy a given value requirement. + */ +template +class ArrayValReq : public ValHasTypeReq +{ +public: + using SP = typename ValReq::SP; + + /* + * Builds an array value requirement: _validate() validates that, + * for a given array value V: + * + * • If `minSize` is set: the size of V is greater than or equal to + * `*minSize`. + * + * • If `maxSize` is set: the size of V is less than or equal to + * `*maxSize`. + * + * • If `elemValReq` is set: all the elements of V satisfy + * `*elemValReq`. + */ + explicit ArrayValReq(const bt2s::optional& minSize, + const bt2s::optional& maxSize, SP elemValReq, + const Logger& parentLogger) : + ValHasTypeReq {ValType::Array, parentLogger}, + _mMinSize {minSize ? *minSize : std::numeric_limits::min()}, + _mMaxSize {maxSize ? *maxSize : std::numeric_limits::max()}, + _mElemValReq {std::move(elemValReq)} + { + } + + /* + * Builds an array value requirement: _validate() validates that, + * for a given array value V: + * + * • If `minSize` is set: the size of V is greater than or equal to + * `*minSize`. + * + * • If `maxSize` is set: the size of V is less than or equal to + * `*maxSize`. + */ + explicit ArrayValReq(const bt2s::optional& minSize, + const bt2s::optional& maxSize, const Logger& parentLogger) : + ArrayValReq {minSize, maxSize, nullptr, parentLogger} + { + } + + /* + * Builds an array value requirement: _validate() validates that, + * for a given array value V: + * + * • The size of V is exactly `exactSize`. + * + * • If `elemValReq` is set: all the elements of V satisfy + * `*elemValReq`. + */ + explicit ArrayValReq(const std::size_t exactSize, SP elemValReq, const Logger& parentLogger) : + ArrayValReq {exactSize, exactSize, std::move(elemValReq), parentLogger} + { + } + + /* + * Builds an array value requirement: _validate() validates that, + * for a given array value V: + * + * • The size of V is exactly `exactSize`. + */ + explicit ArrayValReq(const std::size_t exactSize, const Logger& parentLogger) : + ArrayValReq {exactSize, exactSize, nullptr, parentLogger} + { + } + + /* + * Builds an array value requirement: _validate() validates that all + * the elements of a given array value satisfy `*elemValReq`, if + * set. + */ + explicit ArrayValReq(SP elemValReq, const Logger& parentLogger) : + ArrayValReq {bt2s::nullopt, bt2s::nullopt, std::move(elemValReq), parentLogger} + { + } + + /* + * Builds an array value requirement: _validate() validates that + * a given value is an array value. + */ + explicit ArrayValReq(const Logger& parentLogger) : + ArrayValReq {bt2s::nullopt, bt2s::nullopt, parentLogger} + { + } + + /* + * Returns a shared pointer to array value requirement, forwarding + * the parameters to the constructor. + */ + static SP shared(const bt2s::optional& minSize, + const bt2s::optional& maxSize, SP elemValReq, + const Logger& parentLogger) + { + return std::make_shared(minSize, maxSize, std::move(elemValReq), parentLogger); + } + + /* + * Returns a shared pointer to array value requirement, forwarding + * the parameters to the constructor. + */ + static SP shared(const bt2s::optional& minSize, + const bt2s::optional& maxSize, const Logger& parentLogger) + { + return std::make_shared(minSize, maxSize, parentLogger); + } + + /* + * Returns a shared pointer to array value requirement, forwarding + * the parameters to the constructor. + */ + static SP shared(const std::size_t exactSize, SP elemValReq, const Logger& parentLogger) + { + return std::make_shared(exactSize, std::move(elemValReq), parentLogger); + } + + /* + * Returns a shared pointer to array value requirement, forwarding + * the parameters to the constructor. + */ + static SP shared(const std::size_t exactSize, const Logger& parentLogger) + { + return std::make_shared(exactSize, parentLogger); + } + + /* + * Returns a shared pointer to array value requirement, forwarding + * the parameters to the constructor. + */ + static SP shared(SP elemValReq, const Logger& parentLogger) + { + return std::make_shared(std::move(elemValReq), parentLogger); + } + + /* + * Returns a shared pointer to array value requirement, forwarding + * the parameter to the constructor. + */ + static SP shared(const Logger& parentLogger) + { + return std::make_shared(parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + ValHasTypeReq::_validate(val); + + auto& arrayVal = ValOpsT::asArray(val); + const auto size = ValOpsT::arrayValSize(arrayVal); + + if (size < _mMinSize) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(val), + "Size of array ({}) is too small: expecting at least {} elements.", size, + _mMinSize); + } + + if (size > _mMaxSize) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(val), + "Size of array ({}) is too large: expecting at most {} elements.", size, _mMaxSize); + } + + if (_mElemValReq) { + for (std::size_t i = 0; i < size; ++i) { + auto& elemVal = ValOpsT::arrayValElem(arrayVal, i); + + try { + _mElemValReq->validate(elemVal); + } catch (const Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC( + this->_logger(), this->_loc(elemVal), "Invalid array element #{}.", i + 1); + } + } + } + } + +private: + std::size_t _mMinSize; + std::size_t _mMaxSize; + SP _mElemValReq; +}; + +/* + * Object value property requirement. + * + * An instance of this class contains the requirements of a single + * object value property, that is: + * + * • Whether or not it's required. + * • The requirement of the value of the property. + */ +template +class ObjValPropReq final +{ +public: + /* + * Builds an object value property requirement, required if + * `isRequired` is true: if `valReq` is set, then validate() + * validates that a value satisfies `*valReq`. + * + * Not `explicit` to make the construction of `ObjValReq` lighter. + */ + ObjValPropReq(typename ValReq::SP valReq = nullptr, + const bool isRequired = false) : + _mIsRequired {isRequired}, + _mValReq {std::move(valReq)} + { + } + + /* + * Whether or not the property is required. + */ + bool isRequired() const noexcept + { + return _mIsRequired; + } + + /* + * Validates that `val` satisfies this requirement. + */ + void validate(const ValT& val) const + { + if (_mValReq) { + _mValReq->validate(val); + } + } + +private: + /* Whether or not this property is required */ + bool _mIsRequired = false; + + /* Requirement of the value */ + typename ValReq::SP _mValReq; +}; + +/* + * Object value requirement. + * + * An instance of this class validates that, given a value V: + * + * • V is an object value. + * + * • The properties of V satisfy a given set of object value property + * requirements. + */ +template +class ObjValReq : public ValHasTypeReq +{ +public: + /* Map of property name to property requirement */ + using PropReqs = std::unordered_map>; + + /* Single entry (pair) of `PropReqs` */ + using PropReqsEntry = typename PropReqs::value_type; + +public: + /* + * Builds an object value requirement: _validate() validates that, + * for a given object value V: + * + * • If `allowUnknownProps` is false, then V has no value of which + * the key is not an element of the keys of `propReqs`. + * + * • For each property requirement PR having the key K in + * `propReqs`: if `PR.isRequired()`, then a value having the key K + * exists in V. + * + * • For each value VV having the key K in V: VV satisfies the value + * requirement, if any, of `propReqs[K]`. + */ + explicit ObjValReq(PropReqs propReqs, const bool allowUnknownProps, + const Logger& parentLogger) : + ValHasTypeReq {ValType::Obj, parentLogger}, + _mPropReqs {std::move(propReqs)}, _mAllowUnknownProps {allowUnknownProps} + { + } + + /* + * Builds an object value requirement: _validate() validates that, + * for a given object value V: + * + * • V has no value of which the key is not an element of the keys + * of `propReqs`. + * + * • For each property requirement PR having the key K in + * `propReqs`: if `PR.isRequired()`, then a value having the key K + * exists in V. + * + * • For each value VV having the key K in V: VV satisfies the value + * requirement, if any, of `propReqs[K]`. + */ + explicit ObjValReq(PropReqs propReqs, const Logger& parentLogger) : + ObjValReq {std::move(propReqs), false, parentLogger} + { + } + + /* + * Returns a shared pointer to object value requirement, forwarding + * the parameters to the constructor. + */ + static typename ValReq::SP + shared(PropReqs propReqs, const bool allowUnknownProps, const Logger& parentLogger) + { + return std::make_shared(std::move(propReqs), allowUnknownProps, parentLogger); + } + + /* + * Returns a shared pointer to object value requirement, forwarding + * the parameters to the constructor. + */ + static typename ValReq::SP shared(PropReqs propReqs, const Logger& parentLogger) + { + return std::make_shared(std::move(propReqs), parentLogger); + } + +protected: + void _validate(const ValT& val) const override + { + ValHasTypeReq::_validate(val); + + auto& objVal = ValOpsT::asObj(val); + const auto objValTypeStr = ValOpsT::typeStr(ValType::Obj); + + for (auto& keyPropReqPair : _mPropReqs) { + auto& key = keyPropReqPair.first; + + if (keyPropReqPair.second.isRequired() && !ValOpsT::objValVal(objVal, key)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(objVal), "Missing mandatory {} {} `{}`.", + objValTypeStr, ValOpsT::objValPropName, key); + } + } + + for (auto it = ValOpsT::objValBegin(objVal); it != ValOpsT::objValEnd(objVal); ++it) { + auto& key = ValOpsT::objValItKey(it); + auto& propVal = ValOpsT::objValItVal(it); + const auto keyPropReqPairIt = _mPropReqs.find(key); + + if (keyPropReqPairIt == _mPropReqs.end()) { + /* No property requirement found */ + if (_mAllowUnknownProps) { + continue; + } else { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC( + this->_logger(), Error, this->_loc(propVal), "Unknown {} {} `{}`.", + objValTypeStr, ValOpsT::objValPropName, key); + } + } + + try { + keyPropReqPairIt->second.validate(propVal); + } catch (const Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC( + this->_logger(), this->_loc(propVal), "Invalid {} {} `{}`.", objValTypeStr, + ValOpsT::objValPropName, key); + } + } + } + +private: + PropReqs _mPropReqs; + bool _mAllowUnknownProps; +}; + +} /* namespace bt2c */ + +#endif /* BABELTRACE_CPP_COMMON_BT2C_VAL_REQ_HPP */