Checks - ConfigMate/configmate GitHub Wiki
Checks are a cornerstone feature of ConfigMate. They are defined using ConfigMate Check Language (CMCL), a restricted programmatic language. The most important element in checks are functions which are tied to field types. To see what types are supported and what functions are available for each type, see Types and Functions.
- Type-Specific Functions: Each field type has its own set of validation functions.
-
Logical Operators: Include
&&
,||
, and!
. -
Control Structures: Provide conditional logic (
if-elseif-else
) and iteration (foreach
).
The basic form of a check is a simple expression. A simple expression can be a function expression or a field expression. A function expression starts by invoking a function. A field expression starts with a reference to another field in the configuration file. Any function we invoke in the beginning of a function expression will be invoked on the field being defined. The first funciton in a field expression is invoked on the referenced field. We can then chain more functions to the expression, and each function will be invoked on the result of the previous function.
To illustrate, consider the following example of a check defined for a field of type int:
eq(3)
This is a function expression. It invokes the equality function (eq
) on the int field, and passes the value 3
as an argument to the function. Here we are comparing the value of the field to the value 3
. Now, consider another field of type int exists and it's called F
. We can write a check for the first field that compares it to F
by writing:
eq(F)
Here, the argument to the equality function is a field expression. The field expression is simply a reference to the field F
. The equality function will check that the first field is equal to the value of F
. Now if F
was of type float, we could write:
eq(F.toInt())
We can write function expressions as field expressions by using the keyword this
. The keyword this
refers to the field being defined. So, the following two expressions are equivalent:
eq(3)
this.eq(3)
The use of the keyword this
is necessary when we want to reference the field being defined inside a field expression that referenced another field. For example, consider the following check:
F.eq(this)
A valid check must result in a boolean; a true result will be considered a passing check and a false one a failed one.
Logical operators can be used to combine multiple expressions. The operators &&
and ||
are supported, and they have the same semantics as in most programming languages. The operator !
is also supported, and it negates the result of the expressions it is applied to.
We can group expressions using parentheses. This allows us to control the order in which expressions are evaluated.
Control structures allow us to write more complex checks.
The if-elseif-else
structure allows us to write checks where the desired state or behavior of a field varies with respect to the value of other expressions.
For example, consider the following check:
if (F.eq(3)) {
eq(5)
}
The foreach
structure allows us to iterate over a list of fields and perform checks on each of them. The syntax of the foreach
structure is as follows:
foreach (var : list) {
// Check that uses var
}
Where var
is the name of the variable that will be used to reference each element in the list, and list
is the list of fields to iterate over. The list
must be a field name.
This is another case where the keyword this
is necessary. Consider we are defining a field of type list and we want to perform some check on each of its elements. We can write:
foreach (e : this) {
// Check that uses e
}
-
For a 'host' field:
reachable()
-
For an 'int' field:
range(25, 100)
-
For a 'list' field:
len().gte(3)
foreach(s : this) { s.reachable() }
-
For a 'string' field:
regex('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')
-
For a 'file' field:
exists()
isDir()
perms().eq("0644")