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)
commit7d525492122eca394137cf656382ac3123cfe72b
treeceb70ffe06aa926993649e6862e5b9a0be826d5f
parent497c1e2072916b67a3d8aaddb211c0025998e01a
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<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]
This page took 0.026238 seconds and 4 git commands to generate.