for-GET / jesse

jesse (JSon Schema Erlang) is an implementation of a JSON Schema validator for Erlang.
https://github.com/for-get/jesse
Apache License 2.0
126 stars 64 forks source link
erlang json-schema

jesse Build Status

jesse (JSON Schema Erlang) is an implementation of a JSON Schema validator for Erlang, though it can work just as well as a CLI tool.

jesse implements the following specifications:

Install from git or https://hex.pm/packages/jesse .

For convenience, available as a Docker image too: docker run --rm -it -v $PWD:$PWD -w $PWD ysoftwareab/jesse ....

And whalebrew package: brew install whalebrew && sudo whalebrew install ysoftwareab/jesse; jesse ...

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.

Command-Line Interface

You can either build the bin/jesse executable yourself (just type make), or you can use a Docker image a call it with docker run ysoftwareab/jesse.

You can fire up jesse from the CLI, with

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

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.

Erlang Interface

There are two ways of using jesse:

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.

(parse JSON in advance)

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)

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">>}]}],
                      []}]}

(parse JSON in advance)

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)

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">>]}]}
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

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

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

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

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 still got only two errors.

Let's try using 'infinity' as the argument for the allowed_errors option

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

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

jesse currently supports JSON Schema draft3, draft4 and draft6. 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#">>.

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

A schema_invalid error is a tuple on the form {schema_invalid, Schema, ErrorType} where

Caveats

Contributing

If you see something missing or incorrect, a pull request is most welcome!

License

Apache 2.0

Stargazers over time

Stargazers over time