Dependencies

Dependencies express conditional relationships between configuration elements. They allow you to define rules such as “if this value exists, that one must also exist” without duplicating logic in application code.

Dependencies are declared using the vr_dependency section list. They always operate on the presence or absence of values or sections, never on their actual content.

Important

Default values do not count as “configured” for dependency checks. Only nodes that explicitly exist in the configuration document are considered present.

[client.username]
type: "text"
is_optional: yes

[client.password]
type: "text"
is_optional: yes

*[client.vr_dependency]*
mode: "xnor"
source: "username"
target: "password"
error: "Configure username *and* password, or none of these values"

Rules for Dependencies

  1. Section List Required: Dependencies must be defined using a section list named vr_dependency.

    [server.username]
    type: "text"
    is_optional: yes
    
    [server.password]
    type: "text"
    is_optional: yes
    is_secret: yes
    
    *[vr_dependency]*
    mode: "if"
    source: "server.hostname"
    target: "server.ip_address"
    
  2. Placement and Scope: A vr_dependency may appear either:

    • at the document root, or

    • inside a node-rules definition for a section.

    The dependency applies only within the subtree of the section in which it is defined.

    [server]
    type: "section"
    
    # ...
    
    *[server.vr_dependency]*
    mode: "xor"
    source: "hostname"
    target: "ip_address"
    
  3. Mode Required: Each dependency must define a mode entry with a text value. The mode determines how the configuration state of source and target is interpreted.

    Validators must support the following modes:

    • if — If the source side is configured, the target side must also be configured. If the source is not configured, the dependency is considered satisfied.

    • if_not — If the source side is configured, the target side must not be configured.

    • or — At least one side (source or target) must be configured. Both sides may be configured.

    • xor — Exactly one side must be configured. Neither both nor none are allowed.

    • xnor — Either both sides must be configured, or neither.

    • and — Both source and target must be configured.

    If you are unsure how a specific mode behaves in edge cases, refer to Mode Logic Explained below. That section defines the exact evaluation matrix and should be treated as normative.

  4. Source and Target Required: Each dependency must define both source and target.

    • Each entry may be a single text value or a list of text values.

    • Values are relative name-paths resolved within the same section.

    [client]
    type: "section"
    
    [client.username]
    type: "text"
    is_optional: yes
    
    [client.password]
    type: "text"
    is_optional: yes
    
    *[client.vr_dependency]*
    mode: "if"
    source: "username"
    target: "password"
    
  5. Multiple Values (OR Semantics): If multiple name-paths are listed in source or target, that side of the dependency is considered configured if any of the listed nodes exists.

    # ...
    
    *[vr_dependency]*
    mode: "if"
    source: "api_key", "token"
    target: "endpoint"
    
  6. Custom Error Messages: A dependency may define an error entry to provide a custom validation message.

    # ...
    
    *[vr_dependency]*
    mode: "xor"
    source: "hostname"
    target: "ip_address"
    error: "Configure either 'hostname' or 'ip_address', not both."
    
  7. What “Configured” Means: For dependency evaluation, a node on the source or target side is considered configured only if it is explicitly present in the configuration document.

    A node is not considered configured if it exists solely because a default value was applied during parsing.

    In other words, dependencies operate on what the user actually wrote — not on values that were implicitly added by the validator.

    [app.a]
    type: "integer"
    default: 1
    
    [app]
    # "app.a" is NOT configured.
    # The value exists only because of the default.
    
    [app]
    a: 1
    # "app.a" IS configured.
    # The value is explicitly present in the document,
    # even if it equals the default.
    
  8. Valid Source and Target Paths: Each name-path listed in source and target must resolve to a node that has a node-rules definition and whose presence is not already unconditionally required.

    In other words, the referenced node must be conditionally present. A node is considered conditionally present if at least one element in its effective path:

    • is marked with is_optional: yes, or

    • defines a default value.

    A path must not:

    • reference nodes inside individual alternatives, or

    • reference elements within a section list (such as vr_entry values).

    [app.username]
    type: "text"
    
    [app.password]
    type: "text"
    default: ""
    
    *[vr_dependency]*
    mode: "if"
    source: "app.username"  # ERROR: app.username is not optional
    target: "app.password"
    
    [app.ports]
    type: "SectionList"
    
    [app.ports.vr_entry.port]
    type: "integer"
    default: 0
    
    [app.interface]
    type: "text"
    default: ""
    
    *[app.vr_dependency]*
    mode: "if"
    source: "app.ports.vr_entry.port"  # ERROR: Cannot reference elements of a section list
    target: "app.interface"
    
    *[app.ports]*
    type: "Section"
    
    [app.ports.start]
    type: "integer"
    default: 0
    
    [app.ports.end]
    type: "integer"
    default: 65534
    
    *[app.ports]*
    type: "Integer"
    default: 80
    
    [app.interface]
    type: "text"
    default: ""
    
    *[vr_dependency]*
    mode: "if"
    source: "app.ports.start"  # ERROR: app.ports is defined in alternatives
    target: "app.interface"
    
  9. Mode Case Insensitivity and Normalization: Mode identifiers are case-insensitive and are normalized according to the same rules as names in ELCL.

    Comparisons are performed on the normalized form of the identifier. For example, if_not, IF_NOT, and If Not all refer to the same mode.

    Validators must apply normalization consistently when resolving the mode value.

    # ...
    
    *[vr_dependency]*
    mode: "If Not"
    source: "hostname"
    target: "ip_address"
    
    *[vr_dependency]*
    mode: "if_not"
    source: "port"
    target: "protocol"
    

