From e21065f43a5ae5ca960f19de83cba7586cb60301 Mon Sep 17 00:00:00 2001 From: Anton Belyaev Date: Fri, 17 Mar 2017 13:30:30 +0300 Subject: [PATCH 01/18] add suppport for configurable and extendable validators --- AUTHORS | 1 + Makefile | 2 +- src/jesse_schema_validator.erl | 58 +++++++++---- src/jesse_state.erl | 43 +++++++++- src/jesse_validator_draft3.erl | 83 +++++++++++-------- src/jesse_validator_draft4.erl | 85 +++++++++++--------- test/Generic-Test-Suite/customValidator.json | 36 +++++++++ test/jesse_tests_generic_SUITE.erl | 83 +++++++++++++++++++ 8 files changed, 297 insertions(+), 94 deletions(-) create mode 100644 test/Generic-Test-Suite/customValidator.json create mode 100644 test/jesse_tests_generic_SUITE.erl diff --git a/AUTHORS b/AUTHORS index 112925fc..67320858 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,3 +15,4 @@ Andrei Neculau Stefan Strigler Sergey Prokhorov Yakov +Anton Belyaev \ No newline at end of file diff --git a/Makefile b/Makefile index cd638092..e5e825fd 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ eunit: .PHONY: ct ct: - $(REBAR) ct skip_deps=true suites="jesse_tests_draft3,jesse_tests_draft4" + $(REBAR) ct skip_deps=true suites="jesse_tests_draft3,jesse_tests_draft4,jesse_tests_generic" .PHONY: xref xref: diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index 69611ae3..13c4d47f 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -31,6 +31,16 @@ %% Includes -include("jesse_schema_validator.hrl"). +%% Behaviour definition +-callback check_value(Value, JsonSchema, State) -> + {Value, JsonSchema, State} | no_return() + when + Value :: any(), + JsonSchema :: jesse:json_term(), + State :: jesse_state:state(). + +-callback init_state() -> any() | undefined. + %%% API %% @doc Validates json `Data' against `JsonSchema' with `Options'. %% If the given json is valid, then it is returned to the caller as is, @@ -54,10 +64,29 @@ validate(JsonSchema, Value, Options) -> ) -> jesse_state:state() | no_return(). validate_with_state(JsonSchema, Value, State) -> - SchemaVer = get_schema_ver(JsonSchema, State), - select_and_run_validator(SchemaVer, JsonSchema, Value, State). + Validator = select_validator(JsonSchema, State), + run_validator(Validator, Value, JsonSchema, State). + %%% Internal functions +%% @doc Gets validator from the state or else +%% selects an appropriate one by schema version. +%% @private +select_validator(JsonSchema, State) -> + case jesse_state:get_validator(State) of + undefined -> + select_validator_by_schema(get_schema_ver(JsonSchema, State), State); + Validator -> + Validator + end. + +select_validator_by_schema(?json_schema_draft3, _) -> + jesse_validator_draft3; +select_validator_by_schema(?json_schema_draft4, _) -> + jesse_validator_draft4; +select_validator_by_schema(SchemaURI, State) -> + jesse_error:handle_schema_invalid({?schema_unsupported, SchemaURI}, State). + %% @doc Returns "$schema" property from `JsonSchema' if it is present, %% otherwise the default schema version from `State' is returned. %% @private @@ -76,18 +105,15 @@ result(State) -> _ -> throw(ErrorList) end. -%% @doc Runs appropriate validator depending on schema version -%% it is called with. +%% @doc Goes through attributes of the given `JsonSchema' and +%% validates the `Value' against them calling `Validator'. %% @private -select_and_run_validator(?json_schema_draft3, JsonSchema, Value, State) -> - jesse_validator_draft3:check_value( Value - , jesse_json_path:unwrap_value(JsonSchema) - , State - ); -select_and_run_validator(?json_schema_draft4, JsonSchema, Value, State) -> - jesse_validator_draft4:check_value( Value - , jesse_json_path:unwrap_value(JsonSchema) - , State - ); -select_and_run_validator(SchemaURI, _JsonSchema, _Value, State) -> - jesse_error:handle_schema_invalid({?schema_unsupported, SchemaURI}, State). +run_validator(_Validator, _Value, [], State) -> + State; +run_validator(Validator, Value0, JsonSchema0, State0) -> + JsonSchema1 = jesse_json_path:unwrap_value(JsonSchema0), + {Value, JsonSchema, State} = Validator:check_value( Value0 + , JsonSchema1 + , State0 + ), + run_validator(Validator, Value, JsonSchema, State). diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 875ae3b6..49091641 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -30,6 +30,8 @@ , get_current_schema/1 , get_current_schema_id/1 , get_default_schema_ver/1 + , get_validator/1 + , get_validator_state/1 , get_error_handler/1 , get_error_list/1 , new/2 @@ -37,6 +39,7 @@ , set_allowed_errors/2 , set_current_schema/2 , set_error_list/2 + , set_validator_state/2 , resolve_ref/2 , undo_resolve_ref/2 , canonical_path/2 @@ -62,6 +65,8 @@ , non_neg_integer() ) -> list() | no_return() ) + , validator :: module() | 'undefined' + , validator_state :: any() | 'undefined' , default_schema_ver :: binary() , schema_loader_fun :: fun(( string() ) -> {ok, jesse:json_term()} | @@ -110,6 +115,16 @@ get_current_schema_id(#state{ current_schema = CurrentSchema get_default_schema_ver(#state{default_schema_ver = SchemaVer}) -> SchemaVer. +%% @doc Getter for `validator'. +-spec get_validator(State :: state()) -> module() | undefined. +get_validator(#state{validator = Validator}) -> + Validator. + +%% @doc Getter for `validator_state'. +-spec get_validator_state(State :: state()) -> any() | undefined. +get_validator_state(#state{validator_state = ValidatorState}) -> + ValidatorState. + %% @doc Getter for `error_handler'. -spec get_error_handler(State :: state()) -> fun(( jesse_error:error_reason() , [jesse_error:error_reason()] @@ -144,15 +159,22 @@ new(JsonSchema, Options) -> , Options , MetaSchemaVer ), - LoaderFun = proplists:get_value( schema_loader_fun - , Options - , ?default_schema_loader_fun - ), + Validator = proplists:get_value( validator + , Options + , undefined + ), + ValidatorState = init_validator_state(Validator), + LoaderFun = proplists:get_value( schema_loader_fun + , Options + , ?default_schema_loader_fun + ), NewState = #state{ root_schema = JsonSchema , current_path = [] , allowed_errors = AllowedErrors , error_list = [] , error_handler = ErrorHandler + , validator = Validator + , validator_state = ValidatorState , default_schema_ver = DefaultSchemaVer , schema_loader_fun = LoaderFun }, @@ -183,6 +205,11 @@ set_current_schema(#state{id = Id} = State, NewSchema) -> set_error_list(State, ErrorList) -> State#state{error_list = ErrorList}. +%% @doc Setter for `validator_state'. +-spec set_validator_state(State :: state(), ValidatorState :: any()) -> state(). +set_validator_state(State, ValidatorState) -> + State#state{validator_state = ValidatorState}. + %% @doc Resolve a reference. -spec resolve_ref(State :: state(), Reference :: binary()) -> state(). resolve_ref(State, Reference) -> @@ -238,6 +265,14 @@ undo_resolve_ref(RefState, OriginalState) -> , id = OriginalState#state.id }. +%% @doc Init custom validator state. +%% @private +-spec init_validator_state(Validator :: module() | undefined) -> any() | undefined. +init_validator_state(undefined) -> + undefined; +init_validator_state(Validator) -> + Validator:init_state(). + %% @doc Retrieve a specific part of a schema %% @private -spec load_local_schema( Schema :: not_found | jesse:json_term() diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index b309d7e9..ec7444d2 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -22,9 +22,11 @@ %%%============================================================================= -module(jesse_validator_draft3). +-behaviour(jesse_schema_validator). %% API --export([ check_value/3 +-export([ init_state/0 + , check_value/3 ]). %% Includes @@ -54,15 +56,22 @@ | {data_error(), binary()}. %%% API -%% @doc Goes through attributes of the given schema `JsonSchema' and -%% validates the value `Value' against them. --spec check_value( Value :: any() - , JsonSchema :: jesse:json_term() - , State :: jesse_state:state() - ) -> jesse_state:state() | no_return(). +%% @doc Behaviour callback. Custom state is not used by this validator. +-spec init_state() -> undefined. +init_state() -> + undefined. + +%% @doc Validates the value `Value' against the attributes +%% of the given schema `JsonSchema'. +-spec check_value(Value, JsonSchema, State) -> + {Value, JsonSchema, State} | no_return() + when + Value :: any(), + JsonSchema :: jesse:json_term(), + State :: jesse_state:state(). check_value(Value, [{?TYPE, Type} | Attrs], State) -> NewState = check_type(Value, Type, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?PROPERTIES, Properties} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_properties( Value @@ -71,7 +80,7 @@ check_value(Value, [{?PROPERTIES, Properties} | Attrs], State) -> ); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value( Value , [{?PATTERNPROPERTIES, PatternProperties} | Attrs] , State @@ -83,7 +92,7 @@ check_value( Value ); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value( Value , [{?ADDITIONALPROPERTIES, AdditionalProperties} | Attrs] , State @@ -95,30 +104,30 @@ check_value( Value ); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ITEMS, Items} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_items(Value, Items, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value( Value , [{?ADDITIONALITEMS, _AdditionalItems} | Attrs] , State ) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value(Value, [{?REQUIRED, _Required} | Attrs], State) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; check_value(Value, [{?DEPENDENCIES, Dependencies} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_dependencies(Value, Dependencies, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MINIMUM, Minimum} | Attrs], State) -> NewState = case is_number(Value) of true -> @@ -129,7 +138,7 @@ check_value(Value, [{?MINIMUM, Minimum} | Attrs], State) -> false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXIMUM, Maximum} | Attrs], State) -> NewState = case is_number(Value) of true -> @@ -140,82 +149,82 @@ check_value(Value, [{?MAXIMUM, Maximum} | Attrs], State) -> false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value( Value , [{?EXCLUSIVEMINIMUM, _ExclusiveMinimum} | Attrs] , State ) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value( Value , [{?EXCLUSIVEMAXIMUM, _ExclusiveMaximum} | Attrs] , State ) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; check_value(Value, [{?MINITEMS, MinItems} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_min_items(Value, MinItems, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXITEMS, MaxItems} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_max_items(Value, MaxItems, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?UNIQUEITEMS, Uniqueitems} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_unique_items(Value, Uniqueitems, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?PATTERN, Pattern} | Attrs], State) -> NewState = case is_binary(Value) of true -> check_pattern(Value, Pattern, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MINLENGTH, MinLength} | Attrs], State) -> NewState = case is_binary(Value) of true -> check_min_length(Value, MinLength, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXLENGTH, MaxLength} | Attrs], State) -> NewState = case is_binary(Value) of true -> check_max_length(Value, MaxLength, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ENUM, Enum} | Attrs], State) -> NewState = check_enum(Value, Enum, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?FORMAT, Format} | Attrs], State) -> NewState = check_format(Value, Format, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?DIVISIBLEBY, DivisibleBy} | Attrs], State) -> NewState = case is_number(Value) of true -> check_divisible_by(Value, DivisibleBy, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?DISALLOW, Disallow} | Attrs], State) -> NewState = check_disallow(Value, Disallow, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?EXTENDS, Extends} | Attrs], State) -> NewState = check_extends(Value, Extends, State), - check_value(Value, Attrs, NewState); -check_value(_Value, [], State) -> - State; + {Value, Attrs, NewState}; +check_value(Value, [], State) -> + {Value, [], State}; check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> NewState = validate_ref(Value, RefSchemaURI, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [_Attr | Attrs], State) -> - check_value(Value, Attrs, State). + {Value, Attrs, State}. %%% Internal functions %% @doc Adds Property to the current path and checks the value @@ -880,7 +889,11 @@ check_disallow(Value, Disallow, State) -> check_extends(Value, Extends, State) -> case jesse_lib:is_json_object(Extends) of true -> - check_value(Value, Extends, set_current_schema(State, Extends)); + NewState = set_current_schema(State, Extends), + jesse_schema_validator:validate_with_state( Extends + , Value + , NewState + ); false -> case is_list(Extends) of true -> check_extends_array(Value, Extends, State); diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index a7e9c00b..99f2808b 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -22,9 +22,11 @@ %%%============================================================================= -module(jesse_validator_draft4). +-behaviour(jesse_schema_validator). %% API --export([ check_value/3 +-export([ init_state/0 + , check_value/3 ]). %% Includes @@ -71,15 +73,22 @@ | {data_error(), binary()}. %%% API -%% @doc Goes through attributes of the given schema `JsonSchema' and -%% validates the value `Value' against them. --spec check_value( Value :: any() - , JsonSchema :: jesse:json_term() - , State :: jesse_state:state() - ) -> jesse_state:state() | no_return(). +%% @doc Behaviour callback. Custom state is not used by this validator. +-spec init_state() -> undefined. +init_state() -> + undefined. + +%% @doc Validates the value `Value' against the attributes +%% of the given schema `JsonSchema'. +-spec check_value(Value, JsonSchema, State) -> + {Value, JsonSchema, State} | no_return() + when + Value :: any(), + JsonSchema :: jesse:json_term(), + State :: jesse_state:state(). check_value(Value, [{?TYPE, Type} | Attrs], State) -> NewState = check_type(Value, Type, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?PROPERTIES, Properties} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_properties( Value @@ -88,7 +97,7 @@ check_value(Value, [{?PROPERTIES, Properties} | Attrs], State) -> ); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value( Value , [{?PATTERNPROPERTIES, PatternProperties} | Attrs] , State @@ -100,7 +109,7 @@ check_value( Value ); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value( Value , [{?ADDITIONALPROPERTIES, AdditionalProperties} | Attrs] , State @@ -112,32 +121,32 @@ check_value( Value ); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ITEMS, Items} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_items(Value, Items, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value( Value , [{?ADDITIONALITEMS, _AdditionalItems} | Attrs] , State ) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; check_value(Value, [{?REQUIRED, Required} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_required(Value, Required, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?DEPENDENCIES, Dependencies} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_dependencies(Value, Dependencies, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MINIMUM, Minimum} | Attrs], State) -> NewState = case is_number(Value) of true -> @@ -148,7 +157,7 @@ check_value(Value, [{?MINIMUM, Minimum} | Attrs], State) -> false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXIMUM, Maximum} | Attrs], State) -> NewState = case is_number(Value) of true -> @@ -159,100 +168,100 @@ check_value(Value, [{?MAXIMUM, Maximum} | Attrs], State) -> false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value( Value , [{?EXCLUSIVEMINIMUM, _ExclusiveMinimum} | Attrs] , State ) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema check_value( Value , [{?EXCLUSIVEMAXIMUM, _ExclusiveMaximum} | Attrs] , State ) -> - check_value(Value, Attrs, State); + {Value, Attrs, State}; check_value(Value, [{?MINITEMS, MinItems} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_min_items(Value, MinItems, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXITEMS, MaxItems} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_max_items(Value, MaxItems, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?UNIQUEITEMS, Uniqueitems} | Attrs], State) -> NewState = case jesse_lib:is_array(Value) of true -> check_unique_items(Value, Uniqueitems, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?PATTERN, Pattern} | Attrs], State) -> NewState = case is_binary(Value) of true -> check_pattern(Value, Pattern, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MINLENGTH, MinLength} | Attrs], State) -> NewState = case is_binary(Value) of true -> check_min_length(Value, MinLength, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXLENGTH, MaxLength} | Attrs], State) -> NewState = case is_binary(Value) of true -> check_max_length(Value, MaxLength, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ENUM, Enum} | Attrs], State) -> NewState = check_enum(Value, Enum, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?FORMAT, Format} | Attrs], State) -> NewState = check_format(Value, Format, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MULTIPLEOF, Multiple} | Attrs], State) -> NewState = case is_number(Value) of true -> check_multiple_of(Value, Multiple, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MAXPROPERTIES, MaxProperties} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_max_properties(Value, MaxProperties, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?MINPROPERTIES, MinProperties} | Attrs], State) -> NewState = case jesse_lib:is_json_object(Value) of true -> check_min_properties(Value, MinProperties, State); false -> State end, - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ALLOF, Schemas} | Attrs], State) -> NewState = check_all_of(Value, Schemas, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ANYOF, Schemas} | Attrs], State) -> NewState = check_any_of(Value, Schemas, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?ONEOF, Schemas} | Attrs], State) -> NewState = check_one_of(Value, Schemas, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [{?NOT, Schema} | Attrs], State) -> NewState = check_not(Value, Schema, State), - check_value(Value, Attrs, NewState); -check_value(_Value, [], State) -> - State; + {Value, Attrs, NewState}; +check_value(Value, [], State) -> + {Value, [], State}; check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> NewState = validate_ref(Value, RefSchemaURI, State), - check_value(Value, Attrs, NewState); + {Value, Attrs, NewState}; check_value(Value, [_Attr | Attrs], State) -> - check_value(Value, Attrs, State). + {Value, Attrs, State}. %%% Internal functions %% @doc Adds Property to the current path and checks the value diff --git a/test/Generic-Test-Suite/customValidator.json b/test/Generic-Test-Suite/customValidator.json new file mode 100644 index 00000000..813216a8 --- /dev/null +++ b/test/Generic-Test-Suite/customValidator.json @@ -0,0 +1,36 @@ +[ + { + "description": "use custom validator", + "schema": { + "customDef": "testSuccess", + "properties": { + "testSuccess": { + "type": "boolean" + } + }, + "required": [ + "testSuccess" + ] + }, + "tests": [ + { + "description": "custom validation success", + "data": {"testSuccess": true}, + "valid": true + }, + { + "description": "custom validation failure", + "data": {"testSuccess": false}, + "valid": false + }, + { + "description": "base validation failure", + "data": {"wrongProperty": "whatever"}, + "valid": false + } + ], + "options": { + "validator": "jesse_tests_generic_SUITE" + } + } +] diff --git a/test/jesse_tests_generic_SUITE.erl b/test/jesse_tests_generic_SUITE.erl new file mode 100644 index 00000000..81db75d9 --- /dev/null +++ b/test/jesse_tests_generic_SUITE.erl @@ -0,0 +1,83 @@ +%%%============================================================================= +%% Copyright 2012- Klarna AB +%% Copyright 2015- AUTHORS +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% +%% @doc jesse test suite which covers generic jessy functionality +%% (not schema specific). +%% @end +%%%============================================================================= + +-module(jesse_tests_generic_SUITE). + +-behaviour(jesse_schema_validator). + +-compile([ export_all + ]). + +-define(EXCLUDED_FUNS, [ module_info + , all + , init_per_suite + , end_per_suite + , init_state + , check_value + , update_custom_state + ]). + +-include_lib("common_test/include/ct.hrl"). + +-import(jesse_tests_util, [ get_tests/2 + , do_test/2 + ]). + +all() -> + Exports = ?MODULE:module_info(exports), + [F || {F, _} <- Exports, not lists:member(F, ?EXCLUDED_FUNS)]. + +init_per_suite(Config) -> + inets:start(), + get_tests( "Generic-Test-Suite" + , <<"http://json-schema.org/draft-04/schema#">> + ) + ++ Config. + +end_per_suite(_Config) -> + inets:stop(). + +init_state() -> + 0. + +check_value(Value, [{<<"customDef">>, Property} | Attrs], State0) -> + State = update_custom_state(State0), + NewState = case jesse_json_path:path(Property, Value) of + true -> State; + false -> jesse_error:handle_data_invalid( 'custom_validator_reject' + , Value + , State); + %% Skip if custom property is missing + [] -> State + end, + {Value, Attrs, NewState}; +check_value(Value, JsonSchema, State) -> + jesse_validator_draft4:check_value(Value, JsonSchema, State). + +update_custom_state(State) -> + ValidatorState = 0 = jesse_state:get_validator_state(State), + jesse_state:set_validator_state(State, ValidatorState + 1). + +%%% Testcases + +additionalItems(Config) -> + do_test("customValidator", Config). From c774499254b129883adb5469f96acdeab2bf1057 Mon Sep 17 00:00:00 2001 From: Anton Belyaev Date: Fri, 24 Mar 2017 10:38:18 +0300 Subject: [PATCH 02/18] clarify schema validator interface --- src/jesse_schema_validator.erl | 28 +-- src/jesse_validator_draft3.erl | 260 ++++++++++++------------- src/jesse_validator_draft4.erl | 299 +++++++++++++---------------- test/jesse_tests_generic_SUITE.erl | 23 ++- 4 files changed, 280 insertions(+), 330 deletions(-) diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index 13c4d47f..735cf78f 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -32,12 +32,12 @@ -include("jesse_schema_validator.hrl"). %% Behaviour definition --callback check_value(Value, JsonSchema, State) -> - {Value, JsonSchema, State} | no_return() +-callback check_value(Value, Attr, State) -> + State | no_return() when - Value :: any(), - JsonSchema :: jesse:json_term(), - State :: jesse_state:state(). + Value :: any(), + Attr :: {binary(), jesse:json_term()}, + State :: jesse_state:state(). -callback init_state() -> any() | undefined. @@ -63,8 +63,9 @@ validate(JsonSchema, Value, Options) -> , State :: jesse_state:state() ) -> jesse_state:state() | no_return(). -validate_with_state(JsonSchema, Value, State) -> - Validator = select_validator(JsonSchema, State), +validate_with_state(JsonSchema0, Value, State) -> + Validator = select_validator(JsonSchema0, State), + JsonSchema = jesse_json_path:unwrap_value(JsonSchema0), run_validator(Validator, Value, JsonSchema, State). @@ -110,10 +111,9 @@ result(State) -> %% @private run_validator(_Validator, _Value, [], State) -> State; -run_validator(Validator, Value0, JsonSchema0, State0) -> - JsonSchema1 = jesse_json_path:unwrap_value(JsonSchema0), - {Value, JsonSchema, State} = Validator:check_value( Value0 - , JsonSchema1 - , State0 - ), - run_validator(Validator, Value, JsonSchema, State). +run_validator(Validator, Value, [Attr | Attrs], State0) -> + State = Validator:check_value( Value + , Attr + , State0 + ), + run_validator(Validator, Value, Attrs, State). diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index ec7444d2..4d2c8555 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -63,168 +63,146 @@ init_state() -> %% @doc Validates the value `Value' against the attributes %% of the given schema `JsonSchema'. --spec check_value(Value, JsonSchema, State) -> - {Value, JsonSchema, State} | no_return() +-spec check_value(Value, Attr, State) -> + State | no_return() when - Value :: any(), - JsonSchema :: jesse:json_term(), - State :: jesse_state:state(). -check_value(Value, [{?TYPE, Type} | Attrs], State) -> - NewState = check_type(Value, Type, State), - {Value, Attrs, NewState}; -check_value(Value, [{?PROPERTIES, Properties} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_properties( Value - , unwrap(Properties) - , State - ); - false -> State - end, - {Value, Attrs, NewState}; + Value :: any(), + Attr :: {binary(), jesse:json_term()}, + State :: jesse_state:state(). +check_value(Value, {?TYPE, Type}, State) -> + check_type(Value, Type, State); +check_value(Value, {?PROPERTIES, Properties}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_properties( Value + , unwrap(Properties) + , State + ); + false -> State + end; check_value( Value - , [{?PATTERNPROPERTIES, PatternProperties} | Attrs] + , {?PATTERNPROPERTIES, PatternProperties} , State ) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_pattern_properties( Value - , PatternProperties - , State - ); - false -> State - end, - {Value, Attrs, NewState}; + case jesse_lib:is_json_object(Value) of + true -> check_pattern_properties( Value + , PatternProperties + , State + ); + false -> State + end; check_value( Value - , [{?ADDITIONALPROPERTIES, AdditionalProperties} | Attrs] + , {?ADDITIONALPROPERTIES, AdditionalProperties} , State ) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_additional_properties( Value - , AdditionalProperties - , State - ); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?ITEMS, Items} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_items(Value, Items, State); - false -> State - end, - {Value, Attrs, NewState}; + case jesse_lib:is_json_object(Value) of + true -> check_additional_properties( Value + , AdditionalProperties + , State + ); + false -> State + end; +check_value(Value, {?ITEMS, Items}, State) -> + case jesse_lib:is_array(Value) of + true -> check_items(Value, Items, State); + false -> State + end; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value( Value - , [{?ADDITIONALITEMS, _AdditionalItems} | Attrs] +check_value( _Value + , {?ADDITIONALITEMS, _AdditionalItems} , State ) -> - {Value, Attrs, State}; + State; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value(Value, [{?REQUIRED, _Required} | Attrs], State) -> - {Value, Attrs, State}; -check_value(Value, [{?DEPENDENCIES, Dependencies} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_dependencies(Value, Dependencies, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MINIMUM, Minimum} | Attrs], State) -> - NewState = case is_number(Value) of - true -> - ExclusiveMinimum = get_value( ?EXCLUSIVEMINIMUM - , get_current_schema(State) - ), - check_minimum(Value, Minimum, ExclusiveMinimum, State); - false -> - State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXIMUM, Maximum} | Attrs], State) -> - NewState = case is_number(Value) of - true -> - ExclusiveMaximum = get_value( ?EXCLUSIVEMAXIMUM - , get_current_schema(State) - ), - check_maximum(Value, Maximum, ExclusiveMaximum, State); - false -> - State - end, - {Value, Attrs, NewState}; +check_value(_Value, {?REQUIRED, _Required}, State) -> + State; +check_value(Value, {?DEPENDENCIES, Dependencies}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_dependencies(Value, Dependencies, State); + false -> State + end; +check_value(Value, {?MINIMUM, Minimum}, State) -> + case is_number(Value) of + true -> + ExclusiveMinimum = get_value( ?EXCLUSIVEMINIMUM + , get_current_schema(State) + ), + check_minimum(Value, Minimum, ExclusiveMinimum, State); + false -> + State + end; +check_value(Value, {?MAXIMUM, Maximum}, State) -> + case is_number(Value) of + true -> + ExclusiveMaximum = get_value( ?EXCLUSIVEMAXIMUM + , get_current_schema(State) + ), + check_maximum(Value, Maximum, ExclusiveMaximum, State); + false -> + State + end; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value( Value - , [{?EXCLUSIVEMINIMUM, _ExclusiveMinimum} | Attrs] +check_value( _Value + , {?EXCLUSIVEMINIMUM, _ExclusiveMinimum} , State ) -> - {Value, Attrs, State}; + State; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value( Value - , [{?EXCLUSIVEMAXIMUM, _ExclusiveMaximum} | Attrs] +check_value( _Value + , {?EXCLUSIVEMAXIMUM, _ExclusiveMaximum} , State ) -> - {Value, Attrs, State}; -check_value(Value, [{?MINITEMS, MinItems} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_min_items(Value, MinItems, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXITEMS, MaxItems} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_max_items(Value, MaxItems, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?UNIQUEITEMS, Uniqueitems} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_unique_items(Value, Uniqueitems, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?PATTERN, Pattern} | Attrs], State) -> - NewState = case is_binary(Value) of - true -> check_pattern(Value, Pattern, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MINLENGTH, MinLength} | Attrs], State) -> - NewState = case is_binary(Value) of - true -> check_min_length(Value, MinLength, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXLENGTH, MaxLength} | Attrs], State) -> - NewState = case is_binary(Value) of - true -> check_max_length(Value, MaxLength, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?ENUM, Enum} | Attrs], State) -> - NewState = check_enum(Value, Enum, State), - {Value, Attrs, NewState}; -check_value(Value, [{?FORMAT, Format} | Attrs], State) -> - NewState = check_format(Value, Format, State), - {Value, Attrs, NewState}; -check_value(Value, [{?DIVISIBLEBY, DivisibleBy} | Attrs], State) -> - NewState = case is_number(Value) of - true -> check_divisible_by(Value, DivisibleBy, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?DISALLOW, Disallow} | Attrs], State) -> - NewState = check_disallow(Value, Disallow, State), - {Value, Attrs, NewState}; -check_value(Value, [{?EXTENDS, Extends} | Attrs], State) -> - NewState = check_extends(Value, Extends, State), - {Value, Attrs, NewState}; -check_value(Value, [], State) -> - {Value, [], State}; -check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> - NewState = validate_ref(Value, RefSchemaURI, State), - {Value, Attrs, NewState}; -check_value(Value, [_Attr | Attrs], State) -> - {Value, Attrs, State}. + State; +check_value(Value, {?MINITEMS, MinItems}, State) -> + case jesse_lib:is_array(Value) of + true -> check_min_items(Value, MinItems, State); + false -> State + end; +check_value(Value, {?MAXITEMS, MaxItems}, State) -> + case jesse_lib:is_array(Value) of + true -> check_max_items(Value, MaxItems, State); + false -> State + end; +check_value(Value, {?UNIQUEITEMS, Uniqueitems}, State) -> + case jesse_lib:is_array(Value) of + true -> check_unique_items(Value, Uniqueitems, State); + false -> State + end; +check_value(Value, {?PATTERN, Pattern}, State) -> + case is_binary(Value) of + true -> check_pattern(Value, Pattern, State); + false -> State + end; +check_value(Value, {?MINLENGTH, MinLength}, State) -> + case is_binary(Value) of + true -> check_min_length(Value, MinLength, State); + false -> State + end; +check_value(Value, {?MAXLENGTH, MaxLength}, State) -> + case is_binary(Value) of + true -> check_max_length(Value, MaxLength, State); + false -> State + end; +check_value(Value, {?ENUM, Enum}, State) -> + check_enum(Value, Enum, State); +check_value(Value, {?FORMAT, Format}, State) -> + check_format(Value, Format, State); +check_value(Value, {?DIVISIBLEBY, DivisibleBy}, State) -> + case is_number(Value) of + true -> check_divisible_by(Value, DivisibleBy, State); + false -> State + end; +check_value(Value, {?DISALLOW, Disallow}, State) -> + check_disallow(Value, Disallow, State); +check_value(Value, {?EXTENDS, Extends}, State) -> + check_extends(Value, Extends, State); +check_value(Value, {?REF, RefSchemaURI}, State) -> + validate_ref(Value, RefSchemaURI, State); +check_value(_Value, _Attr, State) -> + State. %%% Internal functions %% @doc Adds Property to the current path and checks the value diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 99f2808b..20093abe 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -80,188 +80,161 @@ init_state() -> %% @doc Validates the value `Value' against the attributes %% of the given schema `JsonSchema'. --spec check_value(Value, JsonSchema, State) -> - {Value, JsonSchema, State} | no_return() +-spec check_value(Value, Attr, State) -> + State | no_return() when - Value :: any(), - JsonSchema :: jesse:json_term(), - State :: jesse_state:state(). -check_value(Value, [{?TYPE, Type} | Attrs], State) -> - NewState = check_type(Value, Type, State), - {Value, Attrs, NewState}; -check_value(Value, [{?PROPERTIES, Properties} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_properties( Value - , unwrap(Properties) - , State - ); - false -> State - end, - {Value, Attrs, NewState}; + Value :: any(), + Attr :: {binary(), jesse:json_term()}, + State :: jesse_state:state(). +check_value(Value, {?TYPE, Type}, State) -> + check_type(Value, Type, State); +check_value(Value, {?PROPERTIES, Properties}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_properties( Value + , unwrap(Properties) + , State + ); + false -> State + end; check_value( Value - , [{?PATTERNPROPERTIES, PatternProperties} | Attrs] + , {?PATTERNPROPERTIES, PatternProperties} , State ) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_pattern_properties( Value - , PatternProperties - , State - ); - false -> State - end, - {Value, Attrs, NewState}; + case jesse_lib:is_json_object(Value) of + true -> check_pattern_properties( Value + , PatternProperties + , State + ); + false -> State + end; check_value( Value - , [{?ADDITIONALPROPERTIES, AdditionalProperties} | Attrs] + , {?ADDITIONALPROPERTIES, AdditionalProperties} , State ) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_additional_properties( Value - , AdditionalProperties - , State - ); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?ITEMS, Items} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_items(Value, Items, State); - false -> State - end, - {Value, Attrs, NewState}; + case jesse_lib:is_json_object(Value) of + true -> check_additional_properties( Value + , AdditionalProperties + , State + ); + false -> State + end; +check_value(Value, {?ITEMS, Items}, State) -> + case jesse_lib:is_array(Value) of + true -> check_items(Value, Items, State); + false -> State + end; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value( Value - , [{?ADDITIONALITEMS, _AdditionalItems} | Attrs] +check_value( _Value + , {?ADDITIONALITEMS, _AdditionalItems} , State ) -> - {Value, Attrs, State}; -check_value(Value, [{?REQUIRED, Required} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_required(Value, Required, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?DEPENDENCIES, Dependencies} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_dependencies(Value, Dependencies, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MINIMUM, Minimum} | Attrs], State) -> - NewState = case is_number(Value) of - true -> - ExclusiveMinimum = get_value( ?EXCLUSIVEMINIMUM - , get_current_schema(State) - ), - check_minimum(Value, Minimum, ExclusiveMinimum, State); - false -> - State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXIMUM, Maximum} | Attrs], State) -> - NewState = case is_number(Value) of - true -> - ExclusiveMaximum = get_value( ?EXCLUSIVEMAXIMUM - , get_current_schema(State) - ), - check_maximum(Value, Maximum, ExclusiveMaximum, State); - false -> - State - end, - {Value, Attrs, NewState}; + State; +check_value(Value, {?REQUIRED, Required}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_required(Value, Required, State); + false -> State + end; +check_value(Value, {?DEPENDENCIES, Dependencies}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_dependencies(Value, Dependencies, State); + false -> State + end; +check_value(Value, {?MINIMUM, Minimum}, State) -> + case is_number(Value) of + true -> + ExclusiveMinimum = get_value( ?EXCLUSIVEMINIMUM + , get_current_schema(State) + ), + check_minimum(Value, Minimum, ExclusiveMinimum, State); + false -> + State + end; +check_value(Value, {?MAXIMUM, Maximum}, State) -> + case is_number(Value) of + true -> + ExclusiveMaximum = get_value( ?EXCLUSIVEMAXIMUM + , get_current_schema(State) + ), + check_maximum(Value, Maximum, ExclusiveMaximum, State); + false -> + State + end; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value( Value - , [{?EXCLUSIVEMINIMUM, _ExclusiveMinimum} | Attrs] +check_value( _Value + , {?EXCLUSIVEMINIMUM, _ExclusiveMinimum} , State ) -> - {Value, Attrs, State}; + State; %% doesn't really do anything, since this attribute will be handled %% by the previous function clause if it's presented in the schema -check_value( Value - , [{?EXCLUSIVEMAXIMUM, _ExclusiveMaximum} | Attrs] +check_value( _Value + , {?EXCLUSIVEMAXIMUM, _ExclusiveMaximum} , State ) -> - {Value, Attrs, State}; -check_value(Value, [{?MINITEMS, MinItems} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_min_items(Value, MinItems, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXITEMS, MaxItems} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_max_items(Value, MaxItems, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?UNIQUEITEMS, Uniqueitems} | Attrs], State) -> - NewState = case jesse_lib:is_array(Value) of - true -> check_unique_items(Value, Uniqueitems, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?PATTERN, Pattern} | Attrs], State) -> - NewState = case is_binary(Value) of - true -> check_pattern(Value, Pattern, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MINLENGTH, MinLength} | Attrs], State) -> - NewState = case is_binary(Value) of - true -> check_min_length(Value, MinLength, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXLENGTH, MaxLength} | Attrs], State) -> - NewState = case is_binary(Value) of - true -> check_max_length(Value, MaxLength, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?ENUM, Enum} | Attrs], State) -> - NewState = check_enum(Value, Enum, State), - {Value, Attrs, NewState}; -check_value(Value, [{?FORMAT, Format} | Attrs], State) -> - NewState = check_format(Value, Format, State), - {Value, Attrs, NewState}; -check_value(Value, [{?MULTIPLEOF, Multiple} | Attrs], State) -> - NewState = case is_number(Value) of - true -> check_multiple_of(Value, Multiple, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MAXPROPERTIES, MaxProperties} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_max_properties(Value, MaxProperties, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?MINPROPERTIES, MinProperties} | Attrs], State) -> - NewState = case jesse_lib:is_json_object(Value) of - true -> check_min_properties(Value, MinProperties, State); - false -> State - end, - {Value, Attrs, NewState}; -check_value(Value, [{?ALLOF, Schemas} | Attrs], State) -> - NewState = check_all_of(Value, Schemas, State), - {Value, Attrs, NewState}; -check_value(Value, [{?ANYOF, Schemas} | Attrs], State) -> - NewState = check_any_of(Value, Schemas, State), - {Value, Attrs, NewState}; -check_value(Value, [{?ONEOF, Schemas} | Attrs], State) -> - NewState = check_one_of(Value, Schemas, State), - {Value, Attrs, NewState}; -check_value(Value, [{?NOT, Schema} | Attrs], State) -> - NewState = check_not(Value, Schema, State), - {Value, Attrs, NewState}; -check_value(Value, [], State) -> - {Value, [], State}; -check_value(Value, [{?REF, RefSchemaURI} | Attrs], State) -> - NewState = validate_ref(Value, RefSchemaURI, State), - {Value, Attrs, NewState}; -check_value(Value, [_Attr | Attrs], State) -> - {Value, Attrs, State}. + State; +check_value(Value, {?MINITEMS, MinItems}, State) -> + case jesse_lib:is_array(Value) of + true -> check_min_items(Value, MinItems, State); + false -> State + end; +check_value(Value, {?MAXITEMS, MaxItems}, State) -> + case jesse_lib:is_array(Value) of + true -> check_max_items(Value, MaxItems, State); + false -> State + end; +check_value(Value, {?UNIQUEITEMS, Uniqueitems}, State) -> + case jesse_lib:is_array(Value) of + true -> check_unique_items(Value, Uniqueitems, State); + false -> State + end; +check_value(Value, {?PATTERN, Pattern}, State) -> + case is_binary(Value) of + true -> check_pattern(Value, Pattern, State); + false -> State + end; +check_value(Value, {?MINLENGTH, MinLength}, State) -> + case is_binary(Value) of + true -> check_min_length(Value, MinLength, State); + false -> State + end; +check_value(Value, {?MAXLENGTH, MaxLength}, State) -> + case is_binary(Value) of + true -> check_max_length(Value, MaxLength, State); + false -> State + end; +check_value(Value, {?ENUM, Enum}, State) -> + check_enum(Value, Enum, State); +check_value(Value, {?FORMAT, Format}, State) -> + check_format(Value, Format, State); +check_value(Value, {?MULTIPLEOF, Multiple}, State) -> + case is_number(Value) of + true -> check_multiple_of(Value, Multiple, State); + false -> State + end; +check_value(Value, {?MAXPROPERTIES, MaxProperties}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_max_properties(Value, MaxProperties, State); + false -> State + end; +check_value(Value, {?MINPROPERTIES, MinProperties}, State) -> + case jesse_lib:is_json_object(Value) of + true -> check_min_properties(Value, MinProperties, State); + false -> State + end; +check_value(Value, {?ALLOF, Schemas}, State) -> + check_all_of(Value, Schemas, State); +check_value(Value, {?ANYOF, Schemas}, State) -> + check_any_of(Value, Schemas, State); +check_value(Value, {?ONEOF, Schemas}, State) -> + check_one_of(Value, Schemas, State); +check_value(Value, {?NOT, Schema}, State) -> + check_not(Value, Schema, State); +check_value(Value, {?REF, RefSchemaURI}, State) -> + validate_ref(Value, RefSchemaURI, State); +check_value(_Value, _Attr, State) -> + State. %%% Internal functions %% @doc Adds Property to the current path and checks the value diff --git a/test/jesse_tests_generic_SUITE.erl b/test/jesse_tests_generic_SUITE.erl index 81db75d9..c332b3f9 100644 --- a/test/jesse_tests_generic_SUITE.erl +++ b/test/jesse_tests_generic_SUITE.erl @@ -59,19 +59,18 @@ end_per_suite(_Config) -> init_state() -> 0. -check_value(Value, [{<<"customDef">>, Property} | Attrs], State0) -> +check_value(Value, {<<"customDef">>, Property}, State0) -> State = update_custom_state(State0), - NewState = case jesse_json_path:path(Property, Value) of - true -> State; - false -> jesse_error:handle_data_invalid( 'custom_validator_reject' - , Value - , State); - %% Skip if custom property is missing - [] -> State - end, - {Value, Attrs, NewState}; -check_value(Value, JsonSchema, State) -> - jesse_validator_draft4:check_value(Value, JsonSchema, State). + case jesse_json_path:path(Property, Value) of + true -> State; + false -> jesse_error:handle_data_invalid( 'custom_validator_reject' + , Value + , State); + %% Skip if custom property is missing + [] -> State + end; +check_value(Value, Attr, State) -> + jesse_validator_draft4:check_value(Value, Attr, State). update_custom_state(State) -> ValidatorState = 0 = jesse_state:get_validator_state(State), From 7ef3873fdc3773ecaa6df8a8061499d6e1270027 Mon Sep 17 00:00:00 2001 From: Anton Belyaev Date: Wed, 22 Mar 2017 15:45:34 +0300 Subject: [PATCH 03/18] introduce jesse:validate_ref/2,3 interface --- src/jesse.erl | 42 ++++++++++++++++++++++++++++++++++ src/jesse_schema_validator.erl | 16 +++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/jesse.erl b/src/jesse.erl index bec342fd..32fc5f1b 100644 --- a/src/jesse.erl +++ b/src/jesse.erl @@ -33,6 +33,8 @@ , load_schemas/3 , validate/2 , validate/3 + , validate_ref/2 + , validate_ref/3 , validate_with_schema/2 , validate_with_schema/3 ]). @@ -150,6 +152,34 @@ validate(Schema, Data, Options) -> throw:Error -> {error, Error} end. +%% @doc Equivalent to {@link validate/2} where `Ref' is an absolute $ref +%% which base Uri is a schema key in the internal storage. +-spec validate_ref( Ref :: string() + , Data :: json_term() | binary() + ) -> {ok, json_term()} + | jesse_error:error() + | jesse_database:error(). +validate_ref(Ref, Data) -> + validate_ref(Ref, Data, []). + +%% @doc Equivalent to {@link validate/3} where `Ref' is an absolute $ref +%% which base Uri is a schema key in the internal storage. +-spec validate_ref( Ref :: string() + , Data :: json_term() | binary() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> {ok, json_term()} + | jesse_error:error() + | jesse_database:error(). +validate_ref(Ref, Data, Options) -> + try + ParserFun = proplists:get_value(parser_fun, Options, fun(X) -> X end), + ParsedData = try_parse(data, ParserFun, Data), + {JsonSchema, LocalRef} = parse_ref(Ref), + jesse_schema_validator:validate_ref(LocalRef, JsonSchema, ParsedData, Options) + catch + throw:Error -> {error, Error} + end. + %% @doc Equivalent to {@link validate_with_schema/3} where `Options' %% is an empty list. -spec validate_with_schema( Schema :: json_term() | binary() @@ -195,3 +225,15 @@ try_parse(Type, ParserFun, JsonBin) -> schema -> throw({schema_error, {parse_error, Error}}) end end. + +%% @doc Loads schema from the internal storage according to the base uri +%% in `Ref' and prepares local ref. +%% @private +parse_ref(Ref) -> + [BaseURI, LocalRef] = re:split( Ref + , <<$#>> + , [{return, binary}, unicode] + ), + JsonSchema = jesse_database:load(binary_to_list(BaseURI)), + LocalRef1 = <<$#, LocalRef/binary>>, + {JsonSchema, LocalRef1}. diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index 735cf78f..58e5118f 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -25,6 +25,7 @@ %% API -export([ validate/3 + , validate_ref/4 , validate_with_state/3 ]). @@ -55,6 +56,21 @@ validate(JsonSchema, Value, Options) -> NewState = validate_with_state(JsonSchema, Value, State), {result(NewState), Value}. +%% @doc Validates json `Data' against `Ref' in `JsonSchema' with `Options'. +%% If the given json is valid, then it is returned to the caller as is, +%% otherwise an exception will be thrown. +-spec validate_ref( Ref :: binary() + , JsonSchema :: jesse:json_term() + , Data :: jesse:json_term() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> {ok, jesse:json_term()} + | no_return(). +validate_ref(Ref, JsonSchema, Value, Options) -> + State = jesse_state:new(JsonSchema, Options), + Schema = [{<<"$ref">>, Ref}], + NewState = validate_with_state(Schema, Value, State), + {result(NewState), Value}. + %% @doc Validates json `Data' against `JsonSchema' with `State'. %% If the given json is valid, then the latest state is returned to the caller, %% otherwise an exception will be thrown. From e2ce63d2c170516a4368b29adc8b136d9c1e574e Mon Sep 17 00:00:00 2001 From: Anton Belyaev Date: Fri, 24 Mar 2017 20:03:57 +0300 Subject: [PATCH 04/18] fix undefined type http_uri --- src/jesse_state.erl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 49091641..6d071c80 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -53,6 +53,8 @@ -include("jesse_schema_validator.hrl"). %% Internal datastructures +-type http_uri() :: string(). + -record( state , { root_schema :: jesse:json_term() , current_schema :: jesse:json_term() @@ -73,7 +75,7 @@ jesse:json_term() | ?not_found ) - , id :: http_uri:uri() | 'undefined' + , id :: http_uri() | 'undefined' } ). @@ -312,8 +314,8 @@ load_local_schema(Schema, [Key | Keys]) -> %% @doc Resolve a new id %% @private --spec combine_id(undefined | http_uri:uri(), - undefined | binary()) -> http_uri:uri(). +-spec combine_id(undefined | http_uri(), + undefined | binary()) -> http_uri(). combine_id(Id, undefined) -> Id; combine_id(Id, RefBin) -> From 9ba37022130e789a40c827690ce95567980d85a1 Mon Sep 17 00:00:00 2001 From: Anton Belyaev Date: Mon, 10 Apr 2017 11:20:24 +0300 Subject: [PATCH 05/18] change validate_ref to validate_definition api --- README.md | 37 +++++++++++++++++ src/jesse.erl | 72 +++++++++++++++++----------------- src/jesse_schema_validator.erl | 27 ++++++++----- 3 files changed, 89 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index ad1e556e..03eb073e 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,38 @@ ok [<<"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: @@ -245,6 +277,11 @@ the given schema), one should use 'default_schema_ver' option when call 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` diff --git a/src/jesse.erl b/src/jesse.erl index 32fc5f1b..2fca225f 100644 --- a/src/jesse.erl +++ b/src/jesse.erl @@ -33,8 +33,8 @@ , load_schemas/3 , validate/2 , validate/3 - , validate_ref/2 - , validate_ref/3 + , validate_definition/3 + , validate_definition/4 , validate_with_schema/2 , validate_with_schema/3 ]). @@ -152,30 +152,40 @@ validate(Schema, Data, Options) -> throw:Error -> {error, Error} end. -%% @doc Equivalent to {@link validate/2} where `Ref' is an absolute $ref -%% which base Uri is a schema key in the internal storage. --spec validate_ref( Ref :: string() - , Data :: json_term() | binary() - ) -> {ok, json_term()} - | jesse_error:error() - | jesse_database:error(). -validate_ref(Ref, Data) -> - validate_ref(Ref, Data, []). - -%% @doc Equivalent to {@link validate/3} where `Ref' is an absolute $ref -%% which base Uri is a schema key in the internal storage. --spec validate_ref( Ref :: string() - , Data :: json_term() | binary() - , Options :: [{Key :: atom(), Data :: any()}] - ) -> {ok, json_term()} - | jesse_error:error() - | jesse_database:error(). -validate_ref(Ref, Data, Options) -> +%% @doc Equivalent to {@link validate_definition/4} where `Options' is an empty list. +-spec validate_definition( Definition :: string() + , Schema :: json_term() | binary() + , Data :: json_term() | binary() + ) -> {ok, json_term()} + | jesse_error:error(). +validate_definition(Definition, Schema, Data) -> + validate_definition(Definition, Schema, Data, []). + +%% @doc Validates json `Data' agains the given `Definition' in the given +%% schema `Schema', using `Options'. +%% If the given json is valid, then it is returned to the caller, otherwise +%% an error with an appropriate error reason is returned. If the `parser_fun' +%% option is provided, then both `Schema' and `Data' are considered to be a +%% binary string, so `parser_fun' is used to convert both binary strings to a +%% supported internal representation of json. +%% If `parser_fun' is not provided, then both `Schema' and `Data' are considered +%% to already be a supported internal representation of json. +-spec validate_definition( Definition :: string() + , Schema :: json_term() | binary() + , Data :: json_term() | binary() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> {ok, json_term()} + | jesse_error:error(). +validate_definition(Defintion, Schema, Data, Options) -> try - ParserFun = proplists:get_value(parser_fun, Options, fun(X) -> X end), - ParsedData = try_parse(data, ParserFun, Data), - {JsonSchema, LocalRef} = parse_ref(Ref), - jesse_schema_validator:validate_ref(LocalRef, JsonSchema, ParsedData, Options) + ParserFun = proplists:get_value(parser_fun, Options, fun(X) -> X end), + ParsedSchema = try_parse(schema, ParserFun, Schema), + ParsedData = try_parse(data, ParserFun, Data), + jesse_schema_validator:validate_definition( Defintion + , ParsedSchema + , ParsedData + , Options + ) catch throw:Error -> {error, Error} end. @@ -225,15 +235,3 @@ try_parse(Type, ParserFun, JsonBin) -> schema -> throw({schema_error, {parse_error, Error}}) end end. - -%% @doc Loads schema from the internal storage according to the base uri -%% in `Ref' and prepares local ref. -%% @private -parse_ref(Ref) -> - [BaseURI, LocalRef] = re:split( Ref - , <<$#>> - , [{return, binary}, unicode] - ), - JsonSchema = jesse_database:load(binary_to_list(BaseURI)), - LocalRef1 = <<$#, LocalRef/binary>>, - {JsonSchema, LocalRef1}. diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index 58e5118f..ccbc8930 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -25,7 +25,7 @@ %% API -export([ validate/3 - , validate_ref/4 + , validate_definition/4 , validate_with_state/3 ]). @@ -56,18 +56,18 @@ validate(JsonSchema, Value, Options) -> NewState = validate_with_state(JsonSchema, Value, State), {result(NewState), Value}. -%% @doc Validates json `Data' against `Ref' in `JsonSchema' with `Options'. +%% @doc Validates json `Data' against `Definition' in `JsonSchema' with `Options'. %% If the given json is valid, then it is returned to the caller as is, %% otherwise an exception will be thrown. --spec validate_ref( Ref :: binary() - , JsonSchema :: jesse:json_term() - , Data :: jesse:json_term() - , Options :: [{Key :: atom(), Data :: any()}] - ) -> {ok, jesse:json_term()} - | no_return(). -validate_ref(Ref, JsonSchema, Value, Options) -> +-spec validate_definition( Definition :: string() + , JsonSchema :: jesse:json_term() + , Data :: jesse:json_term() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> {ok, jesse:json_term()} + | no_return(). +validate_definition(Defintion, JsonSchema, Value, Options) -> State = jesse_state:new(JsonSchema, Options), - Schema = [{<<"$ref">>, Ref}], + Schema = make_definition_ref(Defintion), NewState = validate_with_state(Schema, Value, State), {result(NewState), Value}. @@ -133,3 +133,10 @@ run_validator(Validator, Value, [Attr | Attrs], State0) -> , State0 ), run_validator(Validator, Value, Attrs, State). + +%% @doc Makes a $ref schema object pointing to the given `Definition' +%% in schema defintions. +%% @private +make_definition_ref(Definition) -> + Definition1 = list_to_binary(Definition), + [{<<"$ref">>, <<"#/definitions/", Definition1/binary>>}]. From 39105922d1ce5834383d8e8aa877c60319b9834a Mon Sep 17 00:00:00 2001 From: Anton Belyaev Date: Sun, 21 May 2017 17:41:46 +0300 Subject: [PATCH 06/18] Add init opts to custom validator --- src/jesse_schema_validator.erl | 8 +++++++- src/jesse_state.erl | 21 ++++++++++++++++----- src/jesse_validator_draft3.erl | 6 +++--- src/jesse_validator_draft4.erl | 6 +++--- test/jesse_tests_generic_SUITE.erl | 2 +- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index ccbc8930..1d244a92 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -40,7 +40,13 @@ Attr :: {binary(), jesse:json_term()}, State :: jesse_state:state(). --callback init_state() -> any() | undefined. +-callback init_state(Opts :: jesse_state:validator_opts()) -> + validator_state(). + +-type validator_state() :: any(). + +-export_type([ validator_state/0 + ]). %%% API %% @doc Validates json `Data' against `JsonSchema' with `Options'. diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 6d071c80..d0a0df3b 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -47,6 +47,7 @@ ]). -export_type([ state/0 + , validator_opts/0 ]). %% Includes @@ -81,6 +82,8 @@ -opaque state() :: #state{}. +-type validator_opts() :: any(). + %%% API %% @doc Adds `Property' to the `current_path' in `State'. -spec add_to_path(State :: state(), @@ -165,7 +168,13 @@ new(JsonSchema, Options) -> , Options , undefined ), - ValidatorState = init_validator_state(Validator), + ValidatorOpts = proplists:get_value( validator_opts + , Options + , undefined + ), + ValidatorState = init_validator_state( Validator + , ValidatorOpts + ), LoaderFun = proplists:get_value( schema_loader_fun , Options , ?default_schema_loader_fun @@ -269,11 +278,13 @@ undo_resolve_ref(RefState, OriginalState) -> %% @doc Init custom validator state. %% @private --spec init_validator_state(Validator :: module() | undefined) -> any() | undefined. -init_validator_state(undefined) -> +-spec init_validator_state( Validator :: module() | undefined + , Opts :: validator_opts() + ) -> jesse_schema_validator:validator_state(). +init_validator_state(undefined, _) -> undefined; -init_validator_state(Validator) -> - Validator:init_state(). +init_validator_state(Validator, Opts) -> + Validator:init_state(Opts). %% @doc Retrieve a specific part of a schema %% @private diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 4d2c8555..330893d7 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -25,7 +25,7 @@ -behaviour(jesse_schema_validator). %% API --export([ init_state/0 +-export([ init_state/1 , check_value/3 ]). @@ -57,8 +57,8 @@ %%% API %% @doc Behaviour callback. Custom state is not used by this validator. --spec init_state() -> undefined. -init_state() -> +-spec init_state(_) -> undefined. +init_state(_) -> undefined. %% @doc Validates the value `Value' against the attributes diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 20093abe..d9260cc3 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -25,7 +25,7 @@ -behaviour(jesse_schema_validator). %% API --export([ init_state/0 +-export([ init_state/1 , check_value/3 ]). @@ -74,8 +74,8 @@ %%% API %% @doc Behaviour callback. Custom state is not used by this validator. --spec init_state() -> undefined. -init_state() -> +-spec init_state(_) -> undefined. +init_state(_) -> undefined. %% @doc Validates the value `Value' against the attributes diff --git a/test/jesse_tests_generic_SUITE.erl b/test/jesse_tests_generic_SUITE.erl index c332b3f9..f755f543 100644 --- a/test/jesse_tests_generic_SUITE.erl +++ b/test/jesse_tests_generic_SUITE.erl @@ -56,7 +56,7 @@ init_per_suite(Config) -> end_per_suite(_Config) -> inets:stop(). -init_state() -> +init_state(_) -> 0. check_value(Value, {<<"customDef">>, Property}, State0) -> From 7640f4739374147d2c202a2d39aebbffaff34b63 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 16 Mar 2020 17:44:20 +0300 Subject: [PATCH 07/18] Ucp support in regexes (#6) --- src/jesse_validator_draft3.erl | 2 +- src/jesse_validator_draft4.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 1357a23e..1e835b6c 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -768,7 +768,7 @@ check_unique_items(Value, true, State) -> %% specification from ECMA 262/Perl 5 %% @private check_pattern(Value, Pattern, State) -> - case re:run(Value, Pattern, [{capture, none}, unicode]) of + case re:run(Value, Pattern, [{capture, none}, unicode, ucp]) of match -> State; nomatch -> handle_data_invalid(?no_match, Value, State) diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 1d8a6aee..5a2a6545 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -844,7 +844,7 @@ check_unique_items(Value, true, State) -> %% anchored. %% @private check_pattern(Value, Pattern, State) -> - case re:run(Value, Pattern, [{capture, none}, unicode]) of + case re:run(Value, Pattern, [{capture, none}, unicode, ucp]) of match -> State; nomatch -> handle_data_invalid(?no_match, Value, State) From f583a3bc4f7caea3192e446cc43b1710d1530005 Mon Sep 17 00:00:00 2001 From: Sergei Shuvatov Date: Thu, 19 Mar 2020 13:16:24 +0300 Subject: [PATCH 08/18] Use github actions (#8) --- .github/workflows/erlang.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/erlang.yml diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml new file mode 100644 index 00000000..618b8c88 --- /dev/null +++ b/.github/workflows/erlang.yml @@ -0,0 +1,41 @@ +name: Erlang CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + ERLANG_VERSION: 22.3 + +jobs: + + build: + + runs-on: ubuntu-latest + + container: + image: erlang:22.3 + + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + submodules: recursive + - name: Restore rebar3 chache + uses: actions/cache@v1 + with: + path: ~/.cache/rebar3 + key: rebar3-${{ env.ERLANG_VERSION }} + - name: Get deps + run: rebar3 get-deps + - name: Compile + run: rebar3 compile + - name: Xref + run: rebar3 xref + - name: Dialyzer + run: rebar3 dialyzer + - name: Run tests + # rebar3 doesn't set TEST_DIR env var used in tests + run: TEST_DIR=`readlink -f test` rebar3 do eunit, ct From a21da0609e446f328c01b1a72191cda26a8969a4 Mon Sep 17 00:00:00 2001 From: Sergei Shuvatov Date: Thu, 19 Mar 2020 13:22:26 +0300 Subject: [PATCH 09/18] MSPF-532: get rid of rfc3339 library (#7) --- rebar.config | 7 ------- src/jesse.app.src | 1 - src/jesse_cli.erl | 1 - src/jesse_validator_draft4.erl | 10 ++++++---- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/rebar.config b/rebar.config index a73756af..c01a5bbd 100644 --- a/rebar.config +++ b/rebar.config @@ -32,12 +32,6 @@ , {tag, "2.9.0"} } } - , { rfc3339 - , ".*" - , { git, "git://github.com/andreineculau/rfc3339.git" - , {tag, "0.2.1-r15-compat"} - } - } ]}. {escript_name, "bin/jesse"}. {escript_emu_args, "%%! -noinput\n"}. @@ -45,5 +39,4 @@ , kernel , stdlib , jsx - , rfc3339 ]}. diff --git a/src/jesse.app.src b/src/jesse.app.src index 1b44913e..091bae5f 100644 --- a/src/jesse.app.src +++ b/src/jesse.app.src @@ -6,7 +6,6 @@ , {applications, [ kernel , stdlib , jsx - , rfc3339 ]} , {maintainers, [ "for-GET/jesse" ]} diff --git a/src/jesse_cli.erl b/src/jesse_cli.erl index 0d7d0e15..61907479 100644 --- a/src/jesse_cli.erl +++ b/src/jesse_cli.erl @@ -89,7 +89,6 @@ jesse_run(JsonInstance, Schema, Schemata) -> %% nor application:ensure_started(_) %% in order to maintain compatibility with R16B01 and lower ok = ensure_started(jsx), - ok = ensure_started(rfc3339), ok = ensure_started(jesse), ok = add_schemata(Schemata), {ok, JsonInstanceBinary} = file:read_file(JsonInstance), diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index 5a2a6545..9f17fe62 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -1341,9 +1341,11 @@ remove_last_from_path(State) -> %% @private valid_datetime(DateTimeBin) -> - case rfc3339:parse(DateTimeBin) of - {ok, _} -> - true; - _ -> + DateTimeStr = erlang:binary_to_list(DateTimeBin), + try calendar:rfc3339_to_system_time(DateTimeStr) of + Seconds when is_integer(Seconds) -> + true + catch + error:_ -> false end. From 600cc8318c685de60a1ceee055b3f69bc884800d Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 29 Apr 2020 16:37:36 +0300 Subject: [PATCH 10/18] Add jesse:validate_local_ref, implement validate_defition over it (#9) --- src/jesse.erl | 40 ++++++++++++++++++++++++++++++++++ src/jesse_schema_validator.erl | 32 +++++++++++++++++++-------- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/jesse.erl b/src/jesse.erl index 0a59a052..77aeb2f8 100644 --- a/src/jesse.erl +++ b/src/jesse.erl @@ -35,6 +35,8 @@ , validate/3 , validate_definition/3 , validate_definition/4 + , validate_local_ref/3 + , validate_local_ref/4 , validate_with_schema/2 , validate_with_schema/3 ]). @@ -255,6 +257,44 @@ validate_definition(Defintion, Schema, Data, Options) -> throw:Error -> {error, Error} end. +%% @doc Equivalent to {@link validate_local_ref/4} where `Options' is an empty list. +-spec validate_local_ref( RefPath :: string() + , Schema :: json_term() | binary() + , Data :: json_term() | binary() + ) -> {ok, json_term()} + | jesse_error:error(). +validate_local_ref(RefPath, Schema, Data) -> + validate_local_ref(RefPath, Schema, Data, []). + +%% @doc Validates json `Data' agains the given definition path 'RefPath' in the given +%% schema `Schema', using `Options'. +%% If the given json is valid, then it is returned to the caller, otherwise +%% an error with an appropriate error reason is returned. If the `parser_fun' +%% option is provided, then both `Schema' and `Data' are considered to be a +%% binary string, so `parser_fun' is used to convert both binary strings to a +%% supported internal representation of json. +%% If `parser_fun' is not provided, then both `Schema' and `Data' are considered +%% to already be a supported internal representation of json. +-spec validate_local_ref( RefPath :: string() + , Schema :: json_term() | binary() + , Data :: json_term() | binary() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> {ok, json_term()} + | jesse_error:error(). +validate_local_ref(RefPath, Schema, Data, Options) -> + try + ParserFun = proplists:get_value(parser_fun, Options, fun(X) -> X end), + ParsedSchema = try_parse(schema, ParserFun, Schema), + ParsedData = try_parse(data, ParserFun, Data), + jesse_schema_validator:validate_local_ref( RefPath + , ParsedSchema + , ParsedData + , Options + ) + catch + throw:Error -> {error, Error} + end. + %% @doc Equivalent to {@link validate_with_schema/3} where `Options' %% is an empty list. -spec validate_with_schema( Schema :: schema() | binary() diff --git a/src/jesse_schema_validator.erl b/src/jesse_schema_validator.erl index 1d244a92..311bdc48 100644 --- a/src/jesse_schema_validator.erl +++ b/src/jesse_schema_validator.erl @@ -26,6 +26,7 @@ %% API -export([ validate/3 , validate_definition/4 + , validate_local_ref/4 , validate_with_state/3 ]). @@ -62,7 +63,7 @@ validate(JsonSchema, Value, Options) -> NewState = validate_with_state(JsonSchema, Value, State), {result(NewState), Value}. -%% @doc Validates json `Data' against `Definition' in `JsonSchema' with `Options'. +%% @doc Validates json `Data' against a given $ref path in `JsonSchema' with `Options'. %% If the given json is valid, then it is returned to the caller as is, %% otherwise an exception will be thrown. -spec validate_definition( Definition :: string() @@ -71,10 +72,23 @@ validate(JsonSchema, Value, Options) -> , Options :: [{Key :: atom(), Data :: any()}] ) -> {ok, jesse:json_term()} | no_return(). -validate_definition(Defintion, JsonSchema, Value, Options) -> - State = jesse_state:new(JsonSchema, Options), - Schema = make_definition_ref(Defintion), - NewState = validate_with_state(Schema, Value, State), +validate_definition(Definition, JsonSchema, Value, Options) -> + RefPath = "#/definitions/" ++ Definition, + validate_local_ref(RefPath, JsonSchema, Value, Options). + +%% @doc Validates json `Data' against `Definition' in `JsonSchema' with `Options'. +%% If the given json is valid, then it is returned to the caller as is, +%% otherwise an exception will be thrown. +-spec validate_local_ref( RefPath :: string() + , JsonSchema :: jesse:json_term() + , Data :: jesse:json_term() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> {ok, jesse:json_term()} + | no_return(). +validate_local_ref(RefPath, JsonSchema, Value, Options) -> + State = jesse_state:new(JsonSchema, Options), + Ref = make_ref(RefPath), + NewState = validate_with_state(Ref, Value, State), {result(NewState), Value}. %% @doc Validates json `Data' against `JsonSchema' with `State'. @@ -140,9 +154,9 @@ run_validator(Validator, Value, [Attr | Attrs], State0) -> ), run_validator(Validator, Value, Attrs, State). -%% @doc Makes a $ref schema object pointing to the given `Definition' +%% @doc Makes a $ref schema object pointing to the given path %% in schema defintions. %% @private -make_definition_ref(Definition) -> - Definition1 = list_to_binary(Definition), - [{<<"$ref">>, <<"#/definitions/", Definition1/binary>>}]. +make_ref(RefPath) -> + RefPath1 = list_to_binary(RefPath), + [{<<"$ref">>, RefPath1}]. From 5bb91aa538210cecfc7a51b50d7ae4d7a3a93262 Mon Sep 17 00:00:00 2001 From: Shunichi Shinohara Date: Fri, 28 Feb 2020 17:15:14 +0900 Subject: [PATCH 11/18] Avoid deprecated http_uri functions for OTP 21+ --- rebar.config | 1 + src/jesse_json_path.erl | 15 ++++++++++++++- src/jesse_state.erl | 39 +++++++++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/rebar.config b/rebar.config index c01a5bbd..a426f568 100644 --- a/rebar.config +++ b/rebar.config @@ -3,6 +3,7 @@ , "deps" ]}. {erl_opts, [ {platform_define, "^R[0-9]+", erlang_deprecated_types} + , {platform_define, "^(R[0-9]+|17|18|19|20)", http_uri_depricated_functions} %% , warn_export_all , warn_export_vars , warn_obsolete_guard diff --git a/src/jesse_json_path.erl b/src/jesse_json_path.erl index 1804634d..9d012717 100644 --- a/src/jesse_json_path.erl +++ b/src/jesse_json_path.erl @@ -293,7 +293,7 @@ normalize(K, undefined) -> -spec parse_json_pointer_token(Token :: string()) -> binary(). parse_json_pointer_token(Token) -> - DecodedToken = unicode:characters_to_binary(http_uri:decode(Token)), + DecodedToken = unicode:characters_to_binary(hex_decode(Token)), lists:foldl( fun({From, To}, T) -> binary:replace(T, From, To) end @@ -302,3 +302,16 @@ parse_json_pointer_token(Token) -> , {<<"~1">>, <<"/">>} ] ). + +%% @private +hex_decode([$%, Hex1, Hex2 | Rest]) -> + [hex2dec(Hex1)*16 + hex2dec(Hex2) | hex_decode(Rest)]; +hex_decode([First | Rest]) -> + [First | hex_decode(Rest)]; +hex_decode([]) -> + []. + +%% @private +hex2dec(X) when (X>=$0) andalso (X=<$9) -> X-$0; +hex2dec(X) when (X>=$A) andalso (X=<$F) -> X-$A+10; +hex2dec(X) when (X>=$a) andalso (X=<$f) -> X-$a+10. diff --git a/src/jesse_state.erl b/src/jesse_state.erl index b14bd126..40870ef6 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -329,16 +329,11 @@ load_local_schema(Schema, [Key | Keys]) -> combine_id(Id, undefined) -> Id; combine_id(Id, RefBin) -> - Ref = unicode:characters_to_list(RefBin), - case http_uri:parse(Ref) of - %% Absolute http/s: - {ok, _} -> - Ref; - %% Absolute file: - {error, {no_default_port, file, Ref}} -> + case parse_ref(RefBin) of + {absolute, Ref} -> Ref; %% Relative - _ -> + {relative, Ref} -> combine_relative_id(Id, Ref) end. @@ -433,3 +428,31 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) -> _C:_E -> ?not_found end. + +%% @private +-ifdef(http_uri_depricated_functions). +parse_ref(RefBin) -> + Ref = unicode:characters_to_list(RefBin), + case http_uri:parse(Ref) of + %% Absolute http/s: + {ok, _} -> + {absolute, Ref}; + %% Absolute file: + {error, {no_default_port, file, Ref}} -> + {absolute, Ref}; + %% Relative + _ -> + {relative, Ref} + end. +-else. +parse_ref(RefBin) -> + Ref = unicode:characters_to_list(RefBin), + case uri_string:parse(Ref) of + %% Absolute + #{scheme := _} -> + {absolute, Ref}; + %% Relative + _ -> + {relative, Ref} + end. +-endif. From 2a0fa894bdd6bea553a7b17e734525956b13b321 Mon Sep 17 00:00:00 2001 From: Shunichi Shinohara Date: Wed, 4 Mar 2020 10:34:15 +0900 Subject: [PATCH 12/18] Cosmetics --- src/jesse_json_path.erl | 3 +++ src/jesse_state.erl | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jesse_json_path.erl b/src/jesse_json_path.erl index 9d012717..6ae53dbc 100644 --- a/src/jesse_json_path.erl +++ b/src/jesse_json_path.erl @@ -303,6 +303,9 @@ parse_json_pointer_token(Token) -> ] ). +%% This implementation is based on http_uri:decode(), because there is +%% no direct alternative inin uri_string module. +%% cf. http://erlang.org/pipermail/erlang-questions/2020-March/099207.html %% @private hex_decode([$%, Hex1, Hex2 | Rest]) -> [hex2dec(Hex1)*16 + hex2dec(Hex2) | hex_decode(Rest)]; diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 40870ef6..2783bcbc 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -440,7 +440,6 @@ parse_ref(RefBin) -> %% Absolute file: {error, {no_default_port, file, Ref}} -> {absolute, Ref}; - %% Relative _ -> {relative, Ref} end. @@ -448,10 +447,8 @@ parse_ref(RefBin) -> parse_ref(RefBin) -> Ref = unicode:characters_to_list(RefBin), case uri_string:parse(Ref) of - %% Absolute #{scheme := _} -> {absolute, Ref}; - %% Relative _ -> {relative, Ref} end. From 287efa06c41294d9625428c77fd87474e7ac45a2 Mon Sep 17 00:00:00 2001 From: Shunichi Shinohara Date: Wed, 4 Mar 2020 10:36:55 +0900 Subject: [PATCH 13/18] Use OTP_RELEASE macro --- rebar.config | 1 - src/jesse_state.erl | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/rebar.config b/rebar.config index a426f568..c01a5bbd 100644 --- a/rebar.config +++ b/rebar.config @@ -3,7 +3,6 @@ , "deps" ]}. {erl_opts, [ {platform_define, "^R[0-9]+", erlang_deprecated_types} - , {platform_define, "^(R[0-9]+|17|18|19|20)", http_uri_depricated_functions} %% , warn_export_all , warn_export_vars , warn_obsolete_guard diff --git a/src/jesse_state.erl b/src/jesse_state.erl index 2783bcbc..ce76ebb2 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -430,15 +430,11 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) -> end. %% @private --ifdef(http_uri_depricated_functions). +-ifdef(OTP_RELEASE). %% OTP 21+ parse_ref(RefBin) -> Ref = unicode:characters_to_list(RefBin), - case http_uri:parse(Ref) of - %% Absolute http/s: - {ok, _} -> - {absolute, Ref}; - %% Absolute file: - {error, {no_default_port, file, Ref}} -> + case uri_string:parse(Ref) of + #{scheme := _} -> {absolute, Ref}; _ -> {relative, Ref} @@ -446,8 +442,12 @@ parse_ref(RefBin) -> -else. parse_ref(RefBin) -> Ref = unicode:characters_to_list(RefBin), - case uri_string:parse(Ref) of - #{scheme := _} -> + case http_uri:parse(Ref) of + %% Absolute http/s: + {ok, _} -> + {absolute, Ref}; + %% Absolute file: + {error, {no_default_port, file, Ref}} -> {absolute, Ref}; _ -> {relative, Ref} From ba495bc167cc5e82fe6ec986c4028a36d0e4f061 Mon Sep 17 00:00:00 2001 From: dinama Date: Tue, 12 Jan 2021 13:52:55 +0300 Subject: [PATCH 14/18] erlang.yml: erlang:23.2 --- .github/workflows/erlang.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index 618b8c88..89ceecb2 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -7,7 +7,7 @@ on: branches: [ master ] env: - ERLANG_VERSION: 22.3 + ERLANG_VERSION: 23.2 jobs: @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest container: - image: erlang:22.3 + image: erlang:23.2 steps: - name: Checkout From bbb58fc05e2f0b8caaed77e1a205b1accfa2546c Mon Sep 17 00:00:00 2001 From: dinama Date: Tue, 12 Jan 2021 21:51:42 +0300 Subject: [PATCH 15/18] Update src/jesse_json_path.erl Co-authored-by: Sergey Yelin --- src/jesse_json_path.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jesse_json_path.erl b/src/jesse_json_path.erl index 6ae53dbc..a70d4842 100644 --- a/src/jesse_json_path.erl +++ b/src/jesse_json_path.erl @@ -304,7 +304,7 @@ parse_json_pointer_token(Token) -> ). %% This implementation is based on http_uri:decode(), because there is -%% no direct alternative inin uri_string module. +%% no direct alternative in uri_string module. %% cf. http://erlang.org/pipermail/erlang-questions/2020-March/099207.html %% @private hex_decode([$%, Hex1, Hex2 | Rest]) -> From 095354392294252a9f748e10db218561459e733a Mon Sep 17 00:00:00 2001 From: dinama Date: Thu, 19 Aug 2021 09:44:22 +0300 Subject: [PATCH 16/18] +fix after merge --- .gitignore | 1 + rebar.config | 5 ----- src/jesse_validator_draft4.erl | 8 ++++---- test/jesse_tests_generic_SUITE.erl | 14 +++++++++----- .../extra}/customValidator.json | 0 5 files changed, 14 insertions(+), 14 deletions(-) rename test/{Generic-Test-Suite => jesse_tests_generic_SUITE_data/extra}/customValidator.json (100%) diff --git a/.gitignore b/.gitignore index 3e30d8c8..1e247025 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ /rebar3.crashdump /rebar.lock /test/*.beam +/deps/ \ No newline at end of file diff --git a/rebar.config b/rebar.config index 71d7f03d..78050074 100644 --- a/rebar.config +++ b/rebar.config @@ -30,11 +30,6 @@ {escript_name, jesse}. {escript_emu_args, "%%! -noinput\n"}. -{escript_incl_apps, [ erts - , kernel - , stdlib - , jsx - ]}. { elvis , [#{ dirs => ["src", "test"] diff --git a/src/jesse_validator_draft4.erl b/src/jesse_validator_draft4.erl index b577ce34..59c78019 100644 --- a/src/jesse_validator_draft4.erl +++ b/src/jesse_validator_draft4.erl @@ -25,8 +25,8 @@ -behaviour(jesse_schema_validator). %% API --export([ init_state/1 - , check_value/3 +-export([ check_value/3 + , init_state/1 ]). %% Includes @@ -236,8 +236,8 @@ check_value(Value, {?NOT, Schema}, State) -> check_not(Value, Schema, State); check_value(Value, {?REF, RefSchemaURI}, State) -> validate_ref(Value, RefSchemaURI, State); -check_value(_Value, _Attr, State) -> - State. +check_value(Value, _Attr, State) -> + maybe_external_check_value(Value, State). %%% Internal functions %% @doc Adds Property to the current path and checks the value diff --git a/test/jesse_tests_generic_SUITE.erl b/test/jesse_tests_generic_SUITE.erl index f755f543..2b869f0e 100644 --- a/test/jesse_tests_generic_SUITE.erl +++ b/test/jesse_tests_generic_SUITE.erl @@ -25,6 +25,7 @@ -behaviour(jesse_schema_validator). -compile([ export_all + , nowarn_export_all ]). -define(EXCLUDED_FUNS, [ module_info @@ -38,7 +39,7 @@ -include_lib("common_test/include/ct.hrl"). --import(jesse_tests_util, [ get_tests/2 +-import(jesse_tests_util, [ get_tests/3 , do_test/2 ]). @@ -47,14 +48,17 @@ all() -> [F || {F, _} <- Exports, not lists:member(F, ?EXCLUDED_FUNS)]. init_per_suite(Config) -> - inets:start(), - get_tests( "Generic-Test-Suite" + {ok, _} = application:ensure_all_started(jesse), + get_tests( "standard" , <<"http://json-schema.org/draft-04/schema#">> - ) + , Config) + ++ get_tests( "extra" + , <<"http://json-schema.org/draft-04/schema#">> + , Config) ++ Config. end_per_suite(_Config) -> - inets:stop(). + ok. init_state(_) -> 0. diff --git a/test/Generic-Test-Suite/customValidator.json b/test/jesse_tests_generic_SUITE_data/extra/customValidator.json similarity index 100% rename from test/Generic-Test-Suite/customValidator.json rename to test/jesse_tests_generic_SUITE_data/extra/customValidator.json From fd4eee89ddbbde9b8eed425903425ffc89581ea6 Mon Sep 17 00:00:00 2001 From: dinama Date: Thu, 19 Aug 2021 15:07:01 +0300 Subject: [PATCH 17/18] MSPF-532: avoid rfc3339 --- rebar.config | 1 - src/jesse.app.src | 1 - 2 files changed, 2 deletions(-) diff --git a/rebar.config b/rebar.config index 78050074..cf935fe7 100644 --- a/rebar.config +++ b/rebar.config @@ -21,7 +21,6 @@ ]}. { deps , [ {jsx, "3.1.0"} - , {rfc3339, "0.2.2"} ]}. { project_plugins diff --git a/src/jesse.app.src b/src/jesse.app.src index d79e7ea7..3f02d5e1 100644 --- a/src/jesse.app.src +++ b/src/jesse.app.src @@ -12,7 +12,6 @@ , ssl , inets , jsx - , rfc3339 ]} , {licenses, [ "Apache 2.0" ]} From 6ee802adcdb22e12994f1dd085dccf92005d8f35 Mon Sep 17 00:00:00 2001 From: dinama Date: Fri, 20 Aug 2021 06:43:16 +0300 Subject: [PATCH 18/18] +avoid some diffs +avoid ci/erlang.yaml --- .github/workflows/erlang.yml | 41 ------------------------------ src/jesse_validator_draft3.erl | 13 +++++++--- test/jesse_tests_generic_SUITE.erl | 5 +--- 3 files changed, 11 insertions(+), 48 deletions(-) delete mode 100644 .github/workflows/erlang.yml diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml deleted file mode 100644 index 89ceecb2..00000000 --- a/.github/workflows/erlang.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Erlang CI - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -env: - ERLANG_VERSION: 23.2 - -jobs: - - build: - - runs-on: ubuntu-latest - - container: - image: erlang:23.2 - - steps: - - name: Checkout - uses: actions/checkout@v1 - with: - submodules: recursive - - name: Restore rebar3 chache - uses: actions/cache@v1 - with: - path: ~/.cache/rebar3 - key: rebar3-${{ env.ERLANG_VERSION }} - - name: Get deps - run: rebar3 get-deps - - name: Compile - run: rebar3 compile - - name: Xref - run: rebar3 xref - - name: Dialyzer - run: rebar3 dialyzer - - name: Run tests - # rebar3 doesn't set TEST_DIR env var used in tests - run: TEST_DIR=`readlink -f test` rebar3 do eunit, ct diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index c2517e86..1ab698db 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -203,8 +203,8 @@ check_value(Value, {?EXTENDS, Extends}, State) -> check_extends(Value, Extends, State); check_value(Value, {?REF, RefSchemaURI}, State) -> validate_ref(Value, RefSchemaURI, State); -check_value(_Value, _Attr, State) -> - State. +check_value(Value, _Attr, State) -> + maybe_external_check_value(Value, State). %%% Internal functions %% @doc Adds Property to the current path and checks the value @@ -1031,4 +1031,11 @@ add_to_path(State, Property) -> remove_last_from_path(State) -> jesse_state:remove_last_from_path(State). - +%% @private +maybe_external_check_value(Value, State) -> + case jesse_state:get_external_validator(State) of + undefined -> + State; + Fun -> + Fun(Value, State) + end. diff --git a/test/jesse_tests_generic_SUITE.erl b/test/jesse_tests_generic_SUITE.erl index 2b869f0e..ae8e390b 100644 --- a/test/jesse_tests_generic_SUITE.erl +++ b/test/jesse_tests_generic_SUITE.erl @@ -49,12 +49,9 @@ all() -> init_per_suite(Config) -> {ok, _} = application:ensure_all_started(jesse), - get_tests( "standard" + get_tests( "extra" , <<"http://json-schema.org/draft-04/schema#">> , Config) - ++ get_tests( "extra" - , <<"http://json-schema.org/draft-04/schema#">> - , Config) ++ Config. end_per_suite(_Config) ->