Skip to content

Commit

Permalink
feat(nomasystems#59): Add problem detail return value
Browse files Browse the repository at this point in the history
  • Loading branch information
LoisSotoLopez committed Feb 14, 2024
1 parent eb4cef1 commit 3ef587b
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 40 deletions.
5 changes: 4 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@
erf,
{erf_router, handle, 2},
{erf_router, handle_event, 3},
{erf_static, mime_type, 1}
{erf_static, mime_type, 1},
{erf_problem_details, new, 4},
{erf_problem_details, validation_detail, 3},
{erf_util, binarize_atoms, 1}
]}.

{gradualizer_opts, [
Expand Down
1 change: 1 addition & 0 deletions src/erf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ build_router(SpecPath, SpecParser, Callback, RawStaticRoutes, SwaggerUI) ->
callback => Callback,
static_routes => StaticRoutes
}),
file:write_file("router.erl", erl_prettypr:format(Router)),
case erf_router:load(Router) of
ok ->
{ok, RouterMod, Router};
Expand Down
74 changes: 74 additions & 0 deletions src/erf_problem_details.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
%%% Copyright 2024 Nomasystems, S.L. http://www.nomasystems.com
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License

%% @doc Module that generates Problem Details representations, based on RFC 9457.
-module(erf_problem_details).

%%% EXTERNAL EXPORTS
-export([
new/4,
validation_detail/3
]).

%%% TYPES
-type detail() ::
validation_detail().

-type validation_detail() :: #{
schema_path := binary(),
type := path | query | body,
reason := binary()
}.

-type t() :: #{
title := undefined | binary(),
status := undefined | erf_parser:status_code(),
detail := undefined | binary(),
details := undefined | list(detail())
}.

%%% TYPE EXPORTS
-export_type([
t/0,
validation_detail/0
]).

%%%-----------------------------------------------------------------------------
%%% EXTERNAL EXPORTS
%%%-----------------------------------------------------------------------------
-spec new(Title, Status, Detail, Errors) -> Resp when
Title :: undefined | binary(),
Status :: undefined | erf_parser:status_code(),
Detail :: undefined | binary(),
Errors :: undefined | list(detail()),
Resp :: t().
new(Title, Status, Detail, Details) ->
#{
title => Title,
status => Status,
detail => Detail,
details => Details
}.

-spec validation_detail(Parameter, Type, Reason) -> Resp when
Parameter :: binary(),
Type :: path | query | body,
Reason :: binary(),
Resp :: validation_detail().
validation_detail(Parameter, Type, Reason) ->
#{
schema_path => Parameter,
type => Type,
reason => Reason
}.
119 changes: 81 additions & 38 deletions src/erf_router.erl
Original file line number Diff line number Diff line change
Expand Up @@ -351,44 +351,84 @@ handle_ast(API, #{callback := Callback} = Opts) ->
]
),
erl_syntax:clause(
[erl_syntax:tuple([
erl_syntax:atom('false'),
[
erl_syntax:tuple([
erl_syntax:atom('false'),
erl_syntax:tuple([
erl_syntax:variable('SchemaPath'),
erl_syntax:variable('Description')
]),
erl_syntax:variable('_ConditionIndex')
erl_syntax:tuple([
erl_syntax:variable('SchemaPath'),
erl_syntax:variable('Description')
]),
erl_syntax:variable('_ConditionIndex')
])
])
])],
],
none,
[
erl_syntax:tuple(
[
erl_syntax:integer(400),
erl_syntax:list([]),
erl_syntax:map_expr([
erl_syntax:map_field_assoc(
erl_syntax:binary([
erl_syntax:binary_field(
erl_syntax:string("schema_path")
)
]),
erl_syntax:application(
erl_syntax:atom(erf_util),
erl_syntax:atom(binarize_atoms),
[
erl_syntax:application(
erl_syntax:atom(erlang),
erl_syntax:atom(atom_to_binary),
[erl_syntax:variable('SchemaPath')]
erl_syntax:atom(
erf_problem_details
),
erl_syntax:atom(new),
[
erl_syntax:binary([
erl_syntax:binary_field(
erl_syntax:string(
"Validation error"
)
)
]),
erl_syntax:integer(400),
erl_syntax:binary([
erl_syntax:binary_field(
erl_syntax:string(
"One or more parameters failed validation."
)
)
]),
erl_syntax:list([
erl_syntax:application(
erl_syntax:atom(
erf_problem_details
),
erl_syntax:atom(
validation_detail
),
[
erl_syntax:application(
erl_syntax:atom(
erlang
),
erl_syntax:atom(
atom_to_binary
),
[
erl_syntax:variable(
'SchemaPath'
)
]
),
erl_syntax:atom(
'body'
),
erl_syntax:variable(
'Description'
)
]
)
])
]
)
),
erl_syntax:map_field_assoc(
erl_syntax:binary([
erl_syntax:binary_field(
erl_syntax:string("description")
)
]),
erl_syntax:variable('Description')
)
])
]
)
]
)
]
Expand Down Expand Up @@ -733,17 +773,20 @@ is_valid_request(RawParameters, Request) ->
erl_syntax:atom('andalso'),
[
erl_syntax:list(
[ erl_syntax:tuple([
erl_syntax:fun_expr([
erl_syntax:clause(
none,
[
Condition
]
)
]),
erl_syntax:list([])
]) || Condition <- [RequestBody | Parameters]]
[
erl_syntax:tuple([
erl_syntax:fun_expr([
erl_syntax:clause(
none,
[
Condition
]
)
]),
erl_syntax:list([])
])
|| Condition <- [RequestBody | Parameters]
]
)
]
).
Expand Down
27 changes: 26 additions & 1 deletion src/erf_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
%%% EXTERNAL EXPORTS
-export([
to_pascal_case/1,
to_snake_case/1
to_snake_case/1,
binarize_atoms/1
]).

%%%-----------------------------------------------------------------------------
Expand Down Expand Up @@ -48,6 +49,30 @@ to_snake_case([C | Rest]) when C >= $A andalso C =< $Z ->
to_snake_case([_C | Rest]) ->
to_snake_case(Rest).

-spec binarize_atoms(Map) -> Resp when
Map :: map(),
Resp :: map().
binarize_atoms(Map) when is_map(Map) ->
RecursiveFun = fun
R(V) when is_map(V) -> binarize_atoms(V);
R(V) when is_list(V) ->
lists:map(R, V);
R(V) when is_atom(V) ->
erlang:atom_to_binary(V);
R(V) ->
V
end,
maps:fold(
fun
(K, V, Acc) when is_atom(K) ->
maps:put(atom_to_binary(K), RecursiveFun(V), Acc);
(K, V, Acc) ->
maps:put(K, V, Acc)
end,
#{},
Map
).

%%%-----------------------------------------------------------------------------
%%% INTERNAL FUNCTIONS
%%%-----------------------------------------------------------------------------
Expand Down

0 comments on commit 3ef587b

Please sign in to comment.