Mode Logic Explained

When validating a dependency, the validator performs two steps:

  1. Determine whether the source side is configured.

  2. Determine whether the target side is configured.

The selected mode is then applied to these two boolean results.

Importantly, dependencies operate purely on presence. They never inspect or compare actual values.

Testing if Sources and/or Targets are Present

A single name-path in source or target is considered configured if:

  • the referenced node exists in the original configuration document, and

  • its presence is not the result of a default value.

This distinction is essential.

A node that only exists in the parsed configuration tree because a default value was applied is not considered configured for dependency evaluation.

If multiple name-paths are listed in source or target, that side is considered configured if at least one of the listed nodes is configured (logical OR semantics).

Consider this simplified example:

*[app.vr_dependency]*
mode: "if"
source: "a", "b"
target: "x", "y"

The following configurations illustrate the four possible situations:

[app]  # Neither source nor target are configured.
[app]  # Source is configured, but target is not.
a: 1
[app]  # Target is configured, but source is not.
x: 1
[app]  # Both source and target are configured.
b: 1
y: 1

Four Situations

When evaluating a dependency, the configuration state of the source and target side results in exactly one of four possible situations.

These situations form the logical basis for all dependency modes.

Situation

Description

Neither source nor target values are configured.

One or more source values are configured, and no target values are configured.

No source values are configured, but one or more target values are configured.

One or more source values are configured, and one or more target values are configured.

The Dependency Mode Logic

Each dependency mode defines which of the four situations above are considered valid.

A green checkmark indicates that the dependency is satisfied. A red xmark indicates that the dependency is violated and validation fails.

The following table is normative and defines the exact behavior:

Mode ↓

if

if_not

or

xor

xnor

and

Unsupported Logic Modes and Why They Are Not Supported

You may notice that the logical matrix above is not exhaustive. Several theoretically possible boolean modes are intentionally not supported.

The following modes are omitted by design:

Mode ↓

none

nand

nor

reverse_if

These modes are excluded for the following reasons:

none

This mode would accept all four situations and therefore impose no constraint. It is functionally equivalent to omitting the vr_dependency entry entirely.

nand

While logically valid, it is semantically redundant: the same behavior is already provided by if_not with clearer intent in the context of dependencies.

nor

This mode would only allow the situation where neither side is configured. In practice, this would prohibit configuring any of the referenced nodes, which does not express a dependency but rather a prohibition rule.

reverse_if

This would represent the logical inverse direction of if. Instead of introducing a separate mode, the same behavior is achieved simply by swapping source and target in an if dependency. Adding a dedicated mode would therefore increase surface complexity without adding expressive power.

Examples

Exclusion (XOR)

Exactly one of the two values must be configured:

[server.hostname]
type: "text"
is_optional: yes

[server.ip_address]
type: "text"
is_optional: yes

*[server.vr_dependency]*
mode: "xor"
source: "hostname"
target: "ip_address"
error: "Configure either 'hostname' or 'ip_address'"

Bidirectional Dependency (XNOR)

Either both values must be present, or neither:

[window.x]
type: "integer"
is_optional: yes

[window.y]
type: "integer"
is_optional: yes

*[window.vr_dependency]*
mode: "xnor"
source: "x"
target: "y"
error: "You must either specify both 'x' and 'y' or neither"

Directional Dependency (IF)

A value is required only when a related feature is configured:

[api.media_link]
type: "SectionList"
is_optional: yes

[api.media_link.vr_entry.id]
type: "text"

*[vr_key]*
name: "media_link"
key: "api.media_link.vr_entry.id"

[app.primary_medialink]
type: "text"
key: "media_link"
is_optional: yes

*[vr_dependency]*
mode: "if"
source: "api.media_link"
target: "app.primary_medialink"
error: "You must configure 'primary_medialink' when using this feature"

Version History

Changed in version 1.3.1:

  • Added the missing or and and modes.

  • Clarified that mode identifiers are case-insensitive and must be compared using ELCL normalization rules.

  • Refined the wording of all dependency modes to remove ambiguity.

  • Added a normative section explaining dependency mode logic in detail.

  • Defined stricter requirements for source and target name-paths, including explicit restrictions on section lists and alternative branches.