Use Validation-Rules as Embedded ELCL Document

This tutorial shows how you can define validation rules as an embedded ELCL document and apply them to a parsed configuration.

This approach keeps the rules readable and close to the configuration syntax itself, while still allowing you to embed everything directly into your C++ application.

Important

Before using validation rules, link exactly one validation-rules library variant as described in Integrate Validation Rules as a Submodule.

When to Use This Approach

Embedded ELCL validation documents are ideal for quick starts and moderate rule sets.

Small embedded rule sets are easy to read and maintain.

Rules stay close to the configuration format they validate.

Less suitable for very large or highly dynamic rule sets.

Errors in the rules document are detected at runtime, not at compile time.

If your rules are mostly static and maintained by humans, this approach is often clearer than constructing rules in C++ code.

Step 1: Add the Required Headers

Include both the configuration and validation-rules headers:

#include <erbsland/all_conf.hpp>
#include <erbsland/all_conf_vr.hpp>

#include <format>
#include <iostream>

Step 2: Embed the Rules Document

The rules document is standard ELCL text. Embed it as a C++ raw string literal and parse it using Parser::parseTextOrThrow().

const auto rulesDocument = parser.parseTextOrThrow(R"(
[server]
type: "section"

[server.host]
type: "text"
default: "127.0.0.1"

[server.port]
type: "integer"
minimum: 1024
maximum: 65535

[server.mode]
type: "text"
in: "dev", "prod"
default: "dev"
)");

Important

Do not indent the ELCL content inside the raw string literal. Leading whitespace becomes part of the text and may change parsing behavior.

Step 3: Build and Apply the Rules

Convert the parsed rules document into a vr::Rules instance and validate your configuration document:

using el::conf::vr::Rules;
const auto rules = Rules::createFromDocument(rulesDocument);

// Validate configuration for schema version 1
rules->validate(configDocument, 1);

During validation, the library:

  • Verifies structure (sections and keys)

  • Checks value types

  • Applies constraints (e.g., minimum, maximum, in)

  • Inserts default values where defined

If validation fails, an Error with category Validation is thrown.

Complete Example

#include <erbsland/all_conf.hpp>
#include <erbsland/all_conf_vr.hpp>

#include <format>
#include <iostream>

namespace el = erbsland;

int main() {
    try {
        el::conf::Parser parser;

        const auto configDocument = parser.parseTextOrThrow(R"(
[server]
port: 8443
        )");

        const auto rulesDocument = parser.parseTextOrThrow(R"(
[server]
type: "section"

[server.host]
type: "text"
default: "127.0.0.1"

[server.port]
type: "integer"
minimum: 1024
maximum: 65535

[server.mode]
type: "text"
in: "dev", "prod"
default: "dev"
        )");

        const auto rules =
            el::conf::vr::Rules::createFromDocument(rulesDocument);

        rules->validate(configDocument, 1);

        const auto host =
            configDocument->getOrThrow<std::string>("server.host");
        const auto port =
            configDocument->getOrThrow<int>("server.port");
        const auto mode =
            configDocument->getOrThrow<std::string>("server.mode");

        std::cout << std::format(
            "host={} port={} mode={}\n",
            host, port, mode);

        return 0;
    } catch (const el::conf::Error &error) {
        std::cerr << error.toText().toCharString() << "\n";
        return 1;
    }
}

Try a Failing Input

Change the port in the configuration to 80 and run validation again.

Because the rule defines minimum: 1024, validation will fail and throw an error describing the violated constraint.

This is a good way to verify that your rules behave exactly as intended.