Skip to content

Commit

Permalink
set default values for properties
Browse files Browse the repository at this point in the history
persistence depends on setter_fun option
  • Loading branch information
lazedo authored and andreineculau committed May 20, 2017
1 parent aaca756 commit f9b0e7d
Show file tree
Hide file tree
Showing 7 changed files with 344 additions and 41 deletions.
2 changes: 1 addition & 1 deletion src/jesse_error.erl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
%% throws an exeption, otherwise adds a new element to the list and returs it.
-spec default_error_handler( Error :: error_reason()
, ErrorList :: [error_reason()]
, AllowedErrors :: non_neg_integer()
, AllowedErrors :: non_neg_integer() | 'infinity'
) -> [error_reason()] | no_return().
default_error_handler(Error, ErrorList, AllowedErrors) ->
case AllowedErrors > length(ErrorList) orelse AllowedErrors =:= 'infinity' of
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.
1 change: 1 addition & 0 deletions src/jesse_schema_validator.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
-define(MULTIPLEOF, <<"multipleOf">>).
-define(MAXPROPERTIES, <<"maxProperties">>).
-define(MINPROPERTIES, <<"minProperties">>).
-define(DEFAULT, <<"default">>).

%% Constant definitions for Json types
-define(ANY, <<"any">>).
Expand Down
63 changes: 52 additions & 11 deletions src/jesse_state.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,16 @@
, undo_resolve_ref/2
, canonical_path/2
, combine_id/2
, validator_options/1
, validator_option/2, validator_option/3
]).

-type option() :: {Key :: atom(), Data :: any()}.
-type options() :: [option()].

-export_type([ state/0
, option/0
, options/0
]).

%% Includes
Expand Down Expand Up @@ -75,6 +82,7 @@
, extra_validator :: extra_validator()
, setter_fun :: setter_fun()
, id :: http_uri:uri() | 'undefined'
, validator_options :: options()
}
).

Expand All @@ -98,7 +106,7 @@ add_to_path(State, Property) ->
State#state{current_path = [Property | CurrentPath]}.

%% @doc Getter for `allowed_errors'.
-spec get_allowed_errors(State :: state()) -> non_neg_integer().
-spec get_allowed_errors(State :: state()) -> non_neg_integer() | 'infinity'.
get_allowed_errors(#state{allowed_errors = AllowedErrors}) ->
AllowedErrors.

Expand Down Expand Up @@ -140,7 +148,7 @@ get_error_list(#state{error_list = ErrorList}) ->

%% @doc Returns newly created state.
-spec new( JsonSchema :: jesse:json_term()
, Options :: [{Key :: atom(), Data :: any()}]
, Options :: options()
) -> state().
new(JsonSchema, Options) ->
ErrorHandler = proplists:get_value( error_handler
Expand Down Expand Up @@ -172,6 +180,10 @@ new(JsonSchema, Options) ->
Value = proplists:get_value( with_value
, Options
),
ValidatorOptions = proplists:get_value( validator_options
, Options
, []
),
NewState = #state{ root_schema = JsonSchema
, current_path = []
, allowed_errors = AllowedErrors
Expand All @@ -182,6 +194,7 @@ new(JsonSchema, Options) ->
, extra_validator = ExtraValidator
, setter_fun = SetterFun
, current_value = Value
, validator_options = ValidatorOptions
},
set_current_schema(NewState, JsonSchema).

Expand All @@ -192,7 +205,7 @@ remove_last_from_path(State = #state{current_path = [_Property | Path]}) ->

%% @doc Getter for `allowed_errors'.
-spec set_allowed_errors( State :: state()
, AllowedErrors :: non_neg_integer()
, AllowedErrors :: non_neg_integer() | 'infinity'
) -> state().
set_allowed_errors(#state{} = State, AllowedErrors) ->
State#state{allowed_errors = AllowedErrors}.
Expand Down Expand Up @@ -231,14 +244,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 @@ -249,7 +270,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 @@ -418,12 +443,28 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) ->
get_current_value(#state{current_value = Value}) -> Value.

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

get_extra_validator(#state{extra_validator = Fun}) ->
Fun.

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

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

-spec validator_option(Option :: atom(), State :: state(), Default :: any()) ->
any().
validator_option(Option, #state{validator_options = Options}, Default) ->
proplists:get_value(Option, Options, Default).
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 @@ -1039,3 +1062,80 @@ check_external_validation(Value, State) ->
undefined -> 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) ->
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

0 comments on commit f9b0e7d

Please sign in to comment.