Skip to content
Closed
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Emil Falk <emph@emph.se>
Daniel Lindberg <dajoh10@gmail.com>
Andrei Neculau <andrei.neculau@gmail.com>
Stefan Strigler <stefan@strigler.de>
Emilio Del Tessandoro <emilio.deltessa@gmail.com>
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ jesse implements [Draft 03] (http://tools.ietf.org/html/draft-zyp-json-schema-03
the specification. It supports almost all core schema definitions except:

* format
* $ref

## Quick start - CLI

Expand Down
3 changes: 1 addition & 2 deletions src/jesse_schema_validator.erl
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ validate_with_state(JsonSchema, Value, State) ->
select_and_run_validator(SchemaVer, JsonSchema, Value, State).

%%% Internal functions
%% @doc Returns "$schema" property from `JsonSchema' if it is present,
%% otherwise the default schema version from `State' is returned.

%% @private
get_schema_ver(JsonSchema, State) ->
case jesse_json_path:value(?_SCHEMA, JsonSchema, ?not_found) of
Expand Down
4 changes: 3 additions & 1 deletion src/jesse_schema_validator.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
-define(DISALLOW, <<"disallow">>).
-define(EXTENDS, <<"extends">>).
-define(ID, <<"id">>).
-define(_REF, <<"$ref">>). % NOT IMPLEMENTED YET
-define(REF, <<"$ref">>).

%% Constant definitions for Json types
-define(ANY, <<"any">>).
Expand All @@ -58,6 +58,7 @@
-define(NUMBER, <<"number">>).
-define(OBJECT, <<"object">>).
-define(STRING, <<"string">>).
-define(SEPARATOR, <<"/">>).

%% Supported $schema attributes
-define(default_schema_ver, <<"http://json-schema.org/draft-03/schema#">>).
Expand All @@ -70,6 +71,7 @@
-define(missing_id_field, 'missing_id_field').
-define(missing_required_property, 'missing_required_property').
-define(missing_dependency, 'missing_dependency').
-define(missing_ref_path, 'missing_ref_path').
-define(no_match, 'no_match').
-define(no_extra_properties_allowed, 'no_extra_properties_allowed').
-define(no_extra_items_allowed, 'no_extra_items_allowed').
Expand Down
38 changes: 35 additions & 3 deletions src/jesse_state.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
, get_allowed_errors/1
, get_current_path/1
, get_current_schema/1
, get_original_schema/1
, get_default_schema_ver/1
, get_error_handler/1
, get_error_list/1
Expand All @@ -36,11 +37,17 @@
, set_allowed_errors/2
, set_current_schema/2
, set_error_list/2
, find_schema/2
]).

-export_type([ state/0
]).

%% Includes
-include("jesse_schema_validator.hrl").

-define(SCHEMA_LOADER_FUN, fun jesse_database:read/1).

