Add `bt2c::ValReq` class template system
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 28 Mar 2024 17:53:31 +0000 (13:53 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Wed, 4 Sep 2024 19:05:14 +0000 (15:05 -0400)
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<MyUIntValInRangeReq>(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 <eeppeliteloop@gmail.com>
Change-Id: I71630fc51dafb79dd5ece10efbcf86f3f5933199
Reviewed-on: https://review.lttng.org/c/babeltrace/+/8207
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12696

src/Makefile.am
src/cpp-common/bt2c/val-req.hpp [new file with mode: 0644]

index b38d88d27473433ba9bd2185acfe6632dac86837..e6f39e69dd28f037bd25ce54961c11e1de16df95 100644 (file)
@@ -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 (file)
index 0000000..d70e43b
--- /dev/null
@@ -0,0 +1,946 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_CPP_COMMON_BT2C_VAL_REQ_HPP
+#define BABELTRACE_CPP_COMMON_BT2C_VAL_REQ_HPP
+
+#include <limits>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+
+#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 <typename ScalarValT> 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 <typename ValT, typename ValOpsT>
+class ValReq
+{
+public:
+    /* Shared pointer to constant value requirement */
+    using SP = std::shared_ptr<const ValReq>;
+
+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 <typename ValT, typename ValOpsT>
+class ValHasTypeReq : public ValReq<ValT, ValOpsT>
+{
+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<ValT, ValOpsT> {parentLogger}, _mType {type}
+    {
+    }
+
+    /*
+     * Returns a shared pointer to "value has type" requirement,
+     * forwarding the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(const ValType type, const Logger& parentLogger)
+    {
+        return std::make_shared<ValHasTypeReq>(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 <typename ValT, typename ValOpsT>
+class AnyIntValReq : public ValReq<ValT, ValOpsT>
+{
+public:
+    explicit AnyIntValReq(const Logger& parentLogger) noexcept :
+        ValReq<ValT, ValOpsT> {parentLogger}
+    {
+    }
+
+    /*
+     * Returns a shared pointer to any integer value requirement,
+     * forwarding the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(const Logger& parentLogger)
+    {
+        return std::make_shared<AnyIntValReq>(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 <typename ValT, typename ValOpsT>
+class UIntValReq : public ValHasTypeReq<ValT, ValOpsT>
+{
+public:
+    /*
+     * Builds an unsigned integer value: _validate() validates that the
+     * integer value is an unsigned integer type.
+     */
+    explicit UIntValReq(const Logger& parentLogger) noexcept :
+        ValHasTypeReq<ValT, ValOpsT> {ValType::UInt, parentLogger}
+    {
+    }
+
+    /*
+     * Returns a shared pointer to unsigned integer value requirement,
+     * forwarding the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(const Logger& parentLogger)
+    {
+        return std::make_shared<UIntValReq>(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 <typename ValT, typename ValOpsT>
+class SIntValReq : public AnyIntValReq<ValT, ValOpsT>
+{
+public:
+    explicit SIntValReq(const Logger& parentLogger) noexcept :
+        AnyIntValReq<ValT, ValOpsT> {parentLogger}
+    {
+    }
+
+    /*
+     * Returns a shared pointer to signed value requirement, forwarding
+     * the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(const Logger& parentLogger)
+    {
+        return std::make_shared<SIntValReq>(parentLogger);
+    }
+
+protected:
+    void _validate(const ValT& val) const override
+    {
+        /* Validate that it's an integer value */
+        AnyIntValReq<ValT, ValOpsT>::_validate(val);
+
+        if (ValOpsT::valType(val) == ValType::SInt) {
+            /* Always correct */
+            return;
+        }
+
+        /* Validate the raw value */
+        static constexpr auto llMaxAsUll =
+            static_cast<unsigned long long>(std::numeric_limits<long long>::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 <typename ValT, typename ValOpsT, typename IntValT, ValType TypeV>
+class IntValInRangeReq : public ValHasTypeReq<ValT, ValOpsT>
+{
+private:
+    /* Raw value type */
+    using _RawVal = typename ValOpsT::template ScalarValRawValT<IntValT>;
+
+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<ValT, ValOpsT> {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<ValT, ValOpsT>::SP shared(const bt2s::optional<_RawVal>& minVal,
+                                                     const bt2s::optional<_RawVal>& maxVal,
+                                                     const Logger& parentLogger)
+    {
+        return std::make_shared<IntValInRangeReq>(minVal, maxVal, parentLogger);
+    }
+
+    /*
+     * Returns a shared pointer to "integer value in range" requirement,
+     * forwarding the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(const _RawVal exactVal,
+                                                     const Logger& parentLogger)
+    {
+        return std::make_shared<IntValInRangeReq>(exactVal, parentLogger);
+    }
+
+protected:
+    void _validate(const ValT& val) const override
+    {
+        ValHasTypeReq<ValT, ValOpsT>::_validate(val);
+
+        auto& intVal = static_cast<const IntValT&>(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 <typename RawValT>
+std::string rawValStr(const RawValT& rawVal)
+{
+    return fmt::to_string(rawVal);
+}
+
+template <>
+inline std::string rawValStr<std::string>(const std::string& val)
+{
+    return fmt::format("`{}`", val);
+}
+
+template <>
+inline std::string rawValStr<bool>(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 <typename ValT, typename ValOpsT, typename ScalarValT, ValType TypeV>
+class ScalarValInSetReq : public ValHasTypeReq<ValT, ValOpsT>
+{
+private:
+    /* Raw value type */
+    using _RawVal = typename ValOpsT::template ScalarValRawValT<ScalarValT>;
+
+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<ValT, ValOpsT> {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<ValT, ValOpsT>::SP shared(Set set, const Logger& parentLogger)
+    {
+        return std::make_shared<ScalarValInSetReq>(std::move(set), parentLogger);
+    }
+
+    /*
+     * Returns a shared pointer to "scalar value in set" requirement,
+     * forwarding the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(_RawVal rawVal, const Logger& parentLogger)
+    {
+        return std::make_shared<ScalarValInSetReq>(std::move(rawVal), parentLogger);
+    }
+
+protected:
+    void _validate(const ValT& val) const override
+    {
+        ValHasTypeReq<ValT, ValOpsT>::_validate(val);
+
+        auto& scalarVal = static_cast<const ScalarValT&>(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 <typename ValT, typename ValOpsT>
+class ArrayValReq : public ValHasTypeReq<ValT, ValOpsT>
+{
+public:
+    using SP = typename ValReq<ValT, ValOpsT>::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<std::size_t>& minSize,
+                         const bt2s::optional<std::size_t>& maxSize, SP elemValReq,
+                         const Logger& parentLogger) :
+        ValHasTypeReq<ValT, ValOpsT> {ValType::Array, parentLogger},
+        _mMinSize {minSize ? *minSize : std::numeric_limits<std::size_t>::min()},
+        _mMaxSize {maxSize ? *maxSize : std::numeric_limits<std::size_t>::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<std::size_t>& minSize,
+                         const bt2s::optional<std::size_t>& 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<std::size_t>& minSize,
+                     const bt2s::optional<std::size_t>& maxSize, SP elemValReq,
+                     const Logger& parentLogger)
+    {
+        return std::make_shared<ArrayValReq>(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<std::size_t>& minSize,
+                     const bt2s::optional<std::size_t>& maxSize, const Logger& parentLogger)
+    {
+        return std::make_shared<ArrayValReq>(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<ArrayValReq>(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<ArrayValReq>(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<ArrayValReq>(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<ArrayValReq>(parentLogger);
+    }
+
+protected:
+    void _validate(const ValT& val) const override
+    {
+        ValHasTypeReq<ValT, ValOpsT>::_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 <typename ValT, typename ValOpsT>
+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<ValT, ValOpsT>::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<ValT, ValOpsT>::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 <typename ValT, typename ValOpsT>
+class ObjValReq : public ValHasTypeReq<ValT, ValOpsT>
+{
+public:
+    /* Map of property name to property requirement */
+    using PropReqs = std::unordered_map<std::string, ObjValPropReq<ValT, ValOpsT>>;
+
+    /* 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<ValT, ValOpsT> {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<ValT, ValOpsT>::SP
+    shared(PropReqs propReqs, const bool allowUnknownProps, const Logger& parentLogger)
+    {
+        return std::make_shared<ObjValReq>(std::move(propReqs), allowUnknownProps, parentLogger);
+    }
+
+    /*
+     * Returns a shared pointer to object value requirement, forwarding
+     * the parameters to the constructor.
+     */
+    static typename ValReq<ValT, ValOpsT>::SP shared(PropReqs propReqs, const Logger& parentLogger)
+    {
+        return std::make_shared<ObjValReq>(std::move(propReqs), parentLogger);
+    }
+
+protected:
+    void _validate(const ValT& val) const override
+    {
+        ValHasTypeReq<ValT, ValOpsT>::_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 */
This page took 0.035558 seconds and 4 git commands to generate.