Did you know ... | Search Documentation: |
Packs (add-ons) for SWI-Prolog |
Title: | Dict converter and validator |
---|---|
Rating: | Not rated. Create the first rating! |
Latest version: | 0.0.2 |
SHA1 sum: | be9cca6cdc75876e2b2e25734e4ff7020e0adfba |
Author: | Raivo Laanemets http://rlaanemets.com/ |
Home page: | https://github.com/rla/dict-schema |
No reviews. Create the first review!.
Version | SHA1 | #Downloads | URL |
---|---|---|---|
0.0.1 | 859a21e6154a560936164171701cd677b2c8d477 | 3 | http://packs.rlaanemets.com/dict-schema/dict_schema-0.0.1.tgz |
0.0.2 | be9cca6cdc75876e2b2e25734e4ff7020e0adfba | 126 | http://packs.rlaanemets.com/dict-schema/dict_schema-0.0.2.tgz |
Dict validation/conversion for Swi-Prolog. The library started as a predicate to convert certain dict (from HTTP JSON requests) entries into suitable forms (especially the string/atom conversion). A large part of this library was inspired by JSON-Schema.
Check vehicle against a schema:
?- Schema = _{ type: dict, keys: _{ year: _{ type: integer, min: 1672 }, make: _{ type: atom, min_length: 1 }, model: _{ type: atom, min_length: 1 } } }, Vehicle = vehicle{ year: 1953, make: chevrolet, model: corvette }, convert(Vehicle, Schema, Out, Errors), Out = vehicle{ make: chevrolet, model: corvette, year: 1953 }, Errors = [].
You can name the schema by registering it:
?- register_schema(vehicle, _{ type: dict, keys: _{ year: _{ type: integer, min: 1672 }, make: _{ type: atom, min_length: 1 }, model: _{ type: atom, min_length: 1 } } }).
And then use it by name:
Vehicle = vehicle{ year: 1200, make: chevrolet, model: corvette }, convert(Vehicle, vehicle, Out, Errors). Out = vehicle{ make: chevrolet, model: corvette, year: 1200 }, Errors = [min(#/year, 1200, 1672)].
The last example also shows validation error for the year
key. Another
feature is automatic conversion from strings to atoms when the atom type
is requested:
?- convert("abc", atom, Out, Errors), atom(Out).
Path indicators are used for locating errors in terms. They have the following meaning:
#
- the term root (or root term).key
- (an atom), key of dict.name(N)
- N-th argument of compound with the name name
.[N]
- N-th element in the list./
- path separators.Example:
?- Schema = _{ type: dict, keys: _{ a: _{ type: list, items: _{ type: compound, name: b, arguments: [ number ] } } } }, In = d{ a: [ b(2), b(a), b(4) ] }, convert(In, Schema, Out, Errors). Out = d{a:[b(2), b(a), b(4)]}, Errors = [not_number(#/a/[1]/b(0), a)].
The error path `#/a/[1]/b(0)
` refers here to the key a
in the root dict, the 1-st item
of the list (starts from 0) and the 0-th argument of the term.
All types below assume that input is either a ground or a dict with ground values.
Exceptions are any
and var
. A dict with an unbound tag is allowed depending
on its type's tag
attribute.
String type has the following optional attributes:
Errors:
not_string(Path, Value)
.min_length
property is violated: min_length(Path, Value, MinLength)
.max_length
property is violated: max_length(Path, Value, MaxLength)
.
Works similar to the string
type. When the input is a string,
it is converted into an atom. Has same optional attributes.
When input is not a string or atom, an error
term not_atom(Path, Value)
is produced.
Number type has the following optional attributes:
Errors:
not_number(Path, Value)
.min
property is violated: min(Path, Value, Min)
.max
property is violated: max(Path, Value, Max)
.
Same as the type number
but allows integers only.
The bool type only allows atoms true
and false
. Produces
error not_bool(Path, Value)
when the input is not one of those.
The enum type has attribute values
that contains a list of allowed values.
The list must contain atoms. If the checked value is not in the list
then an error is produced. If the input value is a string then it is converted
into an atom first. All other values produce an error not_enum(Path, Value)
.
The dict type has the following attributes:
false
.Errors:
not_dict(Path, Value)
.additional
property is missing or its value is false
and
every key is not listed in keys
: additional_key(Path, Key)
.no_key(Path, Key)
.tag
property is specified and the input's tag does not
match it: invalid_tag(Path, Tag, RequiredTag)
.
When the tag
property is specified and the input has no tag then input's tag is
unified with the tag
property value.
The list type has the following attributes:
Errors:
not_list(Path, Value)
.min_length
property is violated: min_length(Path, Value, MinLength)
.max_length
property is violated: max_length(Path, Value, MaxLength)
.The compound type has the following attributes:
Errors:
invalid_compound(Path, In)
.compound_args_length(Path, ActualLen, RequiredLen)
.compound_name(Path, ActualName, Name)
.
Union of types can be expressed with using a list. The first schema and
the conversion result that matches is used. When no schema matches then
an error union_mismatch(Path, Reasons)
is produced.
Examples:
?- convert(123, [ number, atom ], Out, Errors). Out = 123, Errors = []. ?- convert(a(1), [ number, atom ], Out, Errors). Out = a(1), Errors = [union_mismatch(#, [ [not_atom(#, a(1))], [not_number(#, a(1))] ])].
Type any
marks the value non-checked and non-converted.
Type var
is for variables in the input. When the input is not
a variable then an error term not_variable(Path, Value)
is produced.
Named schema can be added with register_schema(Name, Schema)
and removed with
unregister_schema(Name)
. Schema names must be atoms.
The following example validates binary tree that has integers in leafs. The top-level schema is an union of branch and leaf schemas.
?- register_schema(tree, [ _{ type: compound, name: branch, arguments: [ tree, tree ] }, _{ type: integer, min: 0 } ]). ?- convert(branch(32, branch(13, 56)), tree, Out, Errors). Out = branch(32, branch(13, 56)), Errors = []. ?- convert(branch(32, a), tree, Out, Errors). Out = branch(32, a), Errors = [union_mismatch(#,[ [not_integer(#,branch(32,a))], [union_mismatch(# / branch(1),[ [not_integer(# / branch(1),a)], [invalid_compound(# / branch(1),a)] ])] ])].
This was the main motivation and use case for this library. The following example
registers a schema for JSON input documents that must contain a property from
. The value
of the property must be an atom with length minimally 3.
:- use_module(library(http/thread_httpd)). :- use_module(library(http/http_dispatch)). :- use_module(library(http/http_json)). :- use_module(library(dict_schema)). % Schema for hello input. :- register_schema(hello, _{ type: dict, keys: _{ from: _{ type: atom, min_length: 3 } } }). :- http_handler('/hello', receive_data, []). receive_data(Request):- http_read_json_dict(Request, Dict), % Convert/validate input: convert(Dict, hello, Hello, Errors), format('Content-type: text/plain; charset=UTF-8~n~n'), ( Errors = [] -> get_dict(from, Hello, From), format('~w said hello~n', [From]) ; format('Input errors: ~w~n', [Errors])). :- http_server(http_dispatch, [port(8080)]).
Example calls using curl:
curl -X POST -H 'Content-Type: application/json' \ -d '{"from": "RLa"}' http://localhost:8080/hello RLa said hello curl -X POST -H 'Content-Type: application/json' \ -d '{"to": "RLa"}' http://localhost:8080/hello Input errors: [additional_key(#,to),no_key(#,from)]
The schema is validated on-the-fly during the validation of the input term. It is checked for valid attributes. Schema errors are thrown as exceptions and are not placed into the Errors output list.
Generated API documentation can be found from here: http://packs.rlaanemets.com/dict-schema/doc.
This package requires Swi-Prolog 7.x.
pack_install(dict_schema).
In the package root, insert into swipl:
[tests/tests]. run_tests.
Or if you cloned the repo:
make test
Please send bug reports/feature request through the GitHub project page.
The MIT License. See the LICENSE file.
Pack contains 5 files holding a total of 34.6K bytes.