%% Internal datastructures
-record( state
, { original_schema :: jesse:json_term()
Expand All @@ -50,14 +57,14 @@
, error_list :: list()
, error_handler :: fun((#state{}) -> list() | no_return())
, default_schema_ver :: atom()
, schema_loader_fun :: fun((binary()) -> {ok, jesse:json_term()} |
jesse:json_term() |
?not_found)
}
).

-opaque state() :: #state{}.

%% Includes
-include("jesse_schema_validator.hrl").

%%% API
%% @doc Adds `Property' to the `current_path' in `State'.
-spec add_to_path(State :: state(), Property :: binary()) -> state().
Expand All @@ -80,6 +87,11 @@ get_current_path(#state{current_path = CurrentPath}) ->
get_current_schema(#state{current_schema = CurrentSchema}) ->
CurrentSchema.

%% @doc Getter for `original_schema'.
-spec get_original_schema(State :: state()) -> jesse:json_term().
get_original_schema(#state{original_schema = OriginalSchema}) ->
OriginalSchema.

%% @doc Getter for `default_schema_ver'.
-spec get_default_schema_ver(State :: state()) -> binary().
get_default_schema_ver(#state{default_schema_ver = SchemaVer}) ->
Expand Down Expand Up @@ -114,13 +126,18 @@ new(JsonSchema, Options) ->
, Options
, ?default_schema_ver
),
LoaderFun = proplists:get_value( schema_loader_fun
, Options
, ?SCHEMA_LOADER_FUN
),
#state{ current_schema = JsonSchema
, current_path = []
, original_schema = JsonSchema
, allowed_errors = AllowedErrors
, error_list = []
, error_handler = ErrorHandler
, default_schema_ver = DefaultSchemaVer
, schema_loader_fun = LoaderFun
}.

%% @doc Removes the last element from `current_path' in `State'.
Expand All @@ -147,6 +164,21 @@ set_current_schema(State, NewSchema) ->
set_error_list(State, ErrorList) ->
State#state{error_list = ErrorList}.

%% @doc Find a schema based on URI
-spec find_schema(State :: state(), SchemaURI :: binary()) ->
jesse:json_term() | ?not_found.
find_schema(#state{schema_loader_fun=LoaderFun}, SchemaURI) ->
try LoaderFun(SchemaURI) of
{ok, Schema} -> Schema;
Schema ->
case jesse_lib:is_json_object(Schema) of
true -> Schema;
false -> ?not_found
end
catch
_:_ -> ?not_found
end.

%%% Local Variables:
%%% erlang-indent-level: 2
%%% End:
62 changes: 62 additions & 0 deletions src/jesse_validator_draft3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ check_value(Value, [{?DISALLOW, Disallow} | Attrs], State) ->
check_value(Value, [{?EXTENDS, Extends} | Attrs], State) ->
NewState = check_extends(Value, Extends, State),
check_value(Value, Attrs, NewState);
check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) ->
NewState = check_ref(Value, RefSchemaURI, State),
check_value(Value, Attrs, NewState);
check_value(_Value, [], State) ->
State;
check_value(Value, [_Attr | Attrs], State) ->
Expand All @@ -202,6 +205,65 @@ check_value(Property, Value, Attrs, State) ->
%% Reset path again
jesse_state:remove_last_from_path(State2).


%% @private
check_ref(Value, <<"#", LocalPath/binary>> = RefSchemaURI, State) ->
Keys = binary:split(LocalPath, <<"/">>, ['global']),
DecodedKeys = lists:map(fun(T) -> decode_path_element(T) end, Keys),
OriginalSchema = jesse_state:get_original_schema(State),

case local_schema(OriginalSchema, DecodedKeys) of
?not_found -> handle_schema_invalid({no_such_ref, RefSchemaURI}, State);
LocalSchema -> check_ref_schema(Value, LocalSchema, State)
end;
check_ref(Value, RefSchemaURI, State) ->
case jesse_state:find_schema(State, RefSchemaURI) of
?not_found -> handle_schema_invalid({no_such_schema, RefSchemaURI}, State);
RefSchema -> check_ref_schema(Value, RefSchema, State)
end.

%% @private
check_ref_schema(Value, RefSchema, State) ->
TmpState = check_value(Value
, unwrap(RefSchema)
, set_current_schema(State, RefSchema)),
set_current_schema(TmpState, get_current_schema(State)).

local_schema(Schema, []) -> Schema;
local_schema(Schema, [<<>> | Keys]) -> local_schema(Schema, Keys);
local_schema(Schema, [Key | Keys]) ->
SubSchema = get_value(Key, Schema),
case {Key, jesse_lib:is_json_object(SubSchema)} of
{?ITEMS, _} -> local_schema_array(SubSchema, Keys);
{_, true} -> local_schema(SubSchema, Keys);
{_, false} -> ?not_found
end.

local_schema_array(Schema, [Key | Keys]) ->
try
Index = binary_to_integer(Key),
SubSchema = lists:nth(Index + 1, Schema),
local_schema(SubSchema, Keys)
catch
% maybe Item is not an integer
error:badarg -> ?not_found;
% maybe it happens to be an integer and there's not such index in the list
error:function_clause -> ?not_found
end;
local_schema_array(Schema, []) ->
Schema.

%% @doc Decodes a $ref URI token. Replacen the ~0 and ~1 according to RFC 6901,
%% run URI decode, then go back to binary.
%% @private
decode_path_element(Token) ->
String = binary:bin_to_list(Token),
String1 = http_uri:decode(String),
String2 = re:replace(String1, "~0", "\~", [global,{return,list}]),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Elvis:

Missing space after "," on line 262

String3 = re:replace(String2, "~1", "/", [global,{return,list}]),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Elvis:

Missing space after "," on line 263

binary:list_to_bin(String3).


%% @doc 5.1. type
%%
%% This attribute defines what the primitive type or the schema of the
Expand Down
13 changes: 6 additions & 7 deletions test/jesse_tests_draft3_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
, pattern/1
, patternProperties/1
, properties/1
%% , ref/1
, ref/1
, required/1
, type/1
, uniqueItems/1
Expand Down Expand Up @@ -79,7 +79,7 @@ all() ->
, pattern
, patternProperties
, properties
%% , ref
, ref
, required
, type
, uniqueItems
Expand Down Expand Up @@ -180,11 +180,10 @@ properties(Config) ->
Specs = ?config(Key, Config),
ok = run_tests(Specs).

%% not implemented yet
%% ref(Config) ->
%% Key = "ref",
%% Specs = ?config(Key, Config),
%% ok = run_tests(Specs).
ref(Config) ->
Key = "ref",
Specs = ?config(Key, Config),
ok = run_tests(Specs).

required(Config) ->
Key = "required",
Expand Down