Skip to content
Merged
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
11 changes: 8 additions & 3 deletions src/openapi_schema.erl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ process(Input, #{} = Opts) ->
(patch,Flag) when Flag == true; Flag == false -> ok;
(extra_obj_key,Flag) when Flag == drop; Flag == error -> ok;
(required_obj_keys,Flag) when Flag == drop; Flag == error -> ok;
(access_type,Flag) when Flag == read; Flag == write -> ok;
(access_type,Flag) when Flag == read; Flag == write; Flag == raw -> ok;
(explain,FlagList) -> check_explain_keys(FlagList);
(K,V) -> error({unknown_option,K,V})
end, Opts),
Expand Down Expand Up @@ -285,9 +285,11 @@ encode3(#{type := <<"object">>, properties := Properties} = Schema, #{query := Q

RequiredKeys = get_required_keys(Schema, Opts),
IsReadOnly = maps:get(readOnly, Prop, false),
IsWriteOnly = maps:get(writeOnly, Prop, false),
IsPrimary = maps:get('x-primary-key', Prop, false),
IsRequired = (lists:member(FieldBin, RequiredKeys) orelse IsPrimary),
IsWriteAccess = maps:get(access_type, Opts, read) == write,
IsReadAccess = maps:get(access_type, Opts, raw) == read,
IsWriteAccess = maps:get(access_type, Opts, raw) == write,

ApplyDefaults = maps:get(apply_defaults, Opts, false),
EffectiveValue = case {Input, Prop} of
Expand Down Expand Up @@ -318,9 +320,12 @@ encode3(#{type := <<"object">>, properties := Properties} = Schema, #{query := Q
{ok, Null} when (Null == null orelse Null == undefined) andalso
not NullableProp andalso not Patching ->
Obj;
% Silently drop read only fields with write access
% Silently drop readOnly fields on write
{ok, _Value} when IsWriteAccess andalso IsReadOnly andalso (not IsRequired) ->
Obj;
% Silently drop writeOnly fields on read
{ok, _Value} when IsReadAccess andalso IsWriteOnly andalso (not IsRequired) ->
Obj;
{ok, Value} ->
case encode3(Prop#{nullable => NullableProp}, Opts, Value, Path ++ [Field]) of
{error, _} = E ->
Expand Down
41 changes: 41 additions & 0 deletions test/example-openapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"openapi": "3.1.1",
"info": {
"contact": { "email": "hello@exampl.com", "name": "Test", "url": "https://example.com/" },
"description": "dummy desc",
"title": "Test API",
"version": "0.1.0"
},
"components": {
"schemas": {
"external_validators_simple": {
"type": "string",
"format": "no_space"
},
"external_validators_array": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string",
"format": "no_space"
}
}
},
"external_validators_object": {
"type": "object",
"properties": {
"in_object": {
"type": "object",
"properties": {
"prop1": {
"type": "string",
"format": "no_space"
}
}
}
}
}
}
}
}
12 changes: 3 additions & 9 deletions test/openapi_handler_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -438,18 +438,12 @@ required_keys_filter(_) ->


select_not_filters_required_keys(_) ->
% p3 is 'writeOnly', so it should be dropped in results
#{elements := [Elem1,Elem1]} = openapi_client:call(test_schema_api, selectCollectionFields,
#{json_body => #{p1 => 1, p2 => 2, p3 => 3, p4 => 4, p5 => 5}}),
#{p1 := 1, p2 := 2, p3 := 3, p4 := 4, p5 :=5} = Elem1,
#{p1 := 1, p2 := 2, p4 := 4, p5 :=5} = Elem1,

% p1, p2 are 'readOnly' required keys
#{elements := [Elem2,Elem2]} = openapi_client:call(test_schema_api, selectCollectionFields,
#{json_body => #{p1 => 1, p2 => 2, p3 => 3, p4 => 4, p5 => 5}, select => <<"p3">>}),
#{p1 := 1, p2 := 2, p3 := 3} = Elem2,
undefined = maps:get(p4, Elem2, undefined),
undefined = maps:get(p5, Elem2, undefined),

% p3 is 'writeOnly' required key
% p1, p2 are required keys, so they are returned despite not explicitly requested
#{elements := [Elem3,Elem3]} = openapi_client:call(test_schema_api, selectCollectionFields,
#{json_body => #{p1 => 1, p2 => 2, p3 => 3, p4 => 4, p5 => 5}, select => <<"p4">>}),
#{p1 := 1, p2 := 2, p4 := 4} = Elem3,
Expand Down
22 changes: 22 additions & 0 deletions test/openapi_schema_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ groups() ->
check_explain_on_error,
one_of_integer_const,
one_of_const_default,
drop_unidirectional_keys,
filter_read_only_props
]},
{introspection, [], [
Expand Down Expand Up @@ -432,6 +433,27 @@ one_of_const_default(_) ->
#{schema => FooType, apply_defaults => true}),
ok.


drop_unidirectional_keys(_) ->
Schema = #{
type => <<"object">>,
properties => #{
pn => #{type => <<"integer">>},
pr => #{type => <<"integer">>, readOnly => true},
pw => #{type => <<"integer">>, writeOnly => true}
}
},
% 'raw' access does not involve any read/write filtering logic
ObjRaw = openapi_schema:process(#{pn => 1, pr => 2, pw => 3}, #{schema => Schema, extra_obj_key => drop, access_type => raw}),
[pn, pr, pw] = lists:sort(maps:keys(ObjRaw)),
% 'write' access drops readOnly properties
ObjW = openapi_schema:process(#{pn => 1, pr => 2, pw => 3}, #{schema => Schema, extra_obj_key => drop, access_type => write}),
[pn, pw] = lists:sort(maps:keys(ObjW)),
% 'read' access drops writeOnly properties
ObjR = openapi_schema:process(#{pn => 1, pr => 2, pw => 3}, #{schema => Schema, extra_obj_key => drop, access_type => read}),
[pn, pr] = lists:sort(maps:keys(ObjR)),
ok.

filter_read_only_props(_) ->
Schema = persistent_term:get({openapi_handler_schema,test_openapi}),
Json = #{
Expand Down