Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default #52

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/jesse.erl
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,20 @@
| ?not_found
).

-type setter_fun() :: fun(( jesse_json_path:path()
, json_term()
, json_term()
) -> json_term())
| undefined.

-type option() :: {allowed_errors, allowed_errors()}
| {default_schema_ver, schema_ver()}
| {error_handler, error_handler()}
| {external_validator, external_validator()}
| {meta_schema_ver, schema_ver()}
| {parser_fun, parser_fun()}
| {schema_loader_fun, schema_loader_fun()}.
| {schema_loader_fun, schema_loader_fun()}
| {setter_fun, setter_fun()}.

-type options() :: [option()].

Expand Down
20 changes: 20 additions & 0 deletions src/jesse_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
-export([ empty_if_not_found/1
, is_array/1
, is_json_object/1
, is_json_object_empty/1
, is_null/1
]).

Expand Down Expand Up @@ -86,3 +87,22 @@ is_null(null) ->
is_null(_Value) ->
false.

%% @doc check if json object is_empty.
-spec is_json_object_empty(Value :: any()) -> boolean().
is_json_object_empty({struct, Value})
when is_list(Value) andalso Value =:= [] ->
true;
is_json_object_empty({Value})
when is_list(Value)
andalso Value =:= [] ->
true;
%% handle `jsx' empty objects
is_json_object_empty([{}]) ->
true;
?IF_MAPS(
is_json_object_empty(Map)
when erlang:is_map(Map) ->
maps:size(Map) =:= 0;
)
is_json_object_empty(_) ->
false.
5 changes: 3 additions & 2 deletions src/jesse_schema_validator.erl
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@
, Options :: [{Key :: atom(), Data :: any()}]
) -> {ok, jesse:json_term()}
| no_return().
validate(JsonSchema, Value, Options) ->
validate(JsonSchema, Value, Options0) ->
Options = [{with_value, Value} | proplists:delete(with_value, Options0)],
State = jesse_state:new(JsonSchema, Options),
NewState = validate_with_state(JsonSchema, Value, State),
{result(NewState), Value}.
{result(NewState), jesse_state:get_current_value(NewState)}.

%% @doc Validates json `Data' against `JsonSchema' with `State'.
%% If the given json is valid, then the latest state is returned to the caller,
Expand Down
1 change: 1 addition & 0 deletions src/jesse_schema_validator.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
-define(MULTIPLEOF, <<"multipleOf">>).
-define(MAXPROPERTIES, <<"maxProperties">>).
-define(MINPROPERTIES, <<"minProperties">>).
-define(DEFAULT, <<"default">>).

%% Constant definitions for Json types
-define(ANY, <<"any">>).
Expand Down
67 changes: 64 additions & 3 deletions src/jesse_state.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
-export([ add_to_path/2
, get_allowed_errors/1
, get_external_validator/1
, get_current_value/1
, get_current_path/1
, get_current_schema/1
, get_current_schema_id/1
Expand All @@ -37,11 +38,14 @@
, remove_last_from_path/1
, set_allowed_errors/2
, set_current_schema/2
, set_value/3
, set_error_list/2
, resolve_ref/2
, undo_resolve_ref/2
, canonical_path/2
, combine_id/2
, validator_options/1
, validator_option/2, validator_option/3
]).

-export_type([ state/0
Expand All @@ -55,13 +59,16 @@
, { allowed_errors :: jesse:allowed_errors()
, current_path :: current_path()
, current_schema :: jesse:schema()
, current_value :: jesse:json_term()
, default_schema_ver :: jesse:schema_ver()
, error_handler :: jesse:error_handler()
, error_list :: jesse:error_list()
, external_validator :: jesse:external_validator()
, id :: jesse:schema_id()
, root_schema :: jesse:schema()
, schema_loader_fun :: jesse:schema_loader_fun()
, setter_fun :: jesse:setter_fun()
, validator_options :: jesse:options()
}
).

Expand Down Expand Up @@ -146,6 +153,16 @@ new(JsonSchema, Options) ->
, Options
, ?default_schema_loader_fun
),
SetterFun = proplists:get_value( setter_fun
, Options
),
Value = proplists:get_value( with_value

Choose a reason for hiding this comment

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

According to Compiler:

variable 'Value' is unused

Choose a reason for hiding this comment

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

According to Xref:

variable 'Value' is unused

Choose a reason for hiding this comment

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

According to Xref:

variable 'Value' is unused

, Options
),
ValidatorOptions = proplists:get_value( validator_options
, Options
, []
),
NewState = #state{ root_schema = JsonSchema
, current_path = []
, allowed_errors = AllowedErrors
Expand All @@ -154,6 +171,9 @@ new(JsonSchema, Options) ->
, default_schema_ver = DefaultSchemaVer
, schema_loader_fun = LoaderFun
, external_validator = ExternalValidator
, setter_fun = SetterFun
, current_value = Value

Choose a reason for hiding this comment

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

According to Xref:

field current_value undefined in record state

Choose a reason for hiding this comment

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

According to Compiler:

field current_value undefined in record state

Choose a reason for hiding this comment

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

According to Compiler:

field current_value undefined in record state

, validator_options = ValidatorOptions
},
set_current_schema(NewState, JsonSchema).

Expand Down Expand Up @@ -205,14 +225,22 @@ resolve_ref(State, Reference) ->
Path = jesse_json_path:parse(Pointer),
case load_local_schema(State#state.root_schema, Path) of
?not_found ->
jesse_error:handle_schema_invalid({?schema_not_found, CanonicalReference}, State);
jesse_error:handle_schema_invalid( { ?schema_not_found
, CanonicalReference
}
, State
);
Schema ->
set_current_schema(State, Schema)
end;
false ->
case load_schema(State, BaseURI) of
?not_found ->
jesse_error:handle_schema_invalid({?schema_not_found, CanonicalReference}, State);
jesse_error:handle_schema_invalid( { ?schema_not_found
, CanonicalReference
}
, State
);
RemoteSchema ->
SchemaVer =
jesse_json_path:value(?SCHEMA, RemoteSchema, ?default_schema_ver),
Expand All @@ -223,7 +251,11 @@ resolve_ref(State, Reference) ->
Path = jesse_json_path:parse(Pointer),
case load_local_schema(RemoteSchema, Path) of
?not_found ->
jesse_error:handle_schema_invalid({?schema_not_found, CanonicalReference}, State);
jesse_error:handle_schema_invalid( { ?schema_not_found
, CanonicalReference
}
, State
);
Schema ->
set_current_schema(NewState, Schema)
end
Expand Down Expand Up @@ -392,3 +424,32 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) ->
%% @private
get_external_validator(#state{external_validator = Fun}) ->
Fun.

%% @doc Getter for `current_value'.
-spec get_current_value(State :: state()) -> jesse:json_term().
get_current_value(#state{current_value = Value}) ->
Value.

-spec set_value(State :: state(), jesse:path(), jesse:json_term()) -> state().
set_value(#state{setter_fun = undefined}=State, _Path, _Value) -> State;
set_value(#state{current_value = undefined}=State, _Path, _Value) -> State;
set_value( #state{ setter_fun = Setter
, current_value = Value

Choose a reason for hiding this comment

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

According to Xref:

field current_value undefined in record state

Choose a reason for hiding this comment

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

According to Compiler:

field current_value undefined in record state

Choose a reason for hiding this comment

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

According to Compiler:

field current_value undefined in record state

} = State
, Path

Choose a reason for hiding this comment

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

According to Xref:

variable 'Path' is unused

Choose a reason for hiding this comment

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

According to Compiler:

variable 'Path' is unused

, NewValue

Choose a reason for hiding this comment

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

According to Xref:

variable 'NewValue' is unused

Choose a reason for hiding this comment

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

According to Compiler:

variable 'NewValue' is unused

Choose a reason for hiding this comment

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

According to Compiler:

variable 'NewValue' is unused

) ->
State#state{current_value = Setter(Path, NewValue, Value)}.

-spec validator_options(State :: state()) -> jesse:options().
validator_options(#state{validator_options = Options}) ->
Options.

-spec validator_option(Option :: atom(), State :: state()) -> any().
validator_option(Option, #state{validator_options = Options}) ->
proplists:get_value(Option, Options).

-spec validator_option(Option :: atom(), State :: state(), Default :: any()) ->
any().
validator_option(Option, #state{validator_options = Options}, Default) ->
proplists:get_value(Option, Options, Default).
132 changes: 116 additions & 16 deletions src/jesse_validator_draft3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
-include("jesse_schema_validator.hrl").

-type schema_error() :: ?wrong_type_dependency
| ?schema_invalid
| ?wrong_type_items.

-type schema_error_type() :: schema_error()
Expand Down Expand Up @@ -348,20 +349,19 @@ check_properties(Value, Properties, State) ->
= lists:foldl( fun({PropertyName, PropertySchema}, CurrentState) ->
case get_value(PropertyName, Value) of
?not_found ->
%% @doc 5.7. required
%%
%% This attribute indicates if the instance must have a value, and not
%% be undefined. This is false by default, making the instance
%% optional.
%% @end
case get_value(?REQUIRED, PropertySchema) of
true ->
handle_data_invalid( {?missing_required_property
, PropertyName}
, Value
, CurrentState);
_ ->
CurrentState
case get_value(?DEFAULT, PropertySchema) of
?not_found ->
check_required( PropertySchema
, PropertyName
, Value
, CurrentState
);
Default ->
check_default( PropertyName
, PropertySchema
, Default
, CurrentState
)
end;
Property ->
NewState = set_current_schema( CurrentState
Expand Down Expand Up @@ -583,6 +583,24 @@ check_items_fun(Tuples, State) ->
),
set_current_schema(TmpState, get_current_schema(State)).


%% @doc 5.7. required
%%
%% This attribute indicates if the instance must have a value, and not
%% be undefined. This is false by default, making the instance
%% optional.
%% @private
check_required(PropertySchema, PropertyName, Value, CurrentState) ->
case get_value(?REQUIRED, PropertySchema) of
true ->
handle_data_invalid( {?missing_required_property
, PropertyName}
, Value
, CurrentState);
_ ->
CurrentState
end.

%% @doc 5.8. dependencies
%%
%% This attribute is an object that defines the requirements of a
Expand Down Expand Up @@ -904,7 +922,8 @@ validate_ref(Value, Reference, State) ->
{error, NewState} ->
undo_resolve_ref(NewState, State);
{ok, NewState, Schema} ->
ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState),
ResultState =
jesse_schema_validator:validate_with_state(Schema, Value, NewState),
undo_resolve_ref(ResultState, State)
end.

Expand Down Expand Up @@ -992,7 +1011,11 @@ compare_properties(Value1, Value2) ->
%% Wrappers
%% @private
get_value(Key, Schema) ->
jesse_json_path:value(Key, Schema, ?not_found).
get_value(Key, Schema, ?not_found).

%% @private
get_value(Key, Schema, Default) ->
jesse_json_path:value(Key, Schema, Default).

%% @private
unwrap(Value) ->
Expand Down Expand Up @@ -1041,3 +1064,80 @@ maybe_external_check_value(Value, State) ->
Fun ->
Fun(Value, State)
end.

%% @private
validator_option(Option, State, Default) ->
jesse_state:validator_option(Option, State, Default).

%% @private
set_value(PropertyName, Value, State) ->

Choose a reason for hiding this comment

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

According to Xref:

jesse_validator_draft3:set_value/3 calls undefined function jesse_state:set_value/3

Choose a reason for hiding this comment

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

According to Xref:

jesse_validator_draft3:set_value/3 calls undefined function jesse_state:set_value/3

Path = lists:reverse([PropertyName] ++ jesse_state:get_current_path(State)),
jesse_state:set_value(State, Path, Value).

%% @private
check_default_for_type(Default, State) ->
validator_option('use_defaults', State, false)
andalso (not jesse_lib:is_json_object(Default)
orelse validator_option( 'apply_defaults_to_empty_objects'
, State
, false
)
orelse not jesse_lib:is_json_object_empty(Default)).

%% @private
check_default(PropertyName, PropertySchema, Default, State) ->
Type = get_value(?TYPE, PropertySchema, ?not_found),
case is_valid_default(Type, Default, State) of
true ->
set_default(PropertyName, PropertySchema, Default, State);
false ->
State
end.

%% @private
is_valid_default(?not_found, _Default, _State) ->
false;
is_valid_default(Type, Default, State)
when is_binary(Type) ->
check_default_for_type(Default, State)
andalso is_type_valid(Default, Type, State);
is_valid_default(Types, Default, State)
when is_list(Types) ->
check_default_for_type(Default, State)
andalso lists:any( fun(Type) ->
is_type_valid(Default, Type, State)
end
, Types
);
is_valid_default(_, _Default, _State) -> false.

%% @private
set_default(PropertyName, PropertySchema, Default, State) ->
State1 = set_value(PropertyName, Default, State),
State2 = add_to_path(State1, PropertyName),
case validate_schema(Default, PropertySchema, State2) of
{true, State4} ->
jesse_state:remove_last_from_path(State4);
_ ->
State
end.

%% @doc Validate a value against a schema in a given state.
%% Used by all combinators to run validation on a schema.
%% @private
validate_schema(Value, Schema, State0) ->
try
case jesse_lib:is_json_object(Schema) of
true ->
State1 = set_current_schema(State0, Schema),
State2 = jesse_schema_validator:validate_with_state( Schema
, Value
, State1
),
{true, State2};
false ->
handle_schema_invalid(?schema_invalid, State0)
end
catch
throw:Errors -> {false, Errors}
end.
Loading