Validation

This is the part of the library that allows validating a data container is valid!

Why not use JSON Schema?

There is actually a plan to digest JSON Schema as well, this library just provides a way to define constraints and enforce them. So one could build a JSON schema parser that makes these constraints and then we could test the validity of any data container.

This library focuses on working with garlic layers for validation meaning you can test against any type that conforms to garlic::ViewLayer and that could include std::vector, RapidJSON types and your own custom types so long as you make wrappers for them.

Constraint

A constraint is the smallest unit of validation, a constraint should take a layer and say if it is valid or not. It has two primary methods.

There are finite set of constraints and there is documentation for them in garlic/constraints.h

auto regex = garlic::make_constraint<garlic::regex_tag>("\\d{1,3});

While this may seem like a clunky way to create these constraints, using the documentation and knowing the convention helps a lot to make it easier to work with these.

Every constraint has a name, message in case it fails and whether or not it is fatal. It just means if there are three constraints next to each other, if one of them fails, we break the loop. Otherwise, you continue despite the failure. This will make more sense as you read through this documentation.

NOTE: Every call to garlic::make_constraint() can optionally end with name, custom message and fatal args.

auto age = make_constraint<regex_tag>("\\d{1,3}", "age_constraint", "Invalid Age!", true);

You can then use a constraint to validate any type conforming to garlic::ViewLayer

JsonDocument json;
age.quick_test(clove);
age.quick_test(json);
if (auto result = age.test(clove); !result) {
std::cout << result.reason << std::endl;
}
...
Note
a garlic::Constraint is like a std::shared_ptr, copying it only copies a pointer to an inner context.

garlic::Constraint does not have any constructor, you instead need to either use garlic::Constraint::make or use a more convenient shortcut garlic::make_constraint().

If you need to make an empty (null context really) constraint, you can use garlic::Constraint::empty() but be careful, passing an empty constraint is like passing a nullptr, it can cause crashes if not handled properly.

auto constraint = garlic::Constraint::empty();
if (...) { // using the empty() method is really to cover cases like this.
constraint = garlic::make_constraint<garlic::range_tag>(10, 25);
}
if (constraint) // check if constraint is initialized
auto result = constraint.quick_test(...);

Field

A garlic::Field is just a group of constraints stored in a single entity with meta data and some properties.

auto user_name = garlic::make_field("User Name");
user_name->add_constraint(garlic::make_constraint<garlic::regex_tag>("\\w{3,12}"));
user_name->add_constraint<garlic::regex_tag>("\\w{3,12}"); // more convenient
auto strict_user_name = garlic::make_field("Strict User Name", {... list of constraints...});
strict_user_name->inherit_constraints_from(user_name);

Fields can have meta data which is basically a dictionary mapping strings to strings.

user_name->meta()["message"] = "Custom message when a field fails!";
user_name->meta()["db/type] = "VARCHAR";

That meta data is meant to be used to store arbitrary information so it can be used for future tools to generate other formats like making migration scripts, or build html forms.

If you set a field to ignore details, it'll return a leaf garlic::ConstraintResult without any details from its inner constraints. This is helpful when you want to use a Field as a constraint that is made of other constraints. For instance, you can pack a bunch of constraints that together validate whether a user name is valid not, but the resulting constraint should only say "invalid username.".

user_name->set_ignore_details(true); // default is false.

Model

A garlic::Model is a way to describe an object's structure. It is essentially a mapping of keys to a field descriptor object.

key -> { required: bool, field: std::shared_ptr<garlic::Field> }

For example, imagine a User object that must have a username and a phone number but phone is optional.

auto user_model = garlic::make_model("User");
user_model->add_field("username", user_name); // user_name from previous section.
user_model->add_field("phone",
garlic::make_field({garlic::make_constraint<garlic::regex_tag>("regex pattern")}),
false); // false here indicates the field is NOT required.
CloveDocument doc; // some arbitrary document.
auto result = user_model->validate(doc);

Model and Field Tags

You can make constraints that validate a model or a field. See garlic::field_tag and garlic::model_tag.

// ignore details regardless of what 'user_name' field's ignore detail settings.
auto field_constraint = garlic::make_constraint<garlic::field_tag>(user_name, false, true);
auto model_constraint = garlic::make_constraint<garlic::model_tag>(user_model);

Module

Now a garlic::Module is simply a repository that contains a bunch of models and fields.

module->add_field(garlic::make_field("UserName"));
module->add_field("SomeAliasName", module->get_field("UserName"));
module->add_model(garlic::make_model("User"));

The idea is that you can make a module from a layer. Meaning you could load a module from a JSON, Yaml, MessagePack or whatever layer you have.

FILE* module_file = fopen("module.json", "r");
auto json_doc = garlic::providers::rapidjson::Json::load(module_file);
auto result = garlic::parsing::load_module(json_doc);
if (!result) {
std::cout << result.error().message() << std::endl;
// handle error here.
}
auto user_model = result->get_model("User");

See garlic/parsing/module.h

There is a plan to build module parsers that read JSON schema and other means like maybe defining a set of python files that describe these models.

Curious about the format of this module? See Module Definitions

garlic::Module
A Module is a repository of models, fields and constraints.
Definition: module.h:39
garlic::Module::add_model
tl::expected< void, std::error_code > add_model(model_pointer model) noexcept
Definition: module.h:76
garlic::CloveDocument
GenericCloveDocument< CAllocator > CloveDocument
A clove document (value) conforming to garlic::RefLayer.
Definition: clove.h:502
garlic::parsing::load_module
static tl::expected< Module, std::error_code > load_module(Layer &&layer) noexcept
Loads a Module from any layer.
Definition: module.h:404
garlic::Module::get_field
field_pointer get_field(const text &name) const noexcept
Definition: module.h:134
garlic::Module::add_field
tl::expected< void, std::error_code > add_field(text &&alias, field_pointer field) noexcept
Definition: module.h:90
garlic::regex_tag
Constraint Tag that passes if a specified regex pattern passes the test.
Definition: constraints.h:539
garlic::Constraint::empty
static Constraint empty() noexcept
Make a Constraint without any context.
Definition: constraints.h:293