Gmconfig - A JSON-SCHEMA-based configuration management support library
Introduction
This library offers the basic support functions for the Gajumaru configuration management subsystem. It is based on JSON-Schema, and includes, among other things:
- A reasonably complete JSON-Schema validator
- Validating user configurations against the schema
- In-service update of the user config
- Caching of the user config (and schema) as persistent terms
- Fast config lookups using key paths
- Lookups can handle both schema defaults and user-provided defaults
- Optional type coercion during validation
- Optional conversion of enums to atoms
- Optional extensions with custom validator funs
JSON-Schema validator
The main thing the validator currently doesn't support is proper
management of complex sets of schemas and subSchemas. There are
some preparations made for supporting "$id" and uris, but this
is still incomplete. "definitions" and "$ref" properties are recognized.
As almost anything is theoretically possible with JSON-Schema, there are surely other things that are unsupported.
All standard data types
"null""boolean""integer""number""boolean""string""array""object"
Dynamic properties
"if""not""oneOf""anyOf""allOf"
Static Properties
Array
"maxItems""minItems""uniqueItems""prefixItems""contains"("minContains","maxContains")
Object
"properties"("minProperties","maxProperties")"patternProperties""additionalProperties""required"
String
"pattern""minLength""maxLength"
Number, Integer
"minimum""maximum""exclusiveMinimum""exclusiveMinimum""multipleOf"(only if object is an integer)
"$updateSemantics"
"replace"(fully replaces any old data)"merge"(for objects, keeps and/or updates existing values)"suggest"(adds value if not already present)
Custom validation options
Using gmconfig_schema_utils:validate(Json, Schema, Opts), a few options are supported
to further enhance the validation (Opts is of type map()):
coerce => boolean() converts strings to integers, null and booleans when needed.
enums_to_atoms => boolean() converts enum strings to atoms
extensions => map() supports mapping x-... properties to custom validators.
Validator extensions
See the following test case:
t_nested_refs() ->
S = read("data/nested_refs_schema.json"),
F = fun(Str, #{<<"tags">> := Tags}) ->
true = lists:any(
fun(T) ->
nomatch =/= string:prefix(Str, T)
end, Tags)
end,
Opts = #{extensions => #{<<"x-serialization">> => F}},
Vs = #{<<"tx">> => #{<<"from">> => <<"ak_good">>}},
Vf = #{<<"tx">> => #{<<"from">> => <<"ac_bad">>}},
validate(Vs, S, Opts),
fails(Vf, S, Opts, #{e => failing_schemas}),
ok.
This simulates an encoding extension, where the example fun here simply checks
if tags specified under the x-serialization property are prefixes of the
given string value.
In the test schema, we can see the following definition:
"properties": {
"from": {
"allOf": [
{ "$ref": "#/components/schemas/Pubkey" },
{ "x-serialization": {
"tags": ["ak"]
}}
]
}
}
...
"Pubkey": {
"type": "string",
"x-serialization": {
"tags": ["ak", "ct"]
}
Whenever the validator encounters an x-... property mapped to a validator fun,
this fun is called with the value and the schema part of the property. The return
value of the fun is ignored, and any normal return is treated as a validation success.
The example illustrates a common pattern in OpenAPI specs, where entity references are
used extensively. The Pubkey data type can have a more general x-serialization
definition, where multiple key types are accepted, whereas a specialized use of the
type can narrow the scope by accepting only a subset of the possible types.