Skip to content

Commit f9b0e7d

Browse files
lazedoandreineculau
authored andcommitted
set default values for properties
persistence depends on setter_fun option
1 parent aaca756 commit f9b0e7d

7 files changed

+344
-41
lines changed

src/jesse_error.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
%% throws an exeption, otherwise adds a new element to the list and returs it.
6161
-spec default_error_handler( Error :: error_reason()
6262
, ErrorList :: [error_reason()]
63-
, AllowedErrors :: non_neg_integer()
63+
, AllowedErrors :: non_neg_integer() | 'infinity'
6464
) -> [error_reason()] | no_return().
6565
default_error_handler(Error, ErrorList, AllowedErrors) ->
6666
case AllowedErrors > length(ErrorList) orelse AllowedErrors =:= 'infinity' of

src/jesse_lib.erl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
-export([ empty_if_not_found/1
2828
, is_array/1
2929
, is_json_object/1
30+
, is_json_object_empty/1
3031
, is_null/1
3132
]).
3233

@@ -86,3 +87,22 @@ is_null(null) ->
8687
is_null(_Value) ->
8788
false.
8889

90+
%% @doc check if json object is_empty.
91+
-spec is_json_object_empty(Value :: any()) -> boolean().
92+
is_json_object_empty({struct, Value})
93+
when is_list(Value) andalso Value =:= [] ->
94+
true;
95+
is_json_object_empty({Value})
96+
when is_list(Value)
97+
andalso Value =:= [] ->
98+
true;
99+
%% handle `jsx' empty objects
100+
is_json_object_empty([{}]) ->
101+
true;
102+
?IF_MAPS(
103+
is_json_object_empty(Map)
104+
when erlang:is_map(Map) ->
105+
maps:size(Map) =:= 0;
106+
)
107+
is_json_object_empty(_) ->
108+
false.

src/jesse_schema_validator.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
-define(MULTIPLEOF, <<"multipleOf">>).
5656
-define(MAXPROPERTIES, <<"maxProperties">>).
5757
-define(MINPROPERTIES, <<"minProperties">>).
58+
-define(DEFAULT, <<"default">>).
5859

5960
%% Constant definitions for Json types
6061
-define(ANY, <<"any">>).

src/jesse_state.erl

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,16 @@
4444
, undo_resolve_ref/2
4545
, canonical_path/2
4646
, combine_id/2
47+
, validator_options/1
48+
, validator_option/2, validator_option/3
4749
]).
4850

51+
-type option() :: {Key :: atom(), Data :: any()}.
52+
-type options() :: [option()].
53+
4954
-export_type([ state/0
55+
, option/0
56+
, options/0
5057
]).
5158

5259
%% Includes
@@ -75,6 +82,7 @@
7582
, extra_validator :: extra_validator()
7683
, setter_fun :: setter_fun()
7784
, id :: http_uri:uri() | 'undefined'
85+
, validator_options :: options()
7886
}
7987
).
8088

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

100108
%% @doc Getter for `allowed_errors'.
101-
-spec get_allowed_errors(State :: state()) -> non_neg_integer().
109+
-spec get_allowed_errors(State :: state()) -> non_neg_integer() | 'infinity'.
102110
get_allowed_errors(#state{allowed_errors = AllowedErrors}) ->
103111
AllowedErrors.
104112

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

141149
%% @doc Returns newly created state.
142150
-spec new( JsonSchema :: jesse:json_term()
143-
, Options :: [{Key :: atom(), Data :: any()}]
151+
, Options :: options()
144152
) -> state().
145153
new(JsonSchema, Options) ->
146154
ErrorHandler = proplists:get_value( error_handler
@@ -172,6 +180,10 @@ new(JsonSchema, Options) ->
172180
Value = proplists:get_value( with_value
173181
, Options
174182
),
183+
ValidatorOptions = proplists:get_value( validator_options
184+
, Options
185+
, []
186+
),
175187
NewState = #state{ root_schema = JsonSchema
176188
, current_path = []
177189
, allowed_errors = AllowedErrors
@@ -182,6 +194,7 @@ new(JsonSchema, Options) ->
182194
, extra_validator = ExtraValidator
183195
, setter_fun = SetterFun
184196
, current_value = Value
197+
, validator_options = ValidatorOptions
185198
},
186199
set_current_schema(NewState, JsonSchema).
187200

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

193206
%% @doc Getter for `allowed_errors'.
194207
-spec set_allowed_errors( State :: state()
195-
, AllowedErrors :: non_neg_integer()
208+
, AllowedErrors :: non_neg_integer() | 'infinity'
196209
) -> state().
197210
set_allowed_errors(#state{} = State, AllowedErrors) ->
198211
State#state{allowed_errors = AllowedErrors}.
@@ -231,14 +244,22 @@ resolve_ref(State, Reference) ->
231244
Path = jesse_json_path:parse(Pointer),
232245
case load_local_schema(State#state.root_schema, Path) of
233246
?not_found ->
234-
jesse_error:handle_schema_invalid({?schema_not_found, CanonicalReference}, State);
247+
jesse_error:handle_schema_invalid( { ?schema_not_found
248+
, CanonicalReference
249+
}
250+
, State
251+
);
235252
Schema ->
236253
set_current_schema(State, Schema)
237254
end;
238255
false ->
239256
case load_schema(State, BaseURI) of
240257
?not_found ->
241-
jesse_error:handle_schema_invalid({?schema_not_found, CanonicalReference}, State);
258+
jesse_error:handle_schema_invalid( { ?schema_not_found
259+
, CanonicalReference
260+
}
261+
, State
262+
);
242263
RemoteSchema ->
243264
SchemaVer =
244265
jesse_json_path:value(?SCHEMA, RemoteSchema, ?default_schema_ver),
@@ -249,7 +270,11 @@ resolve_ref(State, Reference) ->
249270
Path = jesse_json_path:parse(Pointer),
250271
case load_local_schema(RemoteSchema, Path) of
251272
?not_found ->
252-
jesse_error:handle_schema_invalid({?schema_not_found, CanonicalReference}, State);
273+
jesse_error:handle_schema_invalid( { ?schema_not_found
274+
, CanonicalReference
275+
}
276+
, State
277+
);
253278
Schema ->
254279
set_current_schema(NewState, Schema)
255280
end
@@ -418,12 +443,28 @@ load_schema(#state{schema_loader_fun = LoaderFun}, SchemaURI) ->
418443
get_current_value(#state{current_value = Value}) -> Value.
419444

420445
-spec set_value(State :: state(), jesse:path(), jesse:json_term()) -> state().
421-
set_value(#state{setter_fun=undefined}=State, _Path, _Value) -> State;
422-
set_value(#state{current_value=undefined}=State, _Path, _Value) -> State;
423-
set_value(#state{setter_fun=Setter
424-
,current_value=Value
425-
}=State, Path, NewValue) ->
446+
set_value(#state{setter_fun = undefined} = State, _Path, _Value) -> State;
447+
set_value(#state{current_value = undefined} = State, _Path, _Value) -> State;
448+
set_value( #state{setter_fun = Setter
449+
, current_value = Value
450+
} = State
451+
, Path
452+
, NewValue
453+
) ->
426454
State#state{current_value = Setter(Path, NewValue, Value)}.
427455

428456
get_extra_validator(#state{extra_validator = Fun}) ->
429457
Fun.
458+
459+
-spec validator_options(State :: state()) -> options().
460+
validator_options(#state{validator_options = Options}) ->
461+
Options.
462+
463+
-spec validator_option(Option :: atom(), State :: state()) -> any().
464+
validator_option(Option, #state{validator_options = Options}) ->
465+
proplists:get_value(Option, Options).
466+
467+
-spec validator_option(Option :: atom(), State :: state(), Default :: any()) ->
468+
any().
469+
validator_option(Option, #state{validator_options = Options}, Default) ->
470+
proplists:get_value(Option, Options, Default).

src/jesse_validator_draft3.erl

Lines changed: 116 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
-include("jesse_schema_validator.hrl").
3232

3333
-type schema_error() :: ?wrong_type_dependency
34+
| ?schema_invalid
3435
| ?wrong_type_items.
3536

3637
-type schema_error_type() :: schema_error()
@@ -348,20 +349,19 @@ check_properties(Value, Properties, State) ->
348349
= lists:foldl( fun({PropertyName, PropertySchema}, CurrentState) ->
349350
case get_value(PropertyName, Value) of
350351
?not_found ->
351-
%% @doc 5.7. required
352-
%%
353-
%% This attribute indicates if the instance must have a value, and not
354-
%% be undefined. This is false by default, making the instance
355-
%% optional.
356-
%% @end
357-
case get_value(?REQUIRED, PropertySchema) of
358-
true ->
359-
handle_data_invalid( {?missing_required_property
360-
, PropertyName}
361-
, Value
362-
, CurrentState);
363-
_ ->
364-
CurrentState
352+
case get_value(?DEFAULT, PropertySchema) of
353+
?not_found ->
354+
check_required( PropertySchema
355+
, PropertyName
356+
, Value
357+
, CurrentState
358+
);
359+
Default ->
360+
check_default( PropertyName
361+
, PropertySchema
362+
, Default
363+
, CurrentState
364+
)
365365
end;
366366
Property ->
367367
NewState = set_current_schema( CurrentState
@@ -583,6 +583,24 @@ check_items_fun(Tuples, State) ->
583583
),
584584
set_current_schema(TmpState, get_current_schema(State)).
585585

586+
587+
%% @doc 5.7. required
588+
%%
589+
%% This attribute indicates if the instance must have a value, and not
590+
%% be undefined. This is false by default, making the instance
591+
%% optional.
592+
%% @private
593+
check_required(PropertySchema, PropertyName, Value, CurrentState) ->
594+
case get_value(?REQUIRED, PropertySchema) of
595+
true ->
596+
handle_data_invalid( {?missing_required_property
597+
, PropertyName}
598+
, Value
599+
, CurrentState);
600+
_ ->
601+
CurrentState
602+
end.
603+
586604
%% @doc 5.8. dependencies
587605
%%
588606
%% This attribute is an object that defines the requirements of a
@@ -904,7 +922,8 @@ validate_ref(Value, Reference, State) ->
904922
{error, NewState} ->
905923
undo_resolve_ref(NewState, State);
906924
{ok, NewState, Schema} ->
907-
ResultState = jesse_schema_validator:validate_with_state(Schema, Value, NewState),
925+
ResultState =
926+
jesse_schema_validator:validate_with_state(Schema, Value, NewState),
908927
undo_resolve_ref(ResultState, State)
909928
end.
910929

@@ -992,7 +1011,11 @@ compare_properties(Value1, Value2) ->
9921011
%% Wrappers
9931012
%% @private
9941013
get_value(Key, Schema) ->
995-
jesse_json_path:value(Key, Schema, ?not_found).
1014+
get_value(Key, Schema, ?not_found).
1015+
1016+
%% @private
1017+
get_value(Key, Schema, Default) ->
1018+
jesse_json_path:value(Key, Schema, Default).
9961019

9971020
%% @private
9981021
unwrap(Value) ->
@@ -1039,3 +1062,80 @@ check_external_validation(Value, State) ->
10391062
undefined -> State;
10401063
Fun -> Fun(Value, State)
10411064
end.
1065+
1066+
%% @private
1067+
validator_option(Option, State, Default) ->
1068+
jesse_state:validator_option(Option, State, Default).
1069+
1070+
%% @private
1071+
set_value(PropertyName, Value, State) ->
1072+
Path = lists:reverse([PropertyName] ++ jesse_state:get_current_path(State)),
1073+
jesse_state:set_value(State, Path, Value).
1074+
1075+
%% @private
1076+
check_default_for_type(Default, State) ->
1077+
validator_option('use_defaults', State, false)
1078+
andalso (not jesse_lib:is_json_object(Default)
1079+
orelse validator_option( 'apply_defaults_to_empty_objects'
1080+
, State
1081+
, false
1082+
)
1083+
orelse not jesse_lib:is_json_object_empty(Default)).
1084+
1085+
%% @private
1086+
check_default(PropertyName, PropertySchema, Default, State) ->
1087+
Type = get_value(?TYPE, PropertySchema, ?not_found),
1088+
case is_valid_default(Type, Default, State) of
1089+
true ->
1090+
set_default(PropertyName, PropertySchema, Default, State);
1091+
false ->
1092+
State
1093+
end.
1094+
1095+
%% @private
1096+
is_valid_default(?not_found, _Default, _State) ->
1097+
false;
1098+
is_valid_default(Type, Default, State)
1099+
when is_binary(Type) ->
1100+
check_default_for_type(Default, State)
1101+
andalso is_type_valid(Default, Type, State);
1102+
is_valid_default(Types, Default, State)
1103+
when is_list(Types) ->
1104+
check_default_for_type(Default, State)
1105+
andalso lists:any( fun(Type) ->
1106+
is_type_valid(Default, Type, State)
1107+
end
1108+
, Types
1109+
);
1110+
is_valid_default(_, _Default, _State) -> false.
1111+
1112+
%% @private
1113+
set_default(PropertyName, PropertySchema, Default, State) ->
1114+
State1 = set_value(PropertyName, Default, State),
1115+
State2 = add_to_path(State1, PropertyName),
1116+
case validate_schema(Default, PropertySchema, State2) of
1117+
{true, State4} ->
1118+
jesse_state:remove_last_from_path(State4);
1119+
_ ->
1120+
State
1121+
end.
1122+
1123+
%% @doc Validate a value against a schema in a given state.
1124+
%% Used by all combinators to run validation on a schema.
1125+
%% @private
1126+
validate_schema(Value, Schema, State0) ->
1127+
try
1128+
case jesse_lib:is_json_object(Schema) of
1129+
true ->
1130+
State1 = set_current_schema(State0, Schema),
1131+
State2 = jesse_schema_validator:validate_with_state( Schema
1132+
, Value
1133+
, State1
1134+
),
1135+
{true, State2};
1136+
false ->
1137+
handle_schema_invalid(?schema_invalid, State0)
1138+
end
1139+
catch
1140+
throw:Errors -> {false, Errors}
1141+
end.

0 commit comments

Comments
 (0)