diff --git a/src/openapi_schema.erl b/src/openapi_schema.erl index e923d91..de32c38 100644 --- a/src/openapi_schema.erl +++ b/src/openapi_schema.erl @@ -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), @@ -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 @@ -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 -> diff --git a/test/example-openapi.json b/test/example-openapi.json new file mode 100644 index 0000000..6e997ad --- /dev/null +++ b/test/example-openapi.json @@ -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" + } + } + } + } + } + } + } +} diff --git a/test/openapi_handler_SUITE.erl b/test/openapi_handler_SUITE.erl index b6b227b..027dabb 100644 --- a/test/openapi_handler_SUITE.erl +++ b/test/openapi_handler_SUITE.erl @@ -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, diff --git a/test/openapi_schema_SUITE.erl b/test/openapi_schema_SUITE.erl index c3d642c..9f5df55 100644 --- a/test/openapi_schema_SUITE.erl +++ b/test/openapi_schema_SUITE.erl @@ -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, [], [ @@ -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 = #{