A CONL schema provides validation and auto-completion for CONL documents.
It embeds the philosophy of CONL: post-minimal, easy to read, easy to edit, and easy to parse.
A CONL schema is itself a CONL document with two
top-level keys.
is a map of named
definitions, and
names the definition that
should be at the root of the document.
For example:
root =< server >definitions server required keys type =server keys listen =< addr >addr keys host =.* port =\d+
Matches the CONL document:
type =server listen host =localhost port =8080
The expressivity of a CONL schema roughly matches that of a regular expression. If you pair it with an re2 based regular expression engine, you can avoid the exponential matching time possible with JSON-schema.
There are four kinds of definition:
A scalar definition requires that the value is a scalar, and it is matched by the given matcher.
definitions example scalar =[[matcher]]
An any of definition requires that the value matches one (or more) of the given matchers.
definitions example any of =[[matcher]] =[[matcher]] =...
A list definition requires that the value is a list.
Specifying
Specifying
Specifying both lets you require a prefix that matches the required items, followed by any number of values that match the items matcher.
definitions example required items =[[matcher]] =[[matcher]] =... items =[[matcher]]
A map definition requires that the value is a map. As in CONL, each key in the map must be unique.
If
If
The value cannot contain any keys that are not present in either. If there are no required keys, then the value may be empty.
definitions example keys [[matcher]] =[[matcher]] [[matcher]] =[[matcher]] ... required keys [[matcher]] =[[matcher]] [[matcher]] =[[matcher]] ...
It is worth calling out that matching considers both the key and the values. This lets you (in combination with an any of definition) match multiple different types of value in the same position.
definitions example any of =< =server >< client >server required keys type =server keys ... client required keys type =client keys ...
A matcher describes how a value should be matched. There are two
possible kinds of matcher: references (
A matcher is typically represented as a CONL scalar, but it can also be paired with
markdown documentation, in which case it is expressed as a CONL map with the
; matches any scalar value definitions example scalar =.* ; equivalent to the above, but with docs definitions example scalar matches =.* docs ="""markdown Matches any scalar
If the matcher starts with an <
and ends with
>
it references an existing definition.
definitions example required keys mode =< mode >mode one of =server =client
This allows the example’s mode key to be set to either
While it is possible to use this to build up recursive structures (where a map can have values that are the same type). It is an error to set up a cyclical definition where one definition is defined in terms of itself, for example:
definitions ; invalid schema a scalar =< b >b one of =< a >
Patterns in CONL are regular expressions. To make it possible to match strings
easily, the pattern must match the entire value, and `.` matches
any unicode code-point (including \n
). For example:
definitions example any of =one ; "one" (not "done") =(?i)two ; two, Two, TWO, etc. =.* ; "", "a\nb", etc. =[^\r\n]+ ; "any one-line scalar"
Currently the exact details of regular expression matching are implementation defined (to make it easy to use different regex engines as a schema implementor). It is recommended to stick to the basics when it comes to regular expressions to ensure your schemas work with future validators that use different regex engines. The reference implementation uses Go's regexp package.
It is also worth noting that while CONL has three ways to represent scalars,
they are not distinguishable. A matcher that matches the string
=one ="one" ="""markdown one
CONL schema may evolve over the future to support features beyond just validating the struture of the document. For example it might be useful to know if a scalar should be represented in JSON as a string or a number; or whether certain values or keys are deprecated. As such, CONL schema parsers should not error if they encounter unknown keys in maps representing matchers.
We do not expect to make changes that would cause validators to be unable to validate existing schemas.