diff --git a/src/jesse_state.erl b/src/jesse_state.erl index dfd37cf..87a3efe 100644 --- a/src/jesse_state.erl +++ b/src/jesse_state.erl @@ -27,6 +27,7 @@ , get_allowed_errors/1 , get_current_path/1 , get_current_schema/1 + , get_original_schema/1 , get_default_schema_ver/1 , get_error_handler/1 , get_error_list/1 @@ -35,11 +36,17 @@ , set_allowed_errors/2 , set_current_schema/2 , set_error_list/2 + , find_schema/2 ]). -export_type([ state/0 ]). +%% Includes +-include("jesse_schema_validator.hrl"). + +-define(schema_loader_fun, fun jesse_database:read/1). + %% Internal datastructures -record( state , { original_schema :: jesse:json_term() @@ -49,14 +56,12 @@ , error_list :: list() , error_handler :: fun((#state{}) -> list() | no_return()) , default_schema_ver :: atom() + , schema_loader_fun :: fun((binary()) -> {ok, jesse:json_term()} | jesse:json_term() | ?not_found) } ). -opaque state() :: #state{}. -%% Includes --include("jesse_schema_validator.hrl"). - %%% API %% @doc Adds `Property' to the `current_path' in `State'. -spec add_to_path(State :: state(), Property :: binary()) -> state(). @@ -79,6 +84,11 @@ get_current_path(#state{current_path = CurrentPath}) -> get_current_schema(#state{current_schema = CurrentSchema}) -> CurrentSchema. +%% @doc Getter for `original_schema'. +-spec get_original_schema(State :: state()) -> jesse:json_term(). +get_original_schema(#state{original_schema = OriginalSchema}) -> + OriginalSchema. + %% @doc Getter for `default_schema_ver'. -spec get_default_schema_ver(State :: state()) -> binary(). get_default_schema_ver(#state{default_schema_ver = SchemaVer}) -> @@ -113,6 +123,10 @@ new(JsonSchema, Options) -> , Options , ?default_schema_ver ), + LoaderFun = proplists:get_value( schema_loader_fun + , Options + , ?schema_loader_fun + ), #state{ current_schema = JsonSchema , current_path = [] , original_schema = JsonSchema @@ -120,6 +134,7 @@ new(JsonSchema, Options) -> , error_list = [] , error_handler = ErrorHandler , default_schema_ver = DefaultSchemaVer + , schema_loader_fun = LoaderFun }. %% @doc Removes the last element from `current_path' in `State'. @@ -146,6 +161,20 @@ set_current_schema(State, NewSchema) -> set_error_list(State, ErrorList) -> State#state{error_list = ErrorList}. +%% @doc Find a schema based on URI +-spec find_schema(State :: state(), SchemaURI :: binary()) -> jesse:json_term() | ?not_found. +find_schema(#state{schema_loader_fun=LoaderFun}, SchemaURI) -> + try LoaderFun(SchemaURI) of + {ok, Schema} -> Schema; + Schema -> + case jesse_lib:is_json_object(Schema) of + true -> Schema; + false -> ?not_found + end + catch + _:_ -> ?not_found + end. + %%% Local Variables: %%% erlang-indent-level: 2 %%% End: diff --git a/src/jesse_validator_draft3.erl b/src/jesse_validator_draft3.erl index 8938caa..37b6d78 100644 --- a/src/jesse_validator_draft3.erl +++ b/src/jesse_validator_draft3.erl @@ -185,6 +185,9 @@ check_value(Value, [{?DISALLOW, Disallow} | Attrs], State) -> check_value(Value, [{?EXTENDS, Extends} | Attrs], State) -> NewState = check_extends(Value, Extends, State), check_value(Value, Attrs, NewState); +check_value(Value, [{?_REF, RefSchemaURI} | Attrs], State) -> + NewState = check_ref(Value, RefSchemaURI, State), + check_value(Value, Attrs, NewState); check_value(_Value, [], State) -> State; check_value(Value, [_Attr | Attrs], State) -> @@ -861,6 +864,35 @@ check_extends_array(Value, Extends, State) -> , Extends ). +%% @private +check_ref(Value, <<"#", LocalPath/binary>> = RefSchemaURI, State) -> + Keys = binary:split(LocalPath, <<"/">>, ['global']), + OriginalSchema = jesse_state:get_original_schema(State), + + case local_schema(OriginalSchema, Keys) of + ?not_found -> handle_schema_invalid({'schema_unsupported', RefSchemaURI}, State); + LocalSchema -> check_ref_schema(Value, LocalSchema, State) + end; +check_ref(Value, RefSchemaURI, State) -> + case jesse_state:find_schema(State, RefSchemaURI) of + ?not_found -> handle_schema_invalid({'schema_unsupported', RefSchemaURI}, State); + RefSchema -> check_ref_schema(Value, RefSchema, State) + end. + +%% @private +check_ref_schema(Value, RefSchema, State) -> + TmpState = check_value(Value, unwrap(RefSchema), set_current_schema(State, RefSchema)), + set_current_schema(TmpState, get_current_schema(State)). + +local_schema(Schema, []) -> Schema; +local_schema(Schema, [<<>> | Keys]) -> local_schema(Schema, Keys); +local_schema(Schema, [Key | Keys]) -> + SubSchema = get_value(Key, Schema), + case jesse_lib:is_json_object(SubSchema) of + true -> local_schema(SubSchema, Keys); + false -> ?not_found + end. + %%============================================================================= %% @doc Returns `true' if given values (instance) are equal, otherwise `false' %% is returned. diff --git a/test/jesse_tests_draft3_SUITE.erl b/test/jesse_tests_draft3_SUITE.erl index 995daa7..7c2d4ed 100644 --- a/test/jesse_tests_draft3_SUITE.erl +++ b/test/jesse_tests_draft3_SUITE.erl @@ -43,7 +43,7 @@ , pattern/1 , patternProperties/1 , properties/1 - %% , ref/1 + , ref/1 , required/1 , type/1 , uniqueItems/1 @@ -78,7 +78,7 @@ all() -> , pattern , patternProperties , properties - %% , ref + , ref , required , type , uniqueItems @@ -177,11 +177,10 @@ properties(Config) -> Specs = ?config(Key, Config), ok = run_tests(Specs). -%% not implemented yet -%% ref(Config) -> -%% Key = "ref", -%% Specs = ?config(Key, Config), -%% ok = run_tests(Specs). +ref(Config) -> + Key = "ref", + Specs = ?config(Key, Config), + ok = run_tests(Specs). required(Config) -> Key = "required",