mirror of
https://github.com/valitydev/jesse.git
synced 2024-11-06 09:35:23 +00:00
337 lines
13 KiB
Markdown
337 lines
13 KiB
Markdown
**This repository is the actively-maintained follow-up of https://github.com/klarna/jesse. Please update your references.**
|
|
|
|
# jesse [![Build Status][2]][1]
|
|
|
|
jesse (JSON Schema Erlang) is an implementation of a JSON Schema validator
|
|
for Erlang.
|
|
|
|
jesse implements the following specifications:
|
|
|
|
* [Draft 03](http://tools.ietf.org/html/draft-zyp-json-schema-03)
|
|
* [Draft 04](http://tools.ietf.org/html/draft-zyp-json-schema-04)
|
|
|
|
## Erlang API Docs
|
|
|
|
Automatically generated docs are available https://dev.erldocs.com/github.com/for-get/jesse/ .
|
|
|
|
Please keep in mind that the public API is the `jesse.erl` module alone.
|
|
|
|
## Quick start - CLI
|
|
|
|
You can fire up `jesse` from the CLI, with
|
|
```bash
|
|
bin/jesse [path_to_json_schema] path_to_json_schema -- path_to_json_instance [path_to_json_instance]
|
|
```
|
|
|
|
You can also output the result in JSON format, with `--json`, and beautify it e.g. with python
|
|
```bash
|
|
bin/jesse [path_to_json_schema] path_to_json_schema --json -- path_to_json_instance [path_to_json_instance] | python -m json.tool
|
|
```
|
|
|
|
You can pass multiple JSON schemas which should be loaded into jesse in-memory storage, but JSON instances will be validated against the last JSON schema passed.
|
|
|
|
## Quick start - Erlang
|
|
|
|
There are two ways of using jesse:
|
|
|
|
* to use jesse internal in-memory storage to keep all your schema definitions
|
|
In this case jesse will look up a schema definition in its own storage,
|
|
and then validate given a JSON instance.
|
|
* it is also possible to provide jesse with schema definitions when jesse is called.
|
|
|
|
## Examples
|
|
|
|
NOTE: jesse doesn't have any parsing functionality. It currently works with four
|
|
formats: mochijson2, jiffy, jsx and Erlang 17+ maps, so JSON needs to be
|
|
parsed in advance, or you can specify a callback which jesse will use to
|
|
parse JSON.
|
|
|
|
In examples below and in jesse test suite jiffy parser is used.
|
|
|
|
* Use jesse's internal in-memory storage:
|
|
|
|
(parse JSON in advance)
|
|
|
|
```erlang
|
|
1> Schema = jiffy:decode(<<"{\"items\": {\"type\": \"integer\"}}">>).
|
|
{[{<<"items">>,{[{<<"type">>,<<"integer">>}]}}]}
|
|
2> jesse:add_schema("some_key", Schema).
|
|
ok
|
|
3> Json1 = jiffy:decode(<<"[1, 2, 3]">>).
|
|
[1,2,3]
|
|
4> jesse:validate("some_key", Json1).
|
|
{ok,[1,2,3]}
|
|
5> Json2 = jiffy:decode(<<"[1, \"x\"]">>).
|
|
[1,<<"x">>]
|
|
6> jesse:validate("some_key", Json2).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
|
|
wrong_type,<<"x">>,
|
|
[1]}]}
|
|
```
|
|
|
|
The `[1]` in the error is the path in the original value to `<<"x">>` where the
|
|
validation failed. See *Validation errors* below for the full error format.
|
|
|
|
(using a callback)
|
|
|
|
```erlang
|
|
1> jesse:add_schema("some_key",
|
|
1> <<"{\"uniqueItems\": true}">>,
|
|
1> [{parser_fun, fun jiffy:decode/1}]).
|
|
ok
|
|
2> jesse:validate("some_key",
|
|
2> <<"[1, 2]">>,
|
|
2> [{parser_fun, fun jiffy:decode/1}]).
|
|
{ok,[1, 2]}
|
|
3> jesse:validate("some_key",
|
|
3> <<"[{\"foo\": \"bar\"}, {\"foo\": \"bar\"}] ">>,
|
|
3> [{parser_fun, fun jiffy:decode/1}]).
|
|
{error,[{data_invalid,{[{<<"uniqueItems">>,true}]},
|
|
{not_unique,{[{<<"foo">>,<<"bar">>}]}},
|
|
[{[{<<"foo">>,<<"bar">>}]},{[{<<"foo">>,<<"bar">>}]}],
|
|
[]}]}
|
|
```
|
|
|
|
* Call jesse with schema definition in place (do not use internal storage)
|
|
|
|
(parse JSON in advance)
|
|
|
|
```erlang
|
|
1> Schema = jiffy:decode(<<"{\"pattern\": \"^a*$\"}">>).
|
|
{[{<<"pattern">>,<<"^a*$">>}]}
|
|
2> Json1 = jiffy:decode(<<"\"aaa\"">>).
|
|
<<"aaa">>
|
|
3> jesse:validate_with_schema(Schema, Json1).
|
|
{ok,<<"aaa">>}
|
|
4> Json2 = jiffy:decode(<<"\"abc\"">>).
|
|
<<"abc">>
|
|
5> jesse:validate_with_schema(Schema, Json2).
|
|
{error,[{data_invalid,{[{<<"pattern">>,<<"^a*$">>}]},
|
|
no_match,
|
|
<<"abc">>,[]}]}
|
|
```
|
|
|
|
(using a callback)
|
|
|
|
```erlang
|
|
1> Schema = <<"{\"patternProperties\": {\"f.*o\": {\"type\": \"integer\"}}}">>.
|
|
<<"{\"patternProperties\": {\"f.*o\": {\"type\": \"integer\"}}}">>
|
|
2> jesse:validate_with_schema(Schema,
|
|
2> <<"{\"foo\": 1, \"foooooo\" : 2}">>,
|
|
2> [{parser_fun, fun jiffy:decode/1}]).
|
|
{ok,{[{<<"foo">>,1},{<<"foooooo">>,2}]}}
|
|
3> jesse:validate_with_schema(Schema,
|
|
3> <<"{\"foo\": \"bar\", \"fooooo\": 2}">>,
|
|
3> [{parser_fun, fun jiffy:decode/1}]).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
|
|
wrong_type,<<"bar">>,
|
|
[<<"foo">>]}]}
|
|
```
|
|
|
|
* Validate an instanse against a particular definition from schema definitions
|
|
|
|
```erlang
|
|
1> Schema = <<"{\"definitions\": {\"Foo\": {\"properties\": {\"foo\": {\"type\": \"integer\"}}}, \"Bar\": {\"properties\": {\"bar\": {\"type\": \"boolean\"}}}}}">>.
|
|
<<"{\"definitions\": {\"Foo\": {\"properties\": {\"foo\": {\"type\": \"integer\"}}}, \"Bar\": {\"properties\": {\"bar\": {\"type\": \"boolea"...>>
|
|
2> jesse:validate_definition("Foo",
|
|
2> Schema,
|
|
2> <<"{\"foo\": 1}">>,
|
|
2> [{parser_fun, fun jiffy:decode/1}]).
|
|
{ok,[{<<"foo">>,1}]}
|
|
3> jesse:validate_definition("Bar",
|
|
3> Schema,
|
|
3> <<"{\"bar\": 2}">>,
|
|
3> [{parser_fun, fun jiffy:decode/1}]).
|
|
{error,[{data_invalid,[{<<"type">>,<<"boolean">>}],
|
|
wrong_type,2,
|
|
[<<"bar">>]}]}
|
|
4> jesse:validate_definition("FooBar",
|
|
4> Schema,
|
|
4> <<"{\"bar\": 2}">>,
|
|
4> [{parser_fun, fun jiffy:decode/1}]).
|
|
{error,[{schema_invalid,[{<<"definitions">>,
|
|
[{<<"Foo">>,
|
|
[{<<"properties">>,
|
|
[{<<"foo">>,[{<<"type">>,<<"integer">>}]}]}]},
|
|
{<<"Bar">>,
|
|
[{<<"properties">>,
|
|
[{<<"bar">>,[{<<"type">>,<<"boolean">>}]}]}]}]}],
|
|
{schema_not_found,"#/definitions/FooBar"}}]}
|
|
```
|
|
|
|
|
|
* Since 0.4.0 it's possible to instruct jesse to collect errors, and not stop
|
|
immediately when it finds an error in the given JSON instance:
|
|
|
|
```erlang
|
|
1> Schema = <<"{\"properties\": {\"a\": {\"type\": \"integer\"}, \"b\": {\"type\": \"string\"}, \"c\": {\"type\": \"boolean\"}}}">>.
|
|
<<"{\"properties\": {\"a\": {\"type\": \"integer\"}, \"b\": {\"type\": \"string\"}, \"c\": {\"type\": \"boolean\"}}}">>
|
|
2> jesse:validate_with_schema(Schema,
|
|
2> <<"{\"a\": 1, \"b\": \"b\", \"c\": true}">>,
|
|
2> [{parser_fun, fun jiffy:decode/1}]).
|
|
{ok,{[{<<"a">>,1},{<<"b">>,<<"b">>},{<<"c">>,true}]}}
|
|
```
|
|
|
|
now let's change the value of the field "b" to an integer
|
|
|
|
```erlang
|
|
3> jesse:validate_with_schema(Schema,
|
|
3> <<"{\"a\": 1, \"b\": 2, \"c\": true}">>,
|
|
3> [{parser_fun, fun jiffy:decode/1}]).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
|
|
wrong_type,2,
|
|
[<<"b">>]}]}
|
|
```
|
|
|
|
works as expected, but let's change the value of the field "c" as well
|
|
|
|
```erlang
|
|
4> jesse:validate_with_schema(Schema,
|
|
4> <<"{\"a\": 1, \"b\": 2, \"c\": 3}">>,
|
|
4> [{parser_fun, fun jiffy:decode/1}]).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
|
|
wrong_type,2,
|
|
[<<"b">>]}]}
|
|
```
|
|
|
|
still works as expected, jesse stops validating as soon as finds an error.
|
|
|
|
Let's use the `allowed_errors` option, and set it to 1
|
|
|
|
```erlang
|
|
5> jesse:validate_with_schema(Schema,
|
|
5> <<"{\"a\": 1, \"b\": 2, \"c\": 3}">>,
|
|
5> [{parser_fun, fun jiffy:decode/1},
|
|
5> {allowed_errors, 1}]).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"boolean">>}]},
|
|
wrong_type,3,
|
|
[<<"c">>]},
|
|
{data_invalid,{[{<<"type">>,<<"string">>}]},
|
|
wrong_type,2,
|
|
[<<"b">>]}]}
|
|
```
|
|
|
|
now we got a list of two errors.
|
|
|
|
Let's now change the value of the field "a" to a boolean
|
|
|
|
```erlang
|
|
6> jesse:validate_with_schema(Schema,
|
|
6> <<"{\"a\": true, \"b\": 2, \"c\": 3}">>,
|
|
6> [{parser_fun, fun jiffy:decode/1},
|
|
6> {allowed_errors, 1}]).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
|
|
wrong_type,2,
|
|
[<<"b">>]},
|
|
{data_invalid,{[{<<"type">>,<<"integer">>}]},
|
|
wrong_type,true,
|
|
[<<"a">>]}]}
|
|
```
|
|
|
|
we stil got only two errors.
|
|
|
|
Let's try using 'infinity' as the argument for the `allowed_errors` option
|
|
|
|
```erlang
|
|
7> jesse:validate_with_schema(Schema,
|
|
7> <<"{\"a\": true, \"b\": 2, \"c\": 3}">>,
|
|
7> [{parser_fun, fun jiffy:decode/1},
|
|
7> {allowed_errors, infinity}]).
|
|
{error,[{data_invalid,{[{<<"type">>,<<"boolean">>}]},
|
|
wrong_type,3,
|
|
[<<"c">>]},
|
|
{data_invalid,{[{<<"type">>,<<"string">>}]},
|
|
wrong_type,2,
|
|
[<<"b">>]},
|
|
{data_invalid,{[{<<"type">>,<<"integer">>}]},
|
|
wrong_type,true,
|
|
[<<"a">>]}]}
|
|
```
|
|
|
|
Maps example
|
|
|
|
```erlang
|
|
8> jesse:validate_with_schema(Schema,
|
|
8> <<"{\"a\": 1, \"b\": 2, \"c\": true}">>,
|
|
8> [{parser_fun, fun(Bin) -> jiffy:decode(Bin, [return_maps]) end}]).
|
|
{error,[{data_invalid,#{<<"type">> => <<"string">>},
|
|
wrong_type,2,
|
|
[<<"b">>]}]}
|
|
9> jesse:validate_with_schema(Schema,
|
|
9> <<"{\"a\": 1, \"b\": \"val\", \"c\": true}">>,
|
|
9> [{parser_fun, fun(Bin) -> jiffy:decode(Bin, [return_maps]) end}]).
|
|
{ok, #{<<"a">> => 1, <<"b">> => <<"val">>, <<"c">> => true}}
|
|
```
|
|
|
|
## JSON Schema versions
|
|
|
|
Currently there are two popular drafts of JSON Schema: draft3 and draft4. jesse
|
|
supports both. To decide which validator to use jesse tries to read $schema
|
|
property from the given schema, and checks if it's a supported one, otherwise it
|
|
will return an error. If $schema property isn't provided in the given schema,
|
|
jesse will use the default validator (currently the validator for draft3).
|
|
|
|
To specify which validator to use by default (if there's no $schema property in
|
|
the given schema), one should use 'default_schema_ver' option when call
|
|
`jesse:validate/3` or `jesse:validate_with_schema/3`, the value should be
|
|
a binary consisting a schema path,
|
|
i.e. <<"http://json-schema.org/draft-03/schema#">>.
|
|
|
|
It is also possible to specify a validator module to use via `validator` option.
|
|
This option supersedes the mechanism with the $schema property described above.
|
|
Custom validator module can be specified as well. Such module should implement
|
|
`jesse_schema_validator` behaviour.
|
|
|
|
## Validation errors
|
|
|
|
The validation functions `jesse:validate/2` and `jesse:validate_with_schema/2,3`
|
|
return `{ok, Value}` on success and `{error, ListOfErrors}` on failure. An error
|
|
is either `data_invalid` or `schema_invalid`.
|
|
|
|
A `data_invalid` error is a tuple on the form `{data_invalid, Schema, ErrorType,
|
|
Value, Path}` where
|
|
|
|
* Schema is the part of the schema where validation failed
|
|
* ErrorType is the type of error, usually an atom such as `wrong_type`,
|
|
`not_in_range` or `no_match`
|
|
* Value is The part of the value where failed validation agains Schema
|
|
* Path is a path to where validation failed within the original value. The path
|
|
is a list of property names and zero-based array indices referencing the
|
|
properties and array items within a JSON document; e.g. in the JSON document
|
|
`{"foo": [42, 43, 44]}`, the path `[<<"foo">>, 0]` refers to the value 42. An
|
|
empty list refers to the whole JSON document.
|
|
|
|
A `schema_invalid` error is a tuple on the form `{schema_invalid, Schema,
|
|
ErrorType}` where
|
|
* Schema is the part of the schema which is invalid
|
|
* ErrorType is an atom such as `missing_id_field` or a tuple such as
|
|
`{wrong_type_dependency, Dependency}`.
|
|
|
|
## Caveats
|
|
|
|
* pattern and patternProperty attributes:
|
|
|
|
jesse uses standard erlang module `re` for regexp matching, therefore there could be
|
|
some incompatible regular expressions in schemas you define.
|
|
|
|
From erlang docs: "re's matching algorithms are currently based on the PCRE library,
|
|
but not all of the PCRE library is interfaced"
|
|
|
|
But most of common cases should work fine.
|
|
|
|
* internal references (id attribute) are NOT supported
|
|
|
|
http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.1
|
|
|
|
## Contributing
|
|
|
|
If you see something missing or incorrect, a pull request is most welcome!
|
|
|
|
## License
|
|
|
|
[Apache 2.0](LICENSE)
|
|
|
|
|
|
[1]: https://github.com/for-GET/jesse/actions?query=workflow%3ACI+branch%3Amaster
|
|
[2]: https://github.com/for-GET/jesse/workflows/CI/badge.svg?branch=master
|