From fc761d9a1f91855adc37b6b4c34321e1c76c90b0 Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:20:58 +0000 Subject: [PATCH 01/15] restore default_schema_ver in undo_resolve_ref the fixes the mix between draft3/draft4 schemas --- src/jesse_state.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 1155dba..37bd2e7 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -217,6 +217,7 @@ resolve_ref(State, Reference) -> undo_resolve_ref(RefState, OriginalState) -> RefState#state{ root_schema = OriginalState#state.root_schema , current_schema = OriginalState#state.current_schema + , default_schema_ver = OriginalState#state.default_schema_ver , id = OriginalState#state.id }. From 0f50fc399c9a3b1e0a563a13dcdfef3ed7f86768 Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:26:07 +0000 Subject: [PATCH 02/15] create validate_ref wraps the logic of validating referenced schemas --- src/jesse_validator_draft3.erl | 14 +++++++++----- src/jesse_validator_draft4.erl | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 6d8308b..258ce41 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -208,13 +208,11 @@ 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}], State) -> - {NewState0, Schema} = resolve_ref(RefSchemaURI, State), - NewState = - jesse_schema_validator:validate_with_state(Schema, Value, NewState0), - undo_resolve_ref(NewState, State); check_value(_Value, [], State) -> State; +check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> + NewState = validate_ref(Value, RefSchemaURI, State), + check_value(Value, Attrs, NewState); check_value(Value, [_Attr | Attrs], State) -> check_value(Value, Attrs, State). @@ -898,6 +896,12 @@ check_extends_array(Value, Extends, State) -> , Extends ). +%% @private +validate_ref(Value, Reference, State) -> + {NewState, Schema} = resolve_ref(Reference, State), + ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), + undo_resolve_ref(ResultState, State). + %% @private resolve_ref(Reference, State) -> NewState = jesse_state:resolve_ref(State, Reference), diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 3b307a0..1c0a462 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -245,13 +245,11 @@ check_value(Value, [{?ONEOF, Schemas} | Attrs], State) -> check_value(Value, [{?NOT, Schema} | Attrs], State) -> NewState = check_not(Value, Schema, State), check_value(Value, Attrs, NewState); -check_value(Value, [{?REF, RefSchemaURI}], State) -> - {NewState0, Schema} = resolve_ref(RefSchemaURI, State), - NewState = - jesse_schema_validator:validate_with_state(Schema, Value, NewState0), - undo_resolve_ref(NewState, State); check_value(_Value, [], State) -> State; +check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> + NewState = validate_ref(Value, RefSchemaURI, State), + check_value(Value, Attrs, NewState); check_value(Value, [_Attr | Attrs], State) -> check_value(Value, Attrs, State). @@ -1220,6 +1218,12 @@ validate_schema(Value, Schema, State0) -> throw:Errors -> {false, Errors} end. +%% @private +validate_ref(Value, Reference, State) -> + {NewState, Schema} = resolve_ref(Reference, State), + ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), + undo_resolve_ref(ResultState, State). + %% @doc Resolve a JSON reference %% The "id" keyword is taken care of behind the scenes in jesse_state. %% @private From 4997f846dafb4739a8f05c57e9d8341e00b74749 Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:28:39 +0000 Subject: [PATCH 03/15] allow to get current schema id --- src/jesse_state.erl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 37bd2e7..32e002e 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -28,6 +28,7 @@ , get_allowed_errors/1 , get_current_path/1 , get_current_schema/1 + , get_current_schema_id/1 , get_default_schema_ver/1 , get_error_handler/1 , get_error_list/1 @@ -96,6 +97,14 @@ get_current_path(#state{current_path = CurrentPath}) -> get_current_schema(#state{current_schema = CurrentSchema}) -> CurrentSchema. +%% @doc Getter for `current_schema_id'. +-spec get_current_schema_id(State :: state()) -> binary() | undefined. +get_current_schema_id(#state{current_schema = CurrentSchema + ,root_schema = RootSchema + }) -> + Default = jesse_json_path:value(?ID, RootSchema, ?not_found), + jesse_json_path:value(?ID, CurrentSchema, Default). + %% @doc Getter for `default_schema_ver'. -spec get_default_schema_ver(State :: state()) -> binary(). get_default_schema_ver(#state{default_schema_ver = SchemaVer}) -> From 91f2525ab4e3089304870668f658ffcc140ce66f Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:30:11 +0000 Subject: [PATCH 04/15] provide schema id to error --- src/jesse_state.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 32e002e..388827c 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -192,7 +192,7 @@ resolve_ref(State, Reference) -> Path = jesse_json_path:parse(Pointer), case load_local_schema(State#state.root_schema, Path) of ?not_found -> - jesse_error:handle_schema_invalid(?schema_invalid, State); + jesse_error:handle_schema_invalid(Reference, State); Schema -> set_current_schema(State, Schema) end; @@ -203,7 +203,7 @@ resolve_ref(State, Reference) -> ), case load_schema(State, BaseURI) of ?not_found -> - jesse_error:handle_schema_invalid(?schema_invalid, State); + jesse_error:handle_schema_invalid(Reference, State); RemoteSchema -> SchemaVer = jesse_json_path:value(?SCHEMA, RemoteSchema, ?default_schema_ver), @@ -214,7 +214,7 @@ resolve_ref(State, Reference) -> Path = jesse_json_path:parse(Pointer), case load_local_schema(RemoteSchema, Path) of ?not_found -> - jesse_error:handle_schema_invalid(?schema_invalid, State); + jesse_error:handle_schema_invalid(Reference, State); Schema -> set_current_schema(NewState, Schema) end From ea502a6235746bc3c6c964e463d1b47f8625827a Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:40:38 +0000 Subject: [PATCH 05/15] allow extra validator this will extra validations for schema elements example : lookup some value in a database an provide an error --- src/jesse_schema_validator.hrl | 1 + src/jesse_state.erl | 9 +++++++++ src/jesse_validator_draft3.erl | 14 +++++++++++--- src/jesse_validator_draft4.erl | 15 +++++++++++---- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/jesse_schema_validator.hrl b/src/jesse_schema_validator.hrl index 80dcfec..690e061 100644 --- a/src/jesse_schema_validator.hrl +++ b/src/jesse_schema_validator.hrl @@ -112,6 +112,7 @@ -define(not_one_schema_valid, 'not_one_schema_valid'). -define(not_schema_valid, 'not_schema_valid'). -define(wrong_not_schema, 'wrong_not_schema'). +-define(external_error, 'external_error'). %% -define(not_found, not_found). diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 388827c..c22aa7c 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -26,6 +26,7 @@ %% API -export([ add_to_path/2 , get_allowed_errors/1 + , get_extra_validator/1 , get_current_path/1 , get_current_schema/1 , get_current_schema_id/1 @@ -49,6 +50,7 @@ %% Includes -include("jesse_schema_validator.hrl"). +-type extra_validator() :: fun((jesse:json_term(), state()) -> state()) | undefined. %% Internal datastructures -record( state , { root_schema :: jesse:json_term() @@ -68,6 +70,7 @@ jesse:json_term() | ?not_found ) + , extra_validator :: extra_validator() , id :: binary() | 'undefined' } ). @@ -148,6 +151,9 @@ new(JsonSchema, Options) -> , Options , ?default_schema_loader_fun ), + ExtraValidator = proplists:get_value( extra_validator + , Options + ), NewState = #state{ root_schema = JsonSchema , current_path = [] , allowed_errors = AllowedErrors @@ -155,6 +161,7 @@ new(JsonSchema, Options) -> , error_handler = ErrorHandler , default_schema_ver = DefaultSchemaVer , schema_loader_fun = LoaderFun + , extra_validator = ExtraValidator }, set_current_schema(NewState, JsonSchema). @@ -377,3 +384,5 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) -> %% io:format("load_schema: ~p\n", [{_C, _E, erlang:get_stacktrace()}]), ?not_found end. + +get_extra_validator(#state{extra_validator=Fun}) -> Fun. diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 258ce41..7db5e79 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -47,7 +47,8 @@ | ?not_in_range | ?wrong_length | ?wrong_size - | ?wrong_type. + | ?wrong_type + | ?external_error. -type data_error_type() :: data_error() | {data_error(), binary()}. @@ -208,11 +209,11 @@ 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, [], State) -> - State; check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> NewState = validate_ref(Value, RefSchemaURI, State), check_value(Value, Attrs, NewState); +check_value(Value, [], State) -> + check_external_validation(Value, State); check_value(Value, [_Attr | Attrs], State) -> check_value(Value, Attrs, State). @@ -1018,3 +1019,10 @@ add_to_path(State, Property) -> %% @private remove_last_from_path(State) -> jesse_state:remove_last_from_path(State). + +%% @private +check_external_validation(Value, State) -> + case jesse_state:get_extra_validator(State) of + undefined -> State; + Fun -> Fun(Value, State) + end. diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 1c0a462..ae8cd36 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -64,7 +64,8 @@ | ?too_many_properties | ?wrong_length | ?wrong_size - | ?wrong_type. + | ?wrong_type + | ?external_error. -type data_error_type() :: data_error() | {data_error(), binary()}. @@ -245,13 +246,13 @@ check_value(Value, [{?ONEOF, Schemas} | Attrs], State) -> check_value(Value, [{?NOT, Schema} | Attrs], State) -> NewState = check_not(Value, Schema, State), check_value(Value, Attrs, NewState); -check_value(_Value, [], State) -> - State; check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> NewState = validate_ref(Value, RefSchemaURI, State), check_value(Value, Attrs, NewState); +check_value(Value, [], State) -> + check_external_validation(Value, State); check_value(Value, [_Attr | Attrs], State) -> - check_value(Value, Attrs, State). + check_value(Value, Attrs, State). %%% Internal functions %% @doc Adds Property to the current path and checks the value @@ -1351,3 +1352,9 @@ valid_datetime(DateTimeBin) -> _ -> false end. + +check_external_validation(Value, State) -> + case jesse_state:get_extra_validator(State) of + undefined -> State; + Fun -> Fun(Value, State) + end. From 58fac47e02e0d6a706b4ffb361e2160340e0a0a8 Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:43:00 +0000 Subject: [PATCH 06/15] more explicit error for enums --- src/jesse_schema_validator.hrl | 1 + src/jesse_validator_draft3.erl | 3 ++- src/jesse_validator_draft4.erl | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/jesse_schema_validator.hrl b/src/jesse_schema_validator.hrl index 690e061..6c77fb0 100644 --- a/src/jesse_schema_validator.hrl +++ b/src/jesse_schema_validator.hrl @@ -112,6 +112,7 @@ -define(not_one_schema_valid, 'not_one_schema_valid'). -define(not_schema_valid, 'not_schema_valid'). -define(wrong_not_schema, 'wrong_not_schema'). +-define(not_in_enum, 'not_in_enum'). -define(external_error, 'external_error'). %% diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 7db5e79..c35830e 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -48,6 +48,7 @@ | ?wrong_length | ?wrong_size | ?wrong_type + | ?not_in_enum | ?external_error. -type data_error_type() :: data_error() @@ -823,7 +824,7 @@ check_enum(Value, Enum, State) -> case IsValid of true -> State; false -> - handle_data_invalid(?not_in_range, Value, State) + handle_data_invalid(?not_in_enum, Value, State) end. check_format(_Value, _Format, State) -> diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index ae8cd36..b79c6f0 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -65,6 +65,7 @@ | ?wrong_length | ?wrong_size | ?wrong_type + | ?not_in_enum | ?external_error. -type data_error_type() :: data_error() @@ -939,7 +940,7 @@ check_enum(Value, Enum, State) -> case IsValid of true -> State; false -> - handle_data_invalid(?not_in_range, Value, State) + handle_data_invalid(?not_in_enum, Value, State) end. %% @doc format From 46f3d9a7869ca652c481c2864582e9b6faa28f08 Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:47:32 +0000 Subject: [PATCH 07/15] allow a setter_fun in schema validator allows to set values during validation --- src/jesse_schema_validator.erl | 5 +++-- src/jesse_state.erl | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index 69611ae..922526e 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -40,10 +40,11 @@ , Options :: [{Key :: atom(), Data :: any()}] ) -> {ok, jesse:json_term()} | no_return(). -validate(JsonSchema, Value, Options) -> +validate(JsonSchema, Value, Options0) -> + Options = [{with_value, Value} | proplists:delete(with_value, Options0)], State = jesse_state:new(JsonSchema, Options), NewState = validate_with_state(JsonSchema, Value, State), - {result(NewState), Value}. + {result(NewState), jesse_state:get_current_value(NewState)}. %% @doc Validates json `Data' against `JsonSchema' with `State'. %% If the given json is valid, then the latest state is returned to the caller, diff --git a/src/jesse_state.erl b/src/jesse_state.erl index c22aa7c..5edade6 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -27,6 +27,7 @@ -export([ add_to_path/2 , get_allowed_errors/1 , get_extra_validator/1 + , get_current_value/1 , get_current_path/1 , get_current_schema/1 , get_current_schema_id/1 @@ -37,6 +38,7 @@ , remove_last_from_path/1 , set_allowed_errors/2 , set_current_schema/2 + , set_value/3 , set_error_list/2 , resolve_ref/2 , undo_resolve_ref/2 @@ -51,12 +53,15 @@ -include("jesse_schema_validator.hrl"). -type extra_validator() :: fun((jesse:json_term(), state()) -> state()) | undefined. +-type setter_fun() :: fun((jesse:json_path(), jesse:json_term(), jesse:json_term()) -> jesse:json_term()) | undefined. + %% Internal datastructures -record( state , { root_schema :: jesse:json_term() , current_schema :: jesse:json_term() , current_path :: [binary() | non_neg_integer()] %% current path in reversed order + , current_value :: jesse:json_term() , allowed_errors :: non_neg_integer() | 'infinity' , error_list :: list() , error_handler :: fun(( jesse_error:error_reason() @@ -71,6 +76,7 @@ ?not_found ) , extra_validator :: extra_validator() + , setter_fun :: setter_fun() , id :: binary() | 'undefined' } ). @@ -154,6 +160,12 @@ new(JsonSchema, Options) -> ExtraValidator = proplists:get_value( extra_validator , Options ), + SetterFun = proplists:get_value( setter_fun + , Options + ), + Value = proplists:get_value( with_value + , Options + ), NewState = #state{ root_schema = JsonSchema , current_path = [] , allowed_errors = AllowedErrors @@ -162,6 +174,8 @@ new(JsonSchema, Options) -> , default_schema_ver = DefaultSchemaVer , schema_loader_fun = LoaderFun , extra_validator = ExtraValidator + , setter_fun = SetterFun + , current_value = Value }, set_current_schema(NewState, JsonSchema). @@ -385,4 +399,16 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) -> ?not_found end. +%% @doc Getter for `current_value'. +-spec get_current_value(State :: state()) -> jesse:json_term(). +get_current_value(#state{current_value = Value}) -> Value. + +-spec set_value(State :: state(), jesse:path(), jesse:json_term()) -> state(). +set_value(#state{setter_fun=undefined}=State, _Path, _Value) -> State; +set_value(#state{current_value=undefined}=State, _Path, _Value) -> State; +set_value(#state{setter_fun=Setter + ,current_value=Value + }=State, Path, NewValue) -> + State#state{current_value = Setter(Path, NewValue, Value)}. + get_extra_validator(#state{extra_validator=Fun}) -> Fun. From a4bf90fe461464ab88dd3b07ea58722f9acbbcf3 Mon Sep 17 00:00:00 2001 From: Luis Azedo Date: Fri, 24 Feb 2017 14:48:45 +0000 Subject: [PATCH 08/15] set default values for properties persistence depends on setter_fun option --- src/jesse_error.erl | 2 +- src/jesse_schema_validator.hrl | 1 + src/jesse_state.erl | 4 +- src/jesse_validator_draft3.erl | 99 ++++++++++++++++++++++----- src/jesse_validator_draft4.erl | 61 +++++++++++++---- test/jesse_schema_validator_tests.erl | 68 ++++++++++++++++++ 6 files changed, 204 insertions(+), 31 deletions(-) diff --git a/src/jesse_error.erl b/src/jesse_error.erl index 345b22c..453a249 100644 --- a/src/jesse_error.erl +++ b/src/jesse_error.erl @@ -60,7 +60,7 @@ %% throws an exeption, otherwise adds a new element to the list and returs it. -spec default_error_handler( Error :: error_reason() , ErrorList :: [error_reason()] - , AllowedErrors :: non_neg_integer() + , AllowedErrors :: non_neg_integer() | 'infinity' ) -> [error_reason()] | no_return(). default_error_handler(Error, ErrorList, AllowedErrors) -> case AllowedErrors > length(ErrorList) orelse AllowedErrors =:= 'infinity' of diff --git a/src/jesse_schema_validator.hrl b/src/jesse_schema_validator.hrl index 6c77fb0..13b176a 100644 --- a/src/jesse_schema_validator.hrl +++ b/src/jesse_schema_validator.hrl @@ -55,6 +55,7 @@ -define(MULTIPLEOF, <<"multipleOf">>). -define(MAXPROPERTIES, <<"maxProperties">>). -define(MINPROPERTIES, <<"minProperties">>). +-define(DEFAULT, <<"default">>). %% Constant definitions for Json types -define(ANY, <<"any">>). diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 5edade6..1063184 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -92,7 +92,7 @@ add_to_path(State, Property) -> State#state{current_path = [Property | CurrentPath]}. %% @doc Getter for `allowed_errors'. --spec get_allowed_errors(State :: state()) -> non_neg_integer(). +-spec get_allowed_errors(State :: state()) -> non_neg_integer() | 'infinity'. get_allowed_errors(#state{allowed_errors = AllowedErrors}) -> AllowedErrors. @@ -186,7 +186,7 @@ remove_last_from_path(State = #state{current_path = [_Property | Path]}) -> %% @doc Getter for `allowed_errors'. -spec set_allowed_errors( State :: state() - , AllowedErrors :: non_neg_integer() + , AllowedErrors :: non_neg_integer() | 'infinity' ) -> state(). set_allowed_errors(#state{} = State, AllowedErrors) -> State#state{allowed_errors = AllowedErrors}. diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index c35830e..612ac11 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -31,6 +31,7 @@ -include("jesse_schema_validator.hrl"). -type schema_error() :: ?wrong_type_dependency + | ?schema_invalid | ?wrong_type_items. -type schema_error_type() :: schema_error() @@ -348,21 +349,14 @@ check_properties(Value, Properties, State) -> = lists:foldl( fun({PropertyName, PropertySchema}, CurrentState) -> case get_value(PropertyName, Value) of ?not_found -> -%% @doc 5.7. required -%% -%% This attribute indicates if the instance must have a value, and not -%% be undefined. This is false by default, making the instance -%% optional. -%% @end - case get_value(?REQUIRED, PropertySchema) of - true -> - handle_data_invalid( {?missing_required_property - , PropertyName} - , Value - , CurrentState); - _ -> - CurrentState - end; + case get_value(?DEFAULT, PropertySchema) of + ?not_found -> check_required( PropertySchema + , PropertyName + , Value + , CurrentState + ); + Default -> check_default(PropertyName, PropertySchema, Default, CurrentState) + end; Property -> NewState = set_current_schema( CurrentState , PropertySchema @@ -583,6 +577,24 @@ check_items_fun(Tuples, State) -> ), set_current_schema(TmpState, get_current_schema(State)). + +%% @doc 5.7. required +%% +%% This attribute indicates if the instance must have a value, and not +%% be undefined. This is false by default, making the instance +%% optional. +%% @private +check_required(PropertySchema, PropertyName, Value, CurrentState) -> + case get_value(?REQUIRED, PropertySchema) of + true -> + handle_data_invalid( {?missing_required_property + , PropertyName} + , Value + , CurrentState); + _ -> + CurrentState + end. + %% @doc 5.8. dependencies %% %% This attribute is an object that defines the requirements of a @@ -980,7 +992,11 @@ compare_properties(Value1, Value2) -> %% Wrappers %% @private get_value(Key, Schema) -> - jesse_json_path:value(Key, Schema, ?not_found). + get_value(Key, Schema, ?not_found). + +%% @private +get_value(Key, Schema, Default) -> + jesse_json_path:value(Key, Schema, Default). %% @private unwrap(Value) -> @@ -1027,3 +1043,54 @@ check_external_validation(Value, State) -> undefined -> State; Fun -> Fun(Value, State) end. + +%% @private +set_value(PropertyName, Value, State) -> + Path = lists:reverse([PropertyName] ++ jesse_state:get_current_path(State)), + jesse_state:set_value(State, Path, Value). + +-define(types_for_defaults, [ ?STRING + , ?NUMBER + , ?INTEGER + , ?BOOLEAN + , ?OBJECT + ]). + +%% @private +check_default(PropertyName, PropertySchema, Default, State) -> + Type = get_value(?TYPE, PropertySchema, ?not_found), + case Type =/= ?not_found + andalso lists:member(Type, ?types_for_defaults) + andalso is_type_valid(Default, Type, State) of + false -> State; + true -> set_default(PropertyName, PropertySchema, Default, State) + end. + +%% @private +set_default(PropertyName, PropertySchema, Default, State) -> + State1 = set_value(PropertyName, Default, State), + Schema = jesse_state:get_current_schema(State1), + case Schema =/= PropertySchema andalso validate_schema(Default, PropertySchema, State1) of + {true, State4} -> State4; + _ -> State + end. + +%% @doc Validate a value against a schema in a given state. +%% Used by all combinators to run validation on a schema. +%% @private +validate_schema(Value, Schema, State0) -> + try + case jesse_lib:is_json_object(Schema) of + true -> + State1 = set_current_schema(State0, Schema), + State2 = jesse_schema_validator:validate_with_state( Schema + , Value + , State1 + ), + {true, State2}; + false -> + handle_schema_invalid(?schema_invalid, State0) + end + catch + throw:Errors -> {false, Errors} + end. diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index b79c6f0..14b1dba 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -377,17 +377,20 @@ check_properties(Value, Properties, State) -> TmpState = lists:foldl( fun({PropertyName, PropertySchema}, CurrentState) -> case get_value(PropertyName, Value) of - ?not_found -> - CurrentState; - Property -> - NewState = set_current_schema( CurrentState - , PropertySchema - ), - check_value( PropertyName - , Property - , PropertySchema - , NewState - ) + ?not_found -> + case get_value(?DEFAULT, PropertySchema) of + ?not_found -> CurrentState; + Default -> check_default(PropertyName, PropertySchema, Default, CurrentState) + end; + Property -> + NewState = set_current_schema( CurrentState + , PropertySchema + ), + check_value( PropertyName + , Property + , PropertySchema + , NewState + ) end end , State @@ -1304,7 +1307,11 @@ compare_properties(Value1, Value2) -> %% Wrappers %% @private get_value(Key, Schema) -> - jesse_json_path:value(Key, Schema, ?not_found). + get_value(Key, Schema, ?not_found). + +%% @private +get_value(Key, Schema, Default) -> + jesse_json_path:value(Key, Schema, Default). %% @private unwrap(Value) -> @@ -1359,3 +1366,33 @@ check_external_validation(Value, State) -> undefined -> State; Fun -> Fun(Value, State) end. + +%% @private +set_value(PropertyName, Value, State) -> + Path = lists:reverse([PropertyName] ++ jesse_state:get_current_path(State)), + jesse_state:set_value(State, Path, Value). + +-define(types_for_defaults, [ ?STRING + , ?NUMBER + , ?INTEGER + , ?BOOLEAN + , ?OBJECT + ]). + +%% @private +check_default(PropertyName, PropertySchema, Default, State) -> + Type = get_value(?TYPE, PropertySchema, ?not_found), + case Type =/= ?not_found + andalso lists:member(Type, ?types_for_defaults) + andalso is_type_valid(Default, Type) of + false -> State; + true -> set_default(PropertyName, PropertySchema, Default, State) + end. + +%% @private +set_default(PropertyName, PropertySchema, Default, State) -> + State1 = set_value(PropertyName, Default, State), + case validate_schema(Default, PropertySchema, State1) of + {true, State4} -> State4; + _ -> State + end. diff --git a/test/jesse_schema_validator_tests.erl b/test/jesse_schema_validator_tests.erl index df654ea..8057c39 100644 --- a/test/jesse_schema_validator_tests.erl +++ b/test/jesse_schema_validator_tests.erl @@ -21,6 +21,74 @@ -module(jesse_schema_validator_tests). -include_lib("eunit/include/eunit.hrl"). +setter_test() -> + Schema = {[ + {<<"type">>, <<"object">>}, + {<<"properties">>, {[ + {<<"bar">>, {[ + {<<"type">>, <<"string">>}, + {<<"minLength">>, 4}, + {<<"default">>, <<"awesome">>} + ]}} + ]}} + ]}, + + Default = {[{<<"bar">>, <<"awesome">>}]}, + Value = {[]}, + Fun = fun([K], V, {L1}) -> + {[{K, V} | proplists:delete(K, L1)]} + end, + Options = [{setter_fun, Fun}], + + [ ?assertEqual({ok, Value} + ,jesse_schema_validator:validate(Schema, Value, []) + ) + , ?assertEqual({ok, Default} + ,jesse_schema_validator:validate(Schema, Value, Options) + ) + ]. + +invalid_default_test() -> + BadSchema = {[ + {<<"type">>, <<"object">>}, + {<<"properties">>, {[ + {<<"bar">>, {[ + {<<"type">>, <<"string">>}, + {<<"minLength">>, 4}, + {<<"default">>, <<"bad">>} + ]}} + ]}} + ]}, + + GoodSchema = {[ + {<<"type">>, <<"object">>}, + {<<"properties">>, {[ + {<<"bar">>, {[ + {<<"type">>, <<"string">>}, + {<<"minLength">>, 4}, + {<<"default">>, <<"awesome">>} + ]}} + ]}} + ]}, + + WithDefault = {[{<<"bar">>, <<"good">>}]}, + WithoutDefault = {[]}, + + ?assertEqual( + {ok, WithoutDefault}, + jesse_schema_validator:validate(BadSchema, WithoutDefault, []) + ), + + ?assertEqual( + {ok, WithDefault}, + jesse_schema_validator:validate(BadSchema, WithDefault, []) + ), + + ?assertEqual( + {ok, WithoutDefault}, + jesse_schema_validator:validate(GoodSchema, WithoutDefault, []) + ). + data_invalid_test() -> IntegerSchema = {[{<<"type">>, <<"integer">>}]}, From 984635f8fe915789c96640abcb587d7fb5388612 Mon Sep 17 00:00:00 2001 From: lazedo Date: Mon, 27 Feb 2017 15:12:09 +0000 Subject: [PATCH 09/15] return errors in the order they occur default error handler --- src/jesse_error.erl | 2 +- test/jesse_schema_validator_tests.erl | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/jesse_error.erl b/src/jesse_error.erl index 453a249..87c0287 100644 --- a/src/jesse_error.erl +++ b/src/jesse_error.erl @@ -64,7 +64,7 @@ ) -> [error_reason()] | no_return(). default_error_handler(Error, ErrorList, AllowedErrors) -> case AllowedErrors > length(ErrorList) orelse AllowedErrors =:= 'infinity' of - true -> [Error | ErrorList]; + true -> ErrorList ++ [Error]; false -> throw([Error | ErrorList]) end. diff --git a/test/jesse_schema_validator_tests.erl b/test/jesse_schema_validator_tests.erl index 8057c39..cc1f48b 100644 --- a/test/jesse_schema_validator_tests.erl +++ b/test/jesse_schema_validator_tests.erl @@ -362,14 +362,13 @@ map_data_test_draft(URI) -> %% In case of future fails it can be replaced with manual catching and sorting %% of throwed error list, then checked using ?assertMatch ?assertThrow([{data_invalid, - {[ {<<"type">>, <<"integer">>} ]}, - wrong_type, #{}, - [<<"baz">>]}, - - {data_invalid, {[ {<<"type">>, <<"object">>} | _ ]}, wrong_type, 42, [<<"foo">>]} + ,{data_invalid, + {[ {<<"type">>, <<"integer">>} ]}, + wrong_type, #{}, + [<<"baz">>]} ], jesse_schema_validator:validate(Schema, InvalidJson, [{allowed_errors, infinity}])). From 46399e10079be9d0dc0a5d8ffddcb007a0271eb3 Mon Sep 17 00:00:00 2001 From: lazedo Date: Mon, 6 Mar 2017 18:48:26 +0000 Subject: [PATCH 10/15] fix path with default --- src/jesse_validator_draft3.erl | 5 +++-- src/jesse_validator_draft4.erl | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 612ac11..9b5077b 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -1070,8 +1070,9 @@ check_default(PropertyName, PropertySchema, Default, State) -> set_default(PropertyName, PropertySchema, Default, State) -> State1 = set_value(PropertyName, Default, State), Schema = jesse_state:get_current_schema(State1), - case Schema =/= PropertySchema andalso validate_schema(Default, PropertySchema, State1) of - {true, State4} -> State4; + State2 = add_to_path(State1, PropertyName), + case Schema =/= PropertySchema andalso validate_schema(Default, PropertySchema, State2) of + {true, State4} -> jesse_state:remove_last_from_path(State4); _ -> State end. diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 14b1dba..c6ddbc5 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -1392,7 +1392,8 @@ check_default(PropertyName, PropertySchema, Default, State) -> %% @private set_default(PropertyName, PropertySchema, Default, State) -> State1 = set_value(PropertyName, Default, State), - case validate_schema(Default, PropertySchema, State1) of - {true, State4} -> State4; + State2 = add_to_path(State1, PropertyName), + case validate_schema(Default, PropertySchema, State2) of + {true, State4} -> jesse_state:remove_last_from_path(State4); _ -> State end. From 67df6d6b931464563aaa706e773be2bf5a7a56bc Mon Sep 17 00:00:00 2001 From: lazedo Date: Mon, 13 Mar 2017 08:50:26 +0000 Subject: [PATCH 11/15] fix throwing error in order --- src/jesse_error.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jesse_error.erl b/src/jesse_error.erl index 87c0287..adb5061 100644 --- a/src/jesse_error.erl +++ b/src/jesse_error.erl @@ -65,7 +65,7 @@ default_error_handler(Error, ErrorList, AllowedErrors) -> case AllowedErrors > length(ErrorList) orelse AllowedErrors =:= 'infinity' of true -> ErrorList ++ [Error]; - false -> throw([Error | ErrorList]) + false -> throw(ErrorList ++ [Error]) end. %% @doc Generates a new data error and returns the updated state. From d17bc19ab886f4e132b5315db47b61c783258909 Mon Sep 17 00:00:00 2001 From: lazedo Date: Mon, 13 Mar 2017 15:41:48 +0000 Subject: [PATCH 12/15] add condition to apply defaults --- src/jesse_lib.erl | 10 ++++++++++ src/jesse_state.erl | 27 ++++++++++++++++++++++++++- src/jesse_validator_draft3.erl | 16 +++++++--------- src/jesse_validator_draft4.erl | 13 ++++++------- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/jesse_lib.erl b/src/jesse_lib.erl index 0a026c0..bafa68b 100644 --- a/src/jesse_lib.erl +++ b/src/jesse_lib.erl @@ -27,6 +27,7 @@ -export([ empty_if_not_found/1 , is_array/1 , is_json_object/1 + , is_json_object_empty/1 , is_null/1 ]). @@ -67,3 +68,12 @@ is_json_object(_) -> false. -spec is_null(Value :: any()) -> boolean(). is_null(null) -> true; is_null(_Value) -> false. + +%% @doc check if json object is_empty. +-spec is_json_object_empty(Value :: any()) -> boolean(). +is_json_object_empty({struct, Value}) when is_list(Value) andalso Value =:= [] -> true; +is_json_object_empty({Value}) when is_list(Value) andalso Value =:= [] -> true; +%% handle `jsx' empty objects +is_json_object_empty([{}]) -> true; +?IF_MAPS(is_json_object_empty(Map) when erlang:is_map(Map) -> maps:size(Map) =:= 0;) +is_json_object_empty(_) -> false. diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 9ac70fa..d9c3946 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -44,9 +44,16 @@ , undo_resolve_ref/2 , canonical_path/2 , combine_id/2 + , validator_options/1 + , validator_option/2, validator_option/3 ]). +-type option() :: {Key :: atom(), Data :: any()}. +-type options() :: [option()]. + -export_type([ state/0 + , option/0 + , options/0 ]). %% Includes @@ -78,6 +85,7 @@ , extra_validator :: extra_validator() , setter_fun :: setter_fun() , id :: http_uri:uri() | 'undefined' + , validator_options :: options() } ). @@ -134,7 +142,7 @@ get_error_list(#state{error_list = ErrorList}) -> %% @doc Returns newly created state. -spec new( JsonSchema :: jesse:json_term() - , Options :: [{Key :: atom(), Data :: any()}] + , Options :: options() ) -> state(). new(JsonSchema, Options) -> ErrorHandler = proplists:get_value( error_handler @@ -166,6 +174,11 @@ new(JsonSchema, Options) -> Value = proplists:get_value( with_value , Options ), + ValidatorOptions = proplists:get_value( validator_options + , Options + , [] + ), + NewState = #state{ root_schema = JsonSchema , current_path = [] , allowed_errors = AllowedErrors @@ -176,6 +189,7 @@ new(JsonSchema, Options) -> , extra_validator = ExtraValidator , setter_fun = SetterFun , current_value = Value + , validator_options = ValidatorOptions }, set_current_schema(NewState, JsonSchema). @@ -420,3 +434,14 @@ set_value(#state{setter_fun=Setter State#state{current_value = Setter(Path, NewValue, Value)}. get_extra_validator(#state{extra_validator=Fun}) -> Fun. + +-spec validator_options(State :: state()) -> options(). +validator_options(#state{validator_options=Options}) -> Options. + +-spec validator_option(Option :: atom(), State :: state()) -> any(). +validator_option(Option, #state{validator_options=Options}) -> + proplists:get_value(Option, Options). + +-spec validator_option(Option :: atom(), State :: state(), Default :: any()) -> any(). +validator_option(Option, #state{validator_options=Options}, Default) -> + proplists:get_value(Option, Options, Default). diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 5d21561..f85f554 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -1050,18 +1050,17 @@ set_value(PropertyName, Value, State) -> Path = lists:reverse([PropertyName] ++ jesse_state:get_current_path(State)), jesse_state:set_value(State, Path, Value). --define(types_for_defaults, [ ?STRING - , ?NUMBER - , ?INTEGER - , ?BOOLEAN - , ?OBJECT - ]). +check_default_for_type(Default, State) -> + jesse_state:validator_option('use_defaults', State, false) + andalso (not jesse_lib:is_json_object(Default) + orelse jesse_state:validator_option('apply_defaults_to_empty_objects', State, false) + orelse not jesse_lib:is_json_object_empty(Default)). %% @private check_default(PropertyName, PropertySchema, Default, State) -> Type = get_value(?TYPE, PropertySchema, ?not_found), case Type =/= ?not_found - andalso lists:member(Type, ?types_for_defaults) + andalso check_default_for_type(Default, State) andalso is_type_valid(Default, Type, State) of false -> State; true -> set_default(PropertyName, PropertySchema, Default, State) @@ -1070,9 +1069,8 @@ check_default(PropertyName, PropertySchema, Default, State) -> %% @private set_default(PropertyName, PropertySchema, Default, State) -> State1 = set_value(PropertyName, Default, State), - Schema = jesse_state:get_current_schema(State1), State2 = add_to_path(State1, PropertyName), - case Schema =/= PropertySchema andalso validate_schema(Default, PropertySchema, State2) of + case validate_schema(Default, PropertySchema, State2) of {true, State4} -> jesse_state:remove_last_from_path(State4); _ -> State end. diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 308f20f..f64db72 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -1373,18 +1373,17 @@ set_value(PropertyName, Value, State) -> Path = lists:reverse([PropertyName] ++ jesse_state:get_current_path(State)), jesse_state:set_value(State, Path, Value). --define(types_for_defaults, [ ?STRING - , ?NUMBER - , ?INTEGER - , ?BOOLEAN - , ?OBJECT - ]). +check_default_for_type(Default, State) -> + jesse_state:validator_option('use_defaults', State, false) + andalso (not jesse_lib:is_json_object(Default) + orelse jesse_state:validator_option('apply_defaults_to_empty_objects', State, false) + orelse not jesse_lib:is_json_object_empty(Default)). %% @private check_default(PropertyName, PropertySchema, Default, State) -> Type = get_value(?TYPE, PropertySchema, ?not_found), case Type =/= ?not_found - andalso lists:member(Type, ?types_for_defaults) + andalso check_default_for_type(Default, State) andalso is_type_valid(Default, Type) of false -> State; true -> set_default(PropertyName, PropertySchema, Default, State) From e8460e1802e7e4fb5d2dcabf156009c17105e58a Mon Sep 17 00:00:00 2001 From: lazedo Date: Mon, 13 Mar 2017 15:48:03 +0000 Subject: [PATCH 13/15] fix setter test --- test/jesse_schema_validator_tests.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/jesse_schema_validator_tests.erl b/test/jesse_schema_validator_tests.erl index cc1f48b..6724905 100644 --- a/test/jesse_schema_validator_tests.erl +++ b/test/jesse_schema_validator_tests.erl @@ -38,7 +38,9 @@ setter_test() -> Fun = fun([K], V, {L1}) -> {[{K, V} | proplists:delete(K, L1)]} end, - Options = [{setter_fun, Fun}], + Options = [ {setter_fun, Fun} + , {validator_options, [use_defaults]} + ], [ ?assertEqual({ok, Value} ,jesse_schema_validator:validate(Schema, Value, []) From c651c5e640ce15b44a69f4d99787f1c4ae761696 Mon Sep 17 00:00:00 2001 From: lazedo Date: Wed, 29 Mar 2017 04:02:36 +0100 Subject: [PATCH 14/15] handle invalid refs needed when allowed_errors is `infinity` --- src/jesse_validator_draft3.erl | 24 ++++++++++++++++++------ src/jesse_validator_draft4.erl | 20 +++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index f85f554..64420b2 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -913,15 +913,27 @@ check_extends_array(Value, Extends, State) -> %% @private validate_ref(Value, Reference, State) -> - {NewState, Schema} = resolve_ref(Reference, State), - ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), - undo_resolve_ref(ResultState, State). - + case resolve_ref(Reference, State) of + {error, NewState} -> + undo_resolve_ref(NewState, State); + {ok, NewState, Schema} -> + ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), + undo_resolve_ref(ResultState, State) + end. + +%% @doc Resolve a JSON reference +%% The "id" keyword is taken care of behind the scenes in jesse_state. %% @private resolve_ref(Reference, State) -> + CurrentErrors = jesse_state:get_error_list(State), NewState = jesse_state:resolve_ref(State, Reference), - Schema = get_current_schema(NewState), - {NewState, Schema}. + NewErrors = jesse_state:get_error_list(NewState), + case length(CurrentErrors) =:= length(NewErrors) of + true -> + Schema = get_current_schema(NewState), + {ok, NewState, Schema}; + false -> {error, NewState} + end. undo_resolve_ref(State, OriginalState) -> jesse_state:undo_resolve_ref(State, OriginalState). diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index f64db72..8fac2ab 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -1226,17 +1226,27 @@ validate_schema(Value, Schema, State0) -> %% @private validate_ref(Value, Reference, State) -> - {NewState, Schema} = resolve_ref(Reference, State), - ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), - undo_resolve_ref(ResultState, State). + case resolve_ref(Reference, State) of + {error, NewState} -> + undo_resolve_ref(NewState, State); + {ok, NewState, Schema} -> + ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), + undo_resolve_ref(ResultState, State) + end. %% @doc Resolve a JSON reference %% The "id" keyword is taken care of behind the scenes in jesse_state. %% @private resolve_ref(Reference, State) -> + CurrentErrors = jesse_state:get_error_list(State), NewState = jesse_state:resolve_ref(State, Reference), - Schema = get_current_schema(NewState), - {NewState, Schema}. + NewErrors = jesse_state:get_error_list(NewState), + case length(CurrentErrors) =:= length(NewErrors) of + true -> + Schema = get_current_schema(NewState), + {ok, NewState, Schema}; + false -> {error, NewState} + end. undo_resolve_ref(State, OriginalState) -> jesse_state:undo_resolve_ref(State, OriginalState). From 3c455d1524c083c41451466cad3580efae5e081c Mon Sep 17 00:00:00 2001 From: lazedo Date: Mon, 15 May 2017 15:03:12 +0100 Subject: [PATCH 15/15] fix default check when multiples types are provided --- src/jesse_validator_draft3.erl | 19 ++++++++++++----- src/jesse_validator_draft4.erl | 39 +++++++++++++++++----------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 64420b2..2be76fc 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -1071,13 +1071,22 @@ check_default_for_type(Default, State) -> %% @private check_default(PropertyName, PropertySchema, Default, State) -> Type = get_value(?TYPE, PropertySchema, ?not_found), - case Type =/= ?not_found - andalso check_default_for_type(Default, State) - andalso is_type_valid(Default, Type, State) of - false -> State; - true -> set_default(PropertyName, PropertySchema, Default, State) + case is_valid_default(Type, Default, State) of + true -> set_default(PropertyName, PropertySchema, Default, State); + false -> State end. +is_valid_default(?not_found, _Default, _State) -> false; +is_valid_default(Type, Default, State) + when is_binary(Type) -> + check_default_for_type(Default, State) + andalso is_type_valid(Default, Type, State); +is_valid_default(Types, Default, State) + when is_list(Types) -> + check_default_for_type(Default, State) + andalso lists:any(fun(Type) -> is_type_valid(Default, Type, State) end, Types); +is_valid_default(_, _Default, _State) -> false. + %% @private set_default(PropertyName, PropertySchema, Default, State) -> State1 = set_value(PropertyName, Default, State), diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 8fac2ab..fc3c3b9 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -1226,27 +1226,17 @@ validate_schema(Value, Schema, State0) -> %% @private validate_ref(Value, Reference, State) -> - case resolve_ref(Reference, State) of - {error, NewState} -> - undo_resolve_ref(NewState, State); - {ok, NewState, Schema} -> - ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), - undo_resolve_ref(ResultState, State) - end. + {NewState, Schema} = resolve_ref(Reference, State), + ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState), + undo_resolve_ref(ResultState, State). %% @doc Resolve a JSON reference %% The "id" keyword is taken care of behind the scenes in jesse_state. %% @private resolve_ref(Reference, State) -> - CurrentErrors = jesse_state:get_error_list(State), NewState = jesse_state:resolve_ref(State, Reference), - NewErrors = jesse_state:get_error_list(NewState), - case length(CurrentErrors) =:= length(NewErrors) of - true -> - Schema = get_current_schema(NewState), - {ok, NewState, Schema}; - false -> {error, NewState} - end. + Schema = get_current_schema(NewState), + {NewState, Schema}. undo_resolve_ref(State, OriginalState) -> jesse_state:undo_resolve_ref(State, OriginalState). @@ -1392,13 +1382,22 @@ check_default_for_type(Default, State) -> %% @private check_default(PropertyName, PropertySchema, Default, State) -> Type = get_value(?TYPE, PropertySchema, ?not_found), - case Type =/= ?not_found - andalso check_default_for_type(Default, State) - andalso is_type_valid(Default, Type) of - false -> State; - true -> set_default(PropertyName, PropertySchema, Default, State) + case is_valid_default(Type, Default, State) of + true -> set_default(PropertyName, PropertySchema, Default, State); + false -> State end. +is_valid_default(?not_found, _Default, _State) -> false; +is_valid_default(Type, Default, State) + when is_binary(Type) -> + check_default_for_type(Default, State) + andalso is_type_valid(Default, Type); +is_valid_default(Types, Default, State) + when is_list(Types) -> + check_default_for_type(Default, State) + andalso lists:any(fun(Type) -> is_type_valid(Default, Type) end, Types); +is_valid_default(_, _Default, _State) -> false. + %% @private set_default(PropertyName, PropertySchema, Default, State) -> State1 = set_value(PropertyName, Default, State),