forked from loafle/openapi-generator-original
Erlang server (#19722)
* Add documentation to server and handlers * Respond to at least one path-server * Let url servers override base path * Handle reading bigger bodies in cowboy * Improve json error handling * Regenerate erlang-server handlers * Rework API module for performance and completion * Regenerate erlang-server handlers
This commit is contained in:
+121
-115
@@ -46,7 +46,7 @@ accept_callback(Class, OperationID, Req, Context) ->
|
||||
|
||||
-export_type([operation_id/0]).
|
||||
|
||||
-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
|
||||
-dialyzer({nowarn_function, [to_binary/1, validate_response_body/4]}).
|
||||
|
||||
-type rule() ::
|
||||
{type, binary} |
|
||||
@@ -158,24 +158,31 @@ for the `OperationID` operation.
|
||||
{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_param_info(OperationID, Name) ->
|
||||
error({unknown_param, OperationID, Name}).
|
||||
|
||||
-spec populate_request_params(
|
||||
operation_id(), [request_param()], cowboy_req:req(), jesse_state:state(), map()) ->
|
||||
{ok, map(), cowboy_req:req()} | {error, _, cowboy_req:req()}.
|
||||
populate_request_params(_, [], Req, _, Model) ->
|
||||
{ok, Model, Req};
|
||||
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
|
||||
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
|
||||
{ok, K, V, Req} ->
|
||||
populate_request_params(OperationID, T, Req, ValidatorState, maps:put(K, V, Model));
|
||||
populate_request_params(OperationID, [ReqParamName | T], Req0, ValidatorState, Model0) ->
|
||||
case populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) of
|
||||
{ok, V, Req} ->
|
||||
Model = maps:put(ReqParamName, V, Model0),
|
||||
populate_request_params(OperationID, T, Req, ValidatorState, Model);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
||||
#{rules := Rules, source := Source} = request_param_info(OperationID, Name),
|
||||
case get_value(Source, Name, Req0) of
|
||||
-spec populate_request_param(
|
||||
operation_id(), request_param(), cowboy_req:req(), jesse_state:state()) ->
|
||||
{ok, term(), cowboy_req:req()} | {error, term(), cowboy_req:req()}.
|
||||
populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) ->
|
||||
#{rules := Rules, source := Source} = request_param_info(OperationID, ReqParamName),
|
||||
case get_value(Source, ReqParamName, Req0) of
|
||||
{error, Reason, Req} ->
|
||||
{error, Reason, Req};
|
||||
{Value, Req} ->
|
||||
case prepare_param(Rules, Name, Value, ValidatorState) of
|
||||
{ok, Result} -> {ok, Name, Result, Req};
|
||||
case prepare_param(Rules, ReqParamName, Value, ValidatorState) of
|
||||
{ok, Result} -> {ok, Result, Req};
|
||||
{error, Reason} ->
|
||||
{error, Reason, Req}
|
||||
end
|
||||
@@ -185,112 +192,111 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
||||
|
||||
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
|
||||
[
|
||||
validate(schema, ReturnBaseType, Item, ValidatorState)
|
||||
validate(schema, Item, ReturnBaseType, ValidatorState)
|
||||
|| Item <- Body];
|
||||
|
||||
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
|
||||
validate(schema, ReturnBaseType, Body, ValidatorState).
|
||||
validate(schema, Body, ReturnBaseType, ValidatorState).
|
||||
|
||||
validate(Rule = required, Name, Value, _ValidatorState) ->
|
||||
case Value of
|
||||
undefined -> validation_error(Rule, Name);
|
||||
_ -> ok
|
||||
-spec validate(rule(), term(), request_param(), jesse_state:state()) ->
|
||||
ok | {ok, term()}.
|
||||
validate(required, undefined, ReqParamName, _) ->
|
||||
validation_error(required, ReqParamName, undefined);
|
||||
validate(required, _Value, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate(not_required, _Value, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate(_, undefined, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate({type, boolean}, Value, _ReqParamName, _) when is_boolean(Value) ->
|
||||
{ok, Value};
|
||||
validate({type, integer}, Value, _ReqParamName, _) when is_integer(Value) ->
|
||||
ok;
|
||||
validate({type, float}, Value, _ReqParamName, _) when is_float(Value) ->
|
||||
ok;
|
||||
validate({type, binary}, Value, _ReqParamName, _) when is_binary(Value) ->
|
||||
ok;
|
||||
validate(Rule = {type, binary}, Value, ReqParamName, _) ->
|
||||
validation_error(Rule, ReqParamName, Value);
|
||||
validate(Rule = {type, boolean}, Value, ReqParamName, _) ->
|
||||
case binary_to_lower(Value) of
|
||||
<<"true">> -> {ok, true};
|
||||
<<"false">> -> {ok, false};
|
||||
_ -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(not_required, _Name, _Value, _ValidatorState) ->
|
||||
ok;
|
||||
validate(_, _Name, undefined, _ValidatorState) ->
|
||||
ok;
|
||||
validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, integer}, Value, ReqParamName, _) ->
|
||||
try
|
||||
{ok, to_int(Value)}
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, float}, Value, ReqParamName, _) ->
|
||||
try
|
||||
{ok, to_float(Value)}
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, date}, Value, ReqParamName, _) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
|
||||
{ok, Value};
|
||||
validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
|
||||
V = binary_to_lower(Value),
|
||||
try
|
||||
case binary_to_existing_atom(V, utf8) of
|
||||
B when is_boolean(B) -> {ok, B};
|
||||
_ -> validation_error(Rule, Name)
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
end;
|
||||
validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, datetime}, Value, ReqParamName, _) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
end;
|
||||
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {enum, Values}, Value, ReqParamName, _) ->
|
||||
try
|
||||
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
|
||||
case lists:member(FormattedValue, Values) of
|
||||
true -> {ok, FormattedValue};
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {max, Max}, Value, ReqParamName, _) ->
|
||||
case Value =< Max of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {exclusive_max, ExclusiveMax}, Value, ReqParamName, _) ->
|
||||
case Value > ExclusiveMax of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {min, Min}, Value, ReqParamName, _) ->
|
||||
case Value >= Min of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {exclusive_min, ExclusiveMin}, Value, ReqParamName, _) ->
|
||||
case Value =< ExclusiveMin of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {max_length, MaxLength}, Value, ReqParamName, _) ->
|
||||
case size(Value) =< MaxLength of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {min_length, MinLength}, Value, ReqParamName, _) ->
|
||||
case size(Value) >= MinLength of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {pattern, Pattern}, Value, ReqParamName, _) ->
|
||||
{ok, MP} = re:compile(Pattern),
|
||||
case re:run(Value, MP) of
|
||||
{match, _} -> ok;
|
||||
_ -> validation_error(Rule, Name)
|
||||
_ -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
|
||||
validate(Rule = schema, Value, ReqParamName, ValidatorState) ->
|
||||
Definition = iolist_to_binary(["#/components/schemas/", atom_to_binary(ReqParamName)]),
|
||||
try
|
||||
_ = validate_with_schema(Value, Definition, ValidatorState),
|
||||
ok
|
||||
@@ -300,7 +306,7 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
type => schema_invalid,
|
||||
error => Error
|
||||
},
|
||||
validation_error(Rule, Name, Info);
|
||||
validation_error(Rule, ReqParamName, Value, Info);
|
||||
throw:[{data_invalid, Schema, Error, _, Path} | _] ->
|
||||
Info = #{
|
||||
type => data_invalid,
|
||||
@@ -308,25 +314,25 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
schema => Schema,
|
||||
path => Path
|
||||
},
|
||||
validation_error(Rule, Name, Info)
|
||||
validation_error(Rule, ReqParamName, Value, Info)
|
||||
end;
|
||||
validate(Rule, Name, _Value, _ValidatorState) ->
|
||||
?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
|
||||
validate(Rule, _Value, ReqParamName, _) ->
|
||||
?LOG_INFO(#{what => "Cannot validate rule", name => ReqParamName, rule => Rule}),
|
||||
error({unknown_validation_rule, Rule}).
|
||||
|
||||
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
|
||||
validation_error(ViolatedRule, Name) ->
|
||||
validation_error(ViolatedRule, Name, #{}).
|
||||
-spec validation_error(rule(), request_param(), term()) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Value) ->
|
||||
validation_error(ViolatedRule, Name, Value, #{}).
|
||||
|
||||
-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Info) ->
|
||||
throw({wrong_param, Name, ViolatedRule, Info}).
|
||||
-spec validation_error(rule(), request_param(), term(), Info :: #{_ := _}) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Value, Info) ->
|
||||
throw({wrong_param, Name, Value, ViolatedRule, Info}).
|
||||
|
||||
-spec get_value(body | qs_val | header | binding, Name :: any(), Req0 :: cowboy_req:req()) ->
|
||||
{Value :: any(), Req :: cowboy_req:req()} |
|
||||
{error, Reason :: any(), Req :: cowboy_req:req()}.
|
||||
-spec get_value(body | qs_val | header | binding, request_param(), cowboy_req:req()) ->
|
||||
{any(), cowboy_req:req()} |
|
||||
{error, any(), cowboy_req:req()}.
|
||||
get_value(body, _Name, Req0) ->
|
||||
{ok, Body, Req} = cowboy_req:read_body(Req0),
|
||||
{ok, Body, Req} = read_entire_body(Req0),
|
||||
case prepare_body(Body) of
|
||||
{error, Reason} ->
|
||||
{error, Reason, Req};
|
||||
@@ -342,17 +348,30 @@ get_value(header, Name, Req) ->
|
||||
Value = maps:get(to_header(Name), Headers, undefined),
|
||||
{Value, Req};
|
||||
get_value(binding, Name, Req) ->
|
||||
Value = cowboy_req:binding(to_binding(Name), Req),
|
||||
Value = cowboy_req:binding(Name, Req),
|
||||
{Value, Req}.
|
||||
|
||||
-spec read_entire_body(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
read_entire_body(Req) ->
|
||||
read_entire_body(Req, []).
|
||||
|
||||
-spec read_entire_body(cowboy_req:req(), iodata()) -> {ok, binary(), cowboy_req:req()}.
|
||||
read_entire_body(Request, Acc) -> % {
|
||||
case cowboy_req:read_body(Request) of
|
||||
{ok, Data, NewRequest} ->
|
||||
{ok, iolist_to_binary(lists:reverse([Data | Acc])), NewRequest};
|
||||
{more, Data, NewRequest} ->
|
||||
read_entire_body(NewRequest, [Data | Acc])
|
||||
end.
|
||||
|
||||
prepare_body(<<>>) ->
|
||||
<<>>;
|
||||
prepare_body(Body) ->
|
||||
try
|
||||
json:decode(Body)
|
||||
catch
|
||||
error:_ ->
|
||||
{error, {invalid_body, not_json, Body}}
|
||||
error:Error ->
|
||||
{error, {invalid_json, Body, Error}}
|
||||
end.
|
||||
|
||||
validate_with_schema(Body, Definition, ValidatorState) ->
|
||||
@@ -362,18 +381,17 @@ validate_with_schema(Body, Definition, ValidatorState) ->
|
||||
ValidatorState
|
||||
).
|
||||
|
||||
prepare_param(Rules, Name, Value, ValidatorState) ->
|
||||
-spec prepare_param([rule()], request_param(), term(), jesse_state:state()) ->
|
||||
{ok, term()} | {error, Reason :: any()}.
|
||||
prepare_param(Rules, ReqParamName, Value, ValidatorState) ->
|
||||
Fun = fun(Rule, Acc) ->
|
||||
case validate(Rule, Acc, ReqParamName, ValidatorState) of
|
||||
ok -> Acc;
|
||||
{ok, Prepared} -> Prepared
|
||||
end
|
||||
end,
|
||||
try
|
||||
Result = lists:foldl(
|
||||
fun(Rule, Acc) ->
|
||||
case validate(Rule, Name, Acc, ValidatorState) of
|
||||
ok -> Acc;
|
||||
{ok, Prepared} -> Prepared
|
||||
end
|
||||
end,
|
||||
Value,
|
||||
Rules
|
||||
),
|
||||
Result = lists:foldl(Fun, Value, Rules),
|
||||
{ok, Result}
|
||||
catch
|
||||
throw:Reason ->
|
||||
@@ -387,40 +405,28 @@ to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
|
||||
to_binary(V) when is_integer(V) -> integer_to_binary(V);
|
||||
to_binary(V) when is_float(V) -> float_to_binary(V).
|
||||
|
||||
-spec to_list(iodata() | atom() | number()) -> binary().
|
||||
to_list(V) when is_list(V) -> V;
|
||||
to_list(V) when is_binary(V) -> binary_to_list(V);
|
||||
to_list(V) when is_atom(V) -> atom_to_list(V);
|
||||
to_list(V) when is_integer(V) -> integer_to_list(V);
|
||||
to_list(V) when is_float(V) -> float_to_list(V).
|
||||
-spec to_float(binary() | list()) -> integer().
|
||||
to_float(Data) when is_binary(Data) ->
|
||||
binary_to_float(Data);
|
||||
to_float(Data) when is_list(Data) ->
|
||||
list_to_float(Data).
|
||||
|
||||
-spec to_float(iodata()) -> float().
|
||||
to_float(V) ->
|
||||
binary_to_float(iolist_to_binary([V])).
|
||||
|
||||
-spec to_int(integer() | binary() | list()) -> integer().
|
||||
to_int(Data) when is_integer(Data) ->
|
||||
Data;
|
||||
-spec to_int(binary() | list()) -> integer().
|
||||
to_int(Data) when is_binary(Data) ->
|
||||
binary_to_integer(Data);
|
||||
to_int(Data) when is_list(Data) ->
|
||||
list_to_integer(Data).
|
||||
|
||||
-spec to_header(iodata() | atom() | number()) -> binary().
|
||||
-spec to_header(request_param()) -> binary().
|
||||
to_header(Name) ->
|
||||
to_binary(string:lowercase(to_binary(Name))).
|
||||
to_binary(string:lowercase(atom_to_binary(Name, utf8))).
|
||||
|
||||
binary_to_lower(V) when is_binary(V) ->
|
||||
string:lowercase(V).
|
||||
|
||||
-spec to_qs(iodata() | atom() | number()) -> binary().
|
||||
-spec to_qs(request_param()) -> binary().
|
||||
to_qs(Name) ->
|
||||
to_binary(Name).
|
||||
|
||||
-spec to_binding(iodata() | atom() | number()) -> atom().
|
||||
to_binding(Name) ->
|
||||
Prepared = to_binary(Name),
|
||||
binary_to_existing_atom(Prepared, utf8).
|
||||
atom_to_binary(Name, utf8).
|
||||
|
||||
-spec get_opt(any(), []) -> any().
|
||||
get_opt(Key, Opts) ->
|
||||
|
||||
+7
-1
@@ -1,5 +1,11 @@
|
||||
%% basic handler
|
||||
-module({{classname}}).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
{{#operations}}{{#operation}}
|
||||
- `{{httpMethod}}` to `{{path}}`, OperationId: `{{operationId}}`:
|
||||
{{summary}}.
|
||||
{{/operation}}{{/operations}}
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
+22
-7
@@ -19,23 +19,38 @@ get_paths(LogicHandler) ->
|
||||
|
||||
group_paths() ->
|
||||
maps:fold(
|
||||
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
||||
case maps:find(Path, Acc) of
|
||||
fun(OperationID, #{servers := Servers, base_path := BasePath, path := Path,
|
||||
method := Method, handler := Handler}, Acc) ->
|
||||
FullPaths = build_full_paths(Servers, BasePath, Path),
|
||||
merge_paths(FullPaths, OperationID, Method, Handler, Acc)
|
||||
end, #{}, get_operations()).
|
||||
|
||||
build_full_paths([], BasePath, Path) ->
|
||||
[lists:append([BasePath, Path])];
|
||||
build_full_paths(Servers, _BasePath, Path) ->
|
||||
[lists:append([Server, Path]) || Server <- Servers ].
|
||||
|
||||
merge_paths(FullPaths, OperationID, Method, Handler, Acc) ->
|
||||
lists:foldl(
|
||||
fun(Path, Acc0) ->
|
||||
case maps:find(Path, Acc0) of
|
||||
{ok, PathInfo0 = #{operations := Operations0}} ->
|
||||
Operations = Operations0#{Method => OperationID},
|
||||
PathInfo = PathInfo0#{operations => Operations},
|
||||
Acc#{Path => PathInfo};
|
||||
Acc0#{Path => PathInfo};
|
||||
error ->
|
||||
Operations = #{Method => OperationID},
|
||||
PathInfo = #{handler => Handler, operations => Operations},
|
||||
Acc#{Path => PathInfo}
|
||||
Acc0#{Path => PathInfo}
|
||||
end
|
||||
end, #{}, get_operations()).
|
||||
end, Acc, FullPaths).
|
||||
|
||||
get_operations() ->
|
||||
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||
'{{operationId}}' => #{
|
||||
path => "{{{basePathWithoutHost}}}{{{path}}}",
|
||||
'{{operationId}}' => #{
|
||||
servers => [{{#servers}}"{{{url}}}"{{^-last}},{{/-last}}{{/servers}}],
|
||||
base_path => "{{{basePathWithoutHost}}}",
|
||||
path => "{{{path}}}",
|
||||
method => <<"{{httpMethod}}">>,
|
||||
handler => '{{classname}}'
|
||||
}{{^-last}},{{/-last}}{{/operation}}{{^-last}},{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
-module({{packageName}}_server).
|
||||
{{#appDescription}}
|
||||
-moduledoc """
|
||||
{{{appDescription}}}
|
||||
""".
|
||||
{{/appDescription}}
|
||||
|
||||
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_logic_handler).
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ accept_callback(Class, OperationID, Req, Context) ->
|
||||
|
||||
-export_type([operation_id/0]).
|
||||
|
||||
-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
|
||||
-dialyzer({nowarn_function, [to_binary/1, validate_response_body/4]}).
|
||||
|
||||
-type rule() ::
|
||||
{type, binary} |
|
||||
@@ -637,24 +637,31 @@ request_param_info('TestQueryStyleFormExplodeTrueObjectAllOf', 'query_object') -
|
||||
request_param_info(OperationID, Name) ->
|
||||
error({unknown_param, OperationID, Name}).
|
||||
|
||||
-spec populate_request_params(
|
||||
operation_id(), [request_param()], cowboy_req:req(), jesse_state:state(), map()) ->
|
||||
{ok, map(), cowboy_req:req()} | {error, _, cowboy_req:req()}.
|
||||
populate_request_params(_, [], Req, _, Model) ->
|
||||
{ok, Model, Req};
|
||||
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
|
||||
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
|
||||
{ok, K, V, Req} ->
|
||||
populate_request_params(OperationID, T, Req, ValidatorState, maps:put(K, V, Model));
|
||||
populate_request_params(OperationID, [ReqParamName | T], Req0, ValidatorState, Model0) ->
|
||||
case populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) of
|
||||
{ok, V, Req} ->
|
||||
Model = maps:put(ReqParamName, V, Model0),
|
||||
populate_request_params(OperationID, T, Req, ValidatorState, Model);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
||||
#{rules := Rules, source := Source} = request_param_info(OperationID, Name),
|
||||
case get_value(Source, Name, Req0) of
|
||||
-spec populate_request_param(
|
||||
operation_id(), request_param(), cowboy_req:req(), jesse_state:state()) ->
|
||||
{ok, term(), cowboy_req:req()} | {error, term(), cowboy_req:req()}.
|
||||
populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) ->
|
||||
#{rules := Rules, source := Source} = request_param_info(OperationID, ReqParamName),
|
||||
case get_value(Source, ReqParamName, Req0) of
|
||||
{error, Reason, Req} ->
|
||||
{error, Reason, Req};
|
||||
{Value, Req} ->
|
||||
case prepare_param(Rules, Name, Value, ValidatorState) of
|
||||
{ok, Result} -> {ok, Name, Result, Req};
|
||||
case prepare_param(Rules, ReqParamName, Value, ValidatorState) of
|
||||
{ok, Result} -> {ok, Result, Req};
|
||||
{error, Reason} ->
|
||||
{error, Reason, Req}
|
||||
end
|
||||
@@ -664,112 +671,111 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
||||
|
||||
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
|
||||
[
|
||||
validate(schema, ReturnBaseType, Item, ValidatorState)
|
||||
validate(schema, Item, ReturnBaseType, ValidatorState)
|
||||
|| Item <- Body];
|
||||
|
||||
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
|
||||
validate(schema, ReturnBaseType, Body, ValidatorState).
|
||||
validate(schema, Body, ReturnBaseType, ValidatorState).
|
||||
|
||||
validate(Rule = required, Name, Value, _ValidatorState) ->
|
||||
case Value of
|
||||
undefined -> validation_error(Rule, Name);
|
||||
_ -> ok
|
||||
-spec validate(rule(), term(), request_param(), jesse_state:state()) ->
|
||||
ok | {ok, term()}.
|
||||
validate(required, undefined, ReqParamName, _) ->
|
||||
validation_error(required, ReqParamName, undefined);
|
||||
validate(required, _Value, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate(not_required, _Value, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate(_, undefined, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate({type, boolean}, Value, _ReqParamName, _) when is_boolean(Value) ->
|
||||
{ok, Value};
|
||||
validate({type, integer}, Value, _ReqParamName, _) when is_integer(Value) ->
|
||||
ok;
|
||||
validate({type, float}, Value, _ReqParamName, _) when is_float(Value) ->
|
||||
ok;
|
||||
validate({type, binary}, Value, _ReqParamName, _) when is_binary(Value) ->
|
||||
ok;
|
||||
validate(Rule = {type, binary}, Value, ReqParamName, _) ->
|
||||
validation_error(Rule, ReqParamName, Value);
|
||||
validate(Rule = {type, boolean}, Value, ReqParamName, _) ->
|
||||
case binary_to_lower(Value) of
|
||||
<<"true">> -> {ok, true};
|
||||
<<"false">> -> {ok, false};
|
||||
_ -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(not_required, _Name, _Value, _ValidatorState) ->
|
||||
ok;
|
||||
validate(_, _Name, undefined, _ValidatorState) ->
|
||||
ok;
|
||||
validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, integer}, Value, ReqParamName, _) ->
|
||||
try
|
||||
{ok, to_int(Value)}
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, float}, Value, ReqParamName, _) ->
|
||||
try
|
||||
{ok, to_float(Value)}
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, date}, Value, ReqParamName, _) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
|
||||
{ok, Value};
|
||||
validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
|
||||
V = binary_to_lower(Value),
|
||||
try
|
||||
case binary_to_existing_atom(V, utf8) of
|
||||
B when is_boolean(B) -> {ok, B};
|
||||
_ -> validation_error(Rule, Name)
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
end;
|
||||
validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, datetime}, Value, ReqParamName, _) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
end;
|
||||
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {enum, Values}, Value, ReqParamName, _) ->
|
||||
try
|
||||
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
|
||||
case lists:member(FormattedValue, Values) of
|
||||
true -> {ok, FormattedValue};
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {max, Max}, Value, ReqParamName, _) ->
|
||||
case Value =< Max of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {exclusive_max, ExclusiveMax}, Value, ReqParamName, _) ->
|
||||
case Value > ExclusiveMax of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {min, Min}, Value, ReqParamName, _) ->
|
||||
case Value >= Min of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {exclusive_min, ExclusiveMin}, Value, ReqParamName, _) ->
|
||||
case Value =< ExclusiveMin of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {max_length, MaxLength}, Value, ReqParamName, _) ->
|
||||
case size(Value) =< MaxLength of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {min_length, MinLength}, Value, ReqParamName, _) ->
|
||||
case size(Value) >= MinLength of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {pattern, Pattern}, Value, ReqParamName, _) ->
|
||||
{ok, MP} = re:compile(Pattern),
|
||||
case re:run(Value, MP) of
|
||||
{match, _} -> ok;
|
||||
_ -> validation_error(Rule, Name)
|
||||
_ -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
|
||||
validate(Rule = schema, Value, ReqParamName, ValidatorState) ->
|
||||
Definition = iolist_to_binary(["#/components/schemas/", atom_to_binary(ReqParamName)]),
|
||||
try
|
||||
_ = validate_with_schema(Value, Definition, ValidatorState),
|
||||
ok
|
||||
@@ -779,7 +785,7 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
type => schema_invalid,
|
||||
error => Error
|
||||
},
|
||||
validation_error(Rule, Name, Info);
|
||||
validation_error(Rule, ReqParamName, Value, Info);
|
||||
throw:[{data_invalid, Schema, Error, _, Path} | _] ->
|
||||
Info = #{
|
||||
type => data_invalid,
|
||||
@@ -787,25 +793,25 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
schema => Schema,
|
||||
path => Path
|
||||
},
|
||||
validation_error(Rule, Name, Info)
|
||||
validation_error(Rule, ReqParamName, Value, Info)
|
||||
end;
|
||||
validate(Rule, Name, _Value, _ValidatorState) ->
|
||||
?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
|
||||
validate(Rule, _Value, ReqParamName, _) ->
|
||||
?LOG_INFO(#{what => "Cannot validate rule", name => ReqParamName, rule => Rule}),
|
||||
error({unknown_validation_rule, Rule}).
|
||||
|
||||
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
|
||||
validation_error(ViolatedRule, Name) ->
|
||||
validation_error(ViolatedRule, Name, #{}).
|
||||
-spec validation_error(rule(), request_param(), term()) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Value) ->
|
||||
validation_error(ViolatedRule, Name, Value, #{}).
|
||||
|
||||
-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Info) ->
|
||||
throw({wrong_param, Name, ViolatedRule, Info}).
|
||||
-spec validation_error(rule(), request_param(), term(), Info :: #{_ := _}) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Value, Info) ->
|
||||
throw({wrong_param, Name, Value, ViolatedRule, Info}).
|
||||
|
||||
-spec get_value(body | qs_val | header | binding, Name :: any(), Req0 :: cowboy_req:req()) ->
|
||||
{Value :: any(), Req :: cowboy_req:req()} |
|
||||
{error, Reason :: any(), Req :: cowboy_req:req()}.
|
||||
-spec get_value(body | qs_val | header | binding, request_param(), cowboy_req:req()) ->
|
||||
{any(), cowboy_req:req()} |
|
||||
{error, any(), cowboy_req:req()}.
|
||||
get_value(body, _Name, Req0) ->
|
||||
{ok, Body, Req} = cowboy_req:read_body(Req0),
|
||||
{ok, Body, Req} = read_entire_body(Req0),
|
||||
case prepare_body(Body) of
|
||||
{error, Reason} ->
|
||||
{error, Reason, Req};
|
||||
@@ -821,17 +827,30 @@ get_value(header, Name, Req) ->
|
||||
Value = maps:get(to_header(Name), Headers, undefined),
|
||||
{Value, Req};
|
||||
get_value(binding, Name, Req) ->
|
||||
Value = cowboy_req:binding(to_binding(Name), Req),
|
||||
Value = cowboy_req:binding(Name, Req),
|
||||
{Value, Req}.
|
||||
|
||||
-spec read_entire_body(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
read_entire_body(Req) ->
|
||||
read_entire_body(Req, []).
|
||||
|
||||
-spec read_entire_body(cowboy_req:req(), iodata()) -> {ok, binary(), cowboy_req:req()}.
|
||||
read_entire_body(Request, Acc) -> % {
|
||||
case cowboy_req:read_body(Request) of
|
||||
{ok, Data, NewRequest} ->
|
||||
{ok, iolist_to_binary(lists:reverse([Data | Acc])), NewRequest};
|
||||
{more, Data, NewRequest} ->
|
||||
read_entire_body(NewRequest, [Data | Acc])
|
||||
end.
|
||||
|
||||
prepare_body(<<>>) ->
|
||||
<<>>;
|
||||
prepare_body(Body) ->
|
||||
try
|
||||
json:decode(Body)
|
||||
catch
|
||||
error:_ ->
|
||||
{error, {invalid_body, not_json, Body}}
|
||||
error:Error ->
|
||||
{error, {invalid_json, Body, Error}}
|
||||
end.
|
||||
|
||||
validate_with_schema(Body, Definition, ValidatorState) ->
|
||||
@@ -841,18 +860,17 @@ validate_with_schema(Body, Definition, ValidatorState) ->
|
||||
ValidatorState
|
||||
).
|
||||
|
||||
prepare_param(Rules, Name, Value, ValidatorState) ->
|
||||
-spec prepare_param([rule()], request_param(), term(), jesse_state:state()) ->
|
||||
{ok, term()} | {error, Reason :: any()}.
|
||||
prepare_param(Rules, ReqParamName, Value, ValidatorState) ->
|
||||
Fun = fun(Rule, Acc) ->
|
||||
case validate(Rule, Acc, ReqParamName, ValidatorState) of
|
||||
ok -> Acc;
|
||||
{ok, Prepared} -> Prepared
|
||||
end
|
||||
end,
|
||||
try
|
||||
Result = lists:foldl(
|
||||
fun(Rule, Acc) ->
|
||||
case validate(Rule, Name, Acc, ValidatorState) of
|
||||
ok -> Acc;
|
||||
{ok, Prepared} -> Prepared
|
||||
end
|
||||
end,
|
||||
Value,
|
||||
Rules
|
||||
),
|
||||
Result = lists:foldl(Fun, Value, Rules),
|
||||
{ok, Result}
|
||||
catch
|
||||
throw:Reason ->
|
||||
@@ -866,40 +884,28 @@ to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
|
||||
to_binary(V) when is_integer(V) -> integer_to_binary(V);
|
||||
to_binary(V) when is_float(V) -> float_to_binary(V).
|
||||
|
||||
-spec to_list(iodata() | atom() | number()) -> binary().
|
||||
to_list(V) when is_list(V) -> V;
|
||||
to_list(V) when is_binary(V) -> binary_to_list(V);
|
||||
to_list(V) when is_atom(V) -> atom_to_list(V);
|
||||
to_list(V) when is_integer(V) -> integer_to_list(V);
|
||||
to_list(V) when is_float(V) -> float_to_list(V).
|
||||
-spec to_float(binary() | list()) -> integer().
|
||||
to_float(Data) when is_binary(Data) ->
|
||||
binary_to_float(Data);
|
||||
to_float(Data) when is_list(Data) ->
|
||||
list_to_float(Data).
|
||||
|
||||
-spec to_float(iodata()) -> float().
|
||||
to_float(V) ->
|
||||
binary_to_float(iolist_to_binary([V])).
|
||||
|
||||
-spec to_int(integer() | binary() | list()) -> integer().
|
||||
to_int(Data) when is_integer(Data) ->
|
||||
Data;
|
||||
-spec to_int(binary() | list()) -> integer().
|
||||
to_int(Data) when is_binary(Data) ->
|
||||
binary_to_integer(Data);
|
||||
to_int(Data) when is_list(Data) ->
|
||||
list_to_integer(Data).
|
||||
|
||||
-spec to_header(iodata() | atom() | number()) -> binary().
|
||||
-spec to_header(request_param()) -> binary().
|
||||
to_header(Name) ->
|
||||
to_binary(string:lowercase(to_binary(Name))).
|
||||
to_binary(string:lowercase(atom_to_binary(Name, utf8))).
|
||||
|
||||
binary_to_lower(V) when is_binary(V) ->
|
||||
string:lowercase(V).
|
||||
|
||||
-spec to_qs(iodata() | atom() | number()) -> binary().
|
||||
-spec to_qs(request_param()) -> binary().
|
||||
to_qs(Name) ->
|
||||
to_binary(Name).
|
||||
|
||||
-spec to_binding(iodata() | atom() | number()) -> atom().
|
||||
to_binding(Name) ->
|
||||
Prepared = to_binary(Name),
|
||||
binary_to_existing_atom(Prepared, utf8).
|
||||
atom_to_binary(Name, utf8).
|
||||
|
||||
-spec get_opt(any(), []) -> any().
|
||||
get_opt(Key, Opts) ->
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
%% basic handler
|
||||
-module(openapi_auth_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `POST` to `/auth/http/basic`, OperationId: `TestAuthHttpBasic`:
|
||||
To test HTTP basic authentication.
|
||||
|
||||
- `POST` to `/auth/http/bearer`, OperationId: `TestAuthHttpBearer`:
|
||||
To test HTTP bearer authentication.
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
%% basic handler
|
||||
-module(openapi_body_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `POST` to `/binary/gif`, OperationId: `TestBinaryGif`:
|
||||
Test binary (gif) response body.
|
||||
|
||||
- `POST` to `/body/application/octetstream/binary`, OperationId: `TestBodyApplicationOctetstreamBinary`:
|
||||
Test body parameter(s).
|
||||
|
||||
- `POST` to `/body/application/octetstream/array_of_binary`, OperationId: `TestBodyMultipartFormdataArrayOfBinary`:
|
||||
Test array of binary in multipart mime.
|
||||
|
||||
- `POST` to `/body/application/octetstream/single_binary`, OperationId: `TestBodyMultipartFormdataSingleBinary`:
|
||||
Test single binary in multipart mime.
|
||||
|
||||
- `POST` to `/echo/body/allOf/Pet`, OperationId: `TestEchoBodyAllOfPet`:
|
||||
Test body parameter(s).
|
||||
|
||||
- `POST` to `/echo/body/FreeFormObject/response_string`, OperationId: `TestEchoBodyFreeFormObjectResponseString`:
|
||||
Test free form object.
|
||||
|
||||
- `POST` to `/echo/body/Pet`, OperationId: `TestEchoBodyPet`:
|
||||
Test body parameter(s).
|
||||
|
||||
- `POST` to `/echo/body/Pet/response_string`, OperationId: `TestEchoBodyPetResponseString`:
|
||||
Test empty response body.
|
||||
|
||||
- `POST` to `/echo/body/string_enum`, OperationId: `TestEchoBodyStringEnum`:
|
||||
Test string enum response body.
|
||||
|
||||
- `POST` to `/echo/body/Tag/response_string`, OperationId: `TestEchoBodyTagResponseString`:
|
||||
Test empty json (request body).
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
%% basic handler
|
||||
-module(openapi_form_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `POST` to `/form/integer/boolean/string`, OperationId: `TestFormIntegerBooleanString`:
|
||||
Test form parameter(s).
|
||||
|
||||
- `POST` to `/form/object/multipart`, OperationId: `TestFormObjectMultipart`:
|
||||
Test form parameter(s) for multipart schema.
|
||||
|
||||
- `POST` to `/form/oneof`, OperationId: `TestFormOneof`:
|
||||
Test form parameter(s) for oneOf schema.
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
%% basic handler
|
||||
-module(openapi_header_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `GET` to `/header/integer/boolean/string/enums`, OperationId: `TestHeaderIntegerBooleanStringEnums`:
|
||||
Test header parameter(s).
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
%% basic handler
|
||||
-module(openapi_path_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `GET` to `/path/string/:path_string/integer/:path_integer/:enum_nonref_string_path/:enum_ref_string_path`, OperationId: `TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}`:
|
||||
Test path parameter(s).
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
%% basic handler
|
||||
-module(openapi_query_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `GET` to `/query/enum_ref_string`, OperationId: `TestEnumRefString`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/datetime/date/string`, OperationId: `TestQueryDatetimeDateString`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/integer/boolean/string`, OperationId: `TestQueryIntegerBooleanString`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_deepObject/explode_true/object`, OperationId: `TestQueryStyleDeepObjectExplodeTrueObject`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_deepObject/explode_true/object/allOf`, OperationId: `TestQueryStyleDeepObjectExplodeTrueObjectAllOf`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_form/explode_false/array_integer`, OperationId: `TestQueryStyleFormExplodeFalseArrayInteger`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_form/explode_false/array_string`, OperationId: `TestQueryStyleFormExplodeFalseArrayString`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_form/explode_true/array_string`, OperationId: `TestQueryStyleFormExplodeTrueArrayString`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_form/explode_true/object`, OperationId: `TestQueryStyleFormExplodeTrueObject`:
|
||||
Test query parameter(s).
|
||||
|
||||
- `GET` to `/query/style_form/explode_true/object/allOf`, OperationId: `TestQueryStyleFormExplodeTrueObjectAllOf`:
|
||||
Test query parameter(s).
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -19,152 +19,219 @@ get_paths(LogicHandler) ->
|
||||
|
||||
group_paths() ->
|
||||
maps:fold(
|
||||
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
||||
case maps:find(Path, Acc) of
|
||||
fun(OperationID, #{servers := Servers, base_path := BasePath, path := Path,
|
||||
method := Method, handler := Handler}, Acc) ->
|
||||
FullPaths = build_full_paths(Servers, BasePath, Path),
|
||||
merge_paths(FullPaths, OperationID, Method, Handler, Acc)
|
||||
end, #{}, get_operations()).
|
||||
|
||||
build_full_paths([], BasePath, Path) ->
|
||||
[lists:append([BasePath, Path])];
|
||||
build_full_paths(Servers, _BasePath, Path) ->
|
||||
[lists:append([Server, Path]) || Server <- Servers ].
|
||||
|
||||
merge_paths(FullPaths, OperationID, Method, Handler, Acc) ->
|
||||
lists:foldl(
|
||||
fun(Path, Acc0) ->
|
||||
case maps:find(Path, Acc0) of
|
||||
{ok, PathInfo0 = #{operations := Operations0}} ->
|
||||
Operations = Operations0#{Method => OperationID},
|
||||
PathInfo = PathInfo0#{operations => Operations},
|
||||
Acc#{Path => PathInfo};
|
||||
Acc0#{Path => PathInfo};
|
||||
error ->
|
||||
Operations = #{Method => OperationID},
|
||||
PathInfo = #{handler => Handler, operations => Operations},
|
||||
Acc#{Path => PathInfo}
|
||||
Acc0#{Path => PathInfo}
|
||||
end
|
||||
end, #{}, get_operations()).
|
||||
end, Acc, FullPaths).
|
||||
|
||||
get_operations() ->
|
||||
#{
|
||||
'TestAuthHttpBasic' => #{
|
||||
'TestAuthHttpBasic' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/auth/http/basic",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_auth_handler'
|
||||
},
|
||||
'TestAuthHttpBearer' => #{
|
||||
'TestAuthHttpBearer' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/auth/http/bearer",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_auth_handler'
|
||||
},
|
||||
'TestBinaryGif' => #{
|
||||
'TestBinaryGif' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/binary/gif",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestBodyApplicationOctetstreamBinary' => #{
|
||||
'TestBodyApplicationOctetstreamBinary' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/body/application/octetstream/binary",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestBodyMultipartFormdataArrayOfBinary' => #{
|
||||
'TestBodyMultipartFormdataArrayOfBinary' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/body/application/octetstream/array_of_binary",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestBodyMultipartFormdataSingleBinary' => #{
|
||||
'TestBodyMultipartFormdataSingleBinary' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/body/application/octetstream/single_binary",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestEchoBodyAllOfPet' => #{
|
||||
'TestEchoBodyAllOfPet' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/echo/body/allOf/Pet",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestEchoBodyFreeFormObjectResponseString' => #{
|
||||
'TestEchoBodyFreeFormObjectResponseString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/echo/body/FreeFormObject/response_string",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestEchoBodyPet' => #{
|
||||
'TestEchoBodyPet' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/echo/body/Pet",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestEchoBodyPetResponseString' => #{
|
||||
'TestEchoBodyPetResponseString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/echo/body/Pet/response_string",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestEchoBodyStringEnum' => #{
|
||||
'TestEchoBodyStringEnum' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/echo/body/string_enum",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestEchoBodyTagResponseString' => #{
|
||||
'TestEchoBodyTagResponseString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/echo/body/Tag/response_string",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_body_handler'
|
||||
},
|
||||
'TestFormIntegerBooleanString' => #{
|
||||
'TestFormIntegerBooleanString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/form/integer/boolean/string",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_form_handler'
|
||||
},
|
||||
'TestFormObjectMultipart' => #{
|
||||
'TestFormObjectMultipart' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/form/object/multipart",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_form_handler'
|
||||
},
|
||||
'TestFormOneof' => #{
|
||||
'TestFormOneof' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/form/oneof",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_form_handler'
|
||||
},
|
||||
'TestHeaderIntegerBooleanStringEnums' => #{
|
||||
'TestHeaderIntegerBooleanStringEnums' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/header/integer/boolean/string/enums",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_header_handler'
|
||||
},
|
||||
'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}' => #{
|
||||
'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/path/string/:path_string/integer/:path_integer/:enum_nonref_string_path/:enum_ref_string_path",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_path_handler'
|
||||
},
|
||||
'TestEnumRefString' => #{
|
||||
'TestEnumRefString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/enum_ref_string",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryDatetimeDateString' => #{
|
||||
'TestQueryDatetimeDateString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/datetime/date/string",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryIntegerBooleanString' => #{
|
||||
'TestQueryIntegerBooleanString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/integer/boolean/string",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleDeepObjectExplodeTrueObject' => #{
|
||||
'TestQueryStyleDeepObjectExplodeTrueObject' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_deepObject/explode_true/object",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleDeepObjectExplodeTrueObjectAllOf' => #{
|
||||
'TestQueryStyleDeepObjectExplodeTrueObjectAllOf' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_deepObject/explode_true/object/allOf",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleFormExplodeFalseArrayInteger' => #{
|
||||
'TestQueryStyleFormExplodeFalseArrayInteger' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_form/explode_false/array_integer",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleFormExplodeFalseArrayString' => #{
|
||||
'TestQueryStyleFormExplodeFalseArrayString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_form/explode_false/array_string",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleFormExplodeTrueArrayString' => #{
|
||||
'TestQueryStyleFormExplodeTrueArrayString' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_form/explode_true/array_string",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleFormExplodeTrueObject' => #{
|
||||
'TestQueryStyleFormExplodeTrueObject' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_form/explode_true/object",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
},
|
||||
'TestQueryStyleFormExplodeTrueObjectAllOf' => #{
|
||||
'TestQueryStyleFormExplodeTrueObjectAllOf' => #{
|
||||
servers => [],
|
||||
base_path => "",
|
||||
path => "/query/style_form/explode_true/object/allOf",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_query_handler'
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
-module(openapi_server).
|
||||
-moduledoc """
|
||||
Echo Server API
|
||||
""".
|
||||
|
||||
-define(DEFAULT_LOGIC_HANDLER, openapi_logic_handler).
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ accept_callback(Class, OperationID, Req, Context) ->
|
||||
|
||||
-export_type([operation_id/0]).
|
||||
|
||||
-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
|
||||
-dialyzer({nowarn_function, [to_binary/1, validate_response_body/4]}).
|
||||
|
||||
-type rule() ::
|
||||
{type, binary} |
|
||||
@@ -484,24 +484,31 @@ request_param_info('UpdateUser', 'User') ->
|
||||
request_param_info(OperationID, Name) ->
|
||||
error({unknown_param, OperationID, Name}).
|
||||
|
||||
-spec populate_request_params(
|
||||
operation_id(), [request_param()], cowboy_req:req(), jesse_state:state(), map()) ->
|
||||
{ok, map(), cowboy_req:req()} | {error, _, cowboy_req:req()}.
|
||||
populate_request_params(_, [], Req, _, Model) ->
|
||||
{ok, Model, Req};
|
||||
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
|
||||
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
|
||||
{ok, K, V, Req} ->
|
||||
populate_request_params(OperationID, T, Req, ValidatorState, maps:put(K, V, Model));
|
||||
populate_request_params(OperationID, [ReqParamName | T], Req0, ValidatorState, Model0) ->
|
||||
case populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) of
|
||||
{ok, V, Req} ->
|
||||
Model = maps:put(ReqParamName, V, Model0),
|
||||
populate_request_params(OperationID, T, Req, ValidatorState, Model);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
||||
#{rules := Rules, source := Source} = request_param_info(OperationID, Name),
|
||||
case get_value(Source, Name, Req0) of
|
||||
-spec populate_request_param(
|
||||
operation_id(), request_param(), cowboy_req:req(), jesse_state:state()) ->
|
||||
{ok, term(), cowboy_req:req()} | {error, term(), cowboy_req:req()}.
|
||||
populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) ->
|
||||
#{rules := Rules, source := Source} = request_param_info(OperationID, ReqParamName),
|
||||
case get_value(Source, ReqParamName, Req0) of
|
||||
{error, Reason, Req} ->
|
||||
{error, Reason, Req};
|
||||
{Value, Req} ->
|
||||
case prepare_param(Rules, Name, Value, ValidatorState) of
|
||||
{ok, Result} -> {ok, Name, Result, Req};
|
||||
case prepare_param(Rules, ReqParamName, Value, ValidatorState) of
|
||||
{ok, Result} -> {ok, Result, Req};
|
||||
{error, Reason} ->
|
||||
{error, Reason, Req}
|
||||
end
|
||||
@@ -511,112 +518,111 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
||||
|
||||
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
|
||||
[
|
||||
validate(schema, ReturnBaseType, Item, ValidatorState)
|
||||
validate(schema, Item, ReturnBaseType, ValidatorState)
|
||||
|| Item <- Body];
|
||||
|
||||
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
|
||||
validate(schema, ReturnBaseType, Body, ValidatorState).
|
||||
validate(schema, Body, ReturnBaseType, ValidatorState).
|
||||
|
||||
validate(Rule = required, Name, Value, _ValidatorState) ->
|
||||
case Value of
|
||||
undefined -> validation_error(Rule, Name);
|
||||
_ -> ok
|
||||
-spec validate(rule(), term(), request_param(), jesse_state:state()) ->
|
||||
ok | {ok, term()}.
|
||||
validate(required, undefined, ReqParamName, _) ->
|
||||
validation_error(required, ReqParamName, undefined);
|
||||
validate(required, _Value, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate(not_required, _Value, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate(_, undefined, _ReqParamName, _) ->
|
||||
ok;
|
||||
validate({type, boolean}, Value, _ReqParamName, _) when is_boolean(Value) ->
|
||||
{ok, Value};
|
||||
validate({type, integer}, Value, _ReqParamName, _) when is_integer(Value) ->
|
||||
ok;
|
||||
validate({type, float}, Value, _ReqParamName, _) when is_float(Value) ->
|
||||
ok;
|
||||
validate({type, binary}, Value, _ReqParamName, _) when is_binary(Value) ->
|
||||
ok;
|
||||
validate(Rule = {type, binary}, Value, ReqParamName, _) ->
|
||||
validation_error(Rule, ReqParamName, Value);
|
||||
validate(Rule = {type, boolean}, Value, ReqParamName, _) ->
|
||||
case binary_to_lower(Value) of
|
||||
<<"true">> -> {ok, true};
|
||||
<<"false">> -> {ok, false};
|
||||
_ -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(not_required, _Name, _Value, _ValidatorState) ->
|
||||
ok;
|
||||
validate(_, _Name, undefined, _ValidatorState) ->
|
||||
ok;
|
||||
validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, integer}, Value, ReqParamName, _) ->
|
||||
try
|
||||
{ok, to_int(Value)}
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, float}, Value, ReqParamName, _) ->
|
||||
try
|
||||
{ok, to_float(Value)}
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, date}, Value, ReqParamName, _) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
|
||||
{ok, Value};
|
||||
validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
|
||||
V = binary_to_lower(Value),
|
||||
try
|
||||
case binary_to_existing_atom(V, utf8) of
|
||||
B when is_boolean(B) -> {ok, B};
|
||||
_ -> validation_error(Rule, Name)
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
end;
|
||||
validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {type, datetime}, Value, ReqParamName, _) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
|
||||
case is_binary(Value) of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
end;
|
||||
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {enum, Values}, Value, ReqParamName, _) ->
|
||||
try
|
||||
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
|
||||
case lists:member(FormattedValue, Values) of
|
||||
true -> {ok, FormattedValue};
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
validation_error(Rule, Name)
|
||||
validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {max, Max}, Value, ReqParamName, _) ->
|
||||
case Value =< Max of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {exclusive_max, ExclusiveMax}, Value, ReqParamName, _) ->
|
||||
case Value > ExclusiveMax of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {min, Min}, Value, ReqParamName, _) ->
|
||||
case Value >= Min of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {exclusive_min, ExclusiveMin}, Value, ReqParamName, _) ->
|
||||
case Value =< ExclusiveMin of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {max_length, MaxLength}, Value, ReqParamName, _) ->
|
||||
case size(Value) =< MaxLength of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {min_length, MinLength}, Value, ReqParamName, _) ->
|
||||
case size(Value) >= MinLength of
|
||||
true -> ok;
|
||||
false -> validation_error(Rule, Name)
|
||||
false -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
|
||||
validate(Rule = {pattern, Pattern}, Value, ReqParamName, _) ->
|
||||
{ok, MP} = re:compile(Pattern),
|
||||
case re:run(Value, MP) of
|
||||
{match, _} -> ok;
|
||||
_ -> validation_error(Rule, Name)
|
||||
_ -> validation_error(Rule, ReqParamName, Value)
|
||||
end;
|
||||
validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
|
||||
validate(Rule = schema, Value, ReqParamName, ValidatorState) ->
|
||||
Definition = iolist_to_binary(["#/components/schemas/", atom_to_binary(ReqParamName)]),
|
||||
try
|
||||
_ = validate_with_schema(Value, Definition, ValidatorState),
|
||||
ok
|
||||
@@ -626,7 +632,7 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
type => schema_invalid,
|
||||
error => Error
|
||||
},
|
||||
validation_error(Rule, Name, Info);
|
||||
validation_error(Rule, ReqParamName, Value, Info);
|
||||
throw:[{data_invalid, Schema, Error, _, Path} | _] ->
|
||||
Info = #{
|
||||
type => data_invalid,
|
||||
@@ -634,25 +640,25 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
||||
schema => Schema,
|
||||
path => Path
|
||||
},
|
||||
validation_error(Rule, Name, Info)
|
||||
validation_error(Rule, ReqParamName, Value, Info)
|
||||
end;
|
||||
validate(Rule, Name, _Value, _ValidatorState) ->
|
||||
?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
|
||||
validate(Rule, _Value, ReqParamName, _) ->
|
||||
?LOG_INFO(#{what => "Cannot validate rule", name => ReqParamName, rule => Rule}),
|
||||
error({unknown_validation_rule, Rule}).
|
||||
|
||||
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
|
||||
validation_error(ViolatedRule, Name) ->
|
||||
validation_error(ViolatedRule, Name, #{}).
|
||||
-spec validation_error(rule(), request_param(), term()) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Value) ->
|
||||
validation_error(ViolatedRule, Name, Value, #{}).
|
||||
|
||||
-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Info) ->
|
||||
throw({wrong_param, Name, ViolatedRule, Info}).
|
||||
-spec validation_error(rule(), request_param(), term(), Info :: #{_ := _}) -> no_return().
|
||||
validation_error(ViolatedRule, Name, Value, Info) ->
|
||||
throw({wrong_param, Name, Value, ViolatedRule, Info}).
|
||||
|
||||
-spec get_value(body | qs_val | header | binding, Name :: any(), Req0 :: cowboy_req:req()) ->
|
||||
{Value :: any(), Req :: cowboy_req:req()} |
|
||||
{error, Reason :: any(), Req :: cowboy_req:req()}.
|
||||
-spec get_value(body | qs_val | header | binding, request_param(), cowboy_req:req()) ->
|
||||
{any(), cowboy_req:req()} |
|
||||
{error, any(), cowboy_req:req()}.
|
||||
get_value(body, _Name, Req0) ->
|
||||
{ok, Body, Req} = cowboy_req:read_body(Req0),
|
||||
{ok, Body, Req} = read_entire_body(Req0),
|
||||
case prepare_body(Body) of
|
||||
{error, Reason} ->
|
||||
{error, Reason, Req};
|
||||
@@ -668,17 +674,30 @@ get_value(header, Name, Req) ->
|
||||
Value = maps:get(to_header(Name), Headers, undefined),
|
||||
{Value, Req};
|
||||
get_value(binding, Name, Req) ->
|
||||
Value = cowboy_req:binding(to_binding(Name), Req),
|
||||
Value = cowboy_req:binding(Name, Req),
|
||||
{Value, Req}.
|
||||
|
||||
-spec read_entire_body(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
read_entire_body(Req) ->
|
||||
read_entire_body(Req, []).
|
||||
|
||||
-spec read_entire_body(cowboy_req:req(), iodata()) -> {ok, binary(), cowboy_req:req()}.
|
||||
read_entire_body(Request, Acc) -> % {
|
||||
case cowboy_req:read_body(Request) of
|
||||
{ok, Data, NewRequest} ->
|
||||
{ok, iolist_to_binary(lists:reverse([Data | Acc])), NewRequest};
|
||||
{more, Data, NewRequest} ->
|
||||
read_entire_body(NewRequest, [Data | Acc])
|
||||
end.
|
||||
|
||||
prepare_body(<<>>) ->
|
||||
<<>>;
|
||||
prepare_body(Body) ->
|
||||
try
|
||||
json:decode(Body)
|
||||
catch
|
||||
error:_ ->
|
||||
{error, {invalid_body, not_json, Body}}
|
||||
error:Error ->
|
||||
{error, {invalid_json, Body, Error}}
|
||||
end.
|
||||
|
||||
validate_with_schema(Body, Definition, ValidatorState) ->
|
||||
@@ -688,18 +707,17 @@ validate_with_schema(Body, Definition, ValidatorState) ->
|
||||
ValidatorState
|
||||
).
|
||||
|
||||
prepare_param(Rules, Name, Value, ValidatorState) ->
|
||||
-spec prepare_param([rule()], request_param(), term(), jesse_state:state()) ->
|
||||
{ok, term()} | {error, Reason :: any()}.
|
||||
prepare_param(Rules, ReqParamName, Value, ValidatorState) ->
|
||||
Fun = fun(Rule, Acc) ->
|
||||
case validate(Rule, Acc, ReqParamName, ValidatorState) of
|
||||
ok -> Acc;
|
||||
{ok, Prepared} -> Prepared
|
||||
end
|
||||
end,
|
||||
try
|
||||
Result = lists:foldl(
|
||||
fun(Rule, Acc) ->
|
||||
case validate(Rule, Name, Acc, ValidatorState) of
|
||||
ok -> Acc;
|
||||
{ok, Prepared} -> Prepared
|
||||
end
|
||||
end,
|
||||
Value,
|
||||
Rules
|
||||
),
|
||||
Result = lists:foldl(Fun, Value, Rules),
|
||||
{ok, Result}
|
||||
catch
|
||||
throw:Reason ->
|
||||
@@ -713,40 +731,28 @@ to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
|
||||
to_binary(V) when is_integer(V) -> integer_to_binary(V);
|
||||
to_binary(V) when is_float(V) -> float_to_binary(V).
|
||||
|
||||
-spec to_list(iodata() | atom() | number()) -> binary().
|
||||
to_list(V) when is_list(V) -> V;
|
||||
to_list(V) when is_binary(V) -> binary_to_list(V);
|
||||
to_list(V) when is_atom(V) -> atom_to_list(V);
|
||||
to_list(V) when is_integer(V) -> integer_to_list(V);
|
||||
to_list(V) when is_float(V) -> float_to_list(V).
|
||||
-spec to_float(binary() | list()) -> integer().
|
||||
to_float(Data) when is_binary(Data) ->
|
||||
binary_to_float(Data);
|
||||
to_float(Data) when is_list(Data) ->
|
||||
list_to_float(Data).
|
||||
|
||||
-spec to_float(iodata()) -> float().
|
||||
to_float(V) ->
|
||||
binary_to_float(iolist_to_binary([V])).
|
||||
|
||||
-spec to_int(integer() | binary() | list()) -> integer().
|
||||
to_int(Data) when is_integer(Data) ->
|
||||
Data;
|
||||
-spec to_int(binary() | list()) -> integer().
|
||||
to_int(Data) when is_binary(Data) ->
|
||||
binary_to_integer(Data);
|
||||
to_int(Data) when is_list(Data) ->
|
||||
list_to_integer(Data).
|
||||
|
||||
-spec to_header(iodata() | atom() | number()) -> binary().
|
||||
-spec to_header(request_param()) -> binary().
|
||||
to_header(Name) ->
|
||||
to_binary(string:lowercase(to_binary(Name))).
|
||||
to_binary(string:lowercase(atom_to_binary(Name, utf8))).
|
||||
|
||||
binary_to_lower(V) when is_binary(V) ->
|
||||
string:lowercase(V).
|
||||
|
||||
-spec to_qs(iodata() | atom() | number()) -> binary().
|
||||
-spec to_qs(request_param()) -> binary().
|
||||
to_qs(Name) ->
|
||||
to_binary(Name).
|
||||
|
||||
-spec to_binding(iodata() | atom() | number()) -> atom().
|
||||
to_binding(Name) ->
|
||||
Prepared = to_binary(Name),
|
||||
binary_to_existing_atom(Prepared, utf8).
|
||||
atom_to_binary(Name, utf8).
|
||||
|
||||
-spec get_opt(any(), []) -> any().
|
||||
get_opt(Key, Opts) ->
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
%% basic handler
|
||||
-module(openapi_pet_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `POST` to `/pet`, OperationId: `AddPet`:
|
||||
Add a new pet to the store.
|
||||
|
||||
- `DELETE` to `/pet/:petId`, OperationId: `DeletePet`:
|
||||
Deletes a pet.
|
||||
|
||||
- `GET` to `/pet/findByStatus`, OperationId: `FindPetsByStatus`:
|
||||
Finds Pets by status.
|
||||
|
||||
- `GET` to `/pet/findByTags`, OperationId: `FindPetsByTags`:
|
||||
Finds Pets by tags.
|
||||
|
||||
- `GET` to `/pet/:petId`, OperationId: `GetPetById`:
|
||||
Find pet by ID.
|
||||
|
||||
- `PUT` to `/pet`, OperationId: `UpdatePet`:
|
||||
Update an existing pet.
|
||||
|
||||
- `POST` to `/pet/:petId`, OperationId: `UpdatePetWithForm`:
|
||||
Updates a pet in the store with form data.
|
||||
|
||||
- `POST` to `/pet/:petId/uploadImage`, OperationId: `UploadFile`:
|
||||
uploads an image.
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -19,118 +19,171 @@ get_paths(LogicHandler) ->
|
||||
|
||||
group_paths() ->
|
||||
maps:fold(
|
||||
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
||||
case maps:find(Path, Acc) of
|
||||
fun(OperationID, #{servers := Servers, base_path := BasePath, path := Path,
|
||||
method := Method, handler := Handler}, Acc) ->
|
||||
FullPaths = build_full_paths(Servers, BasePath, Path),
|
||||
merge_paths(FullPaths, OperationID, Method, Handler, Acc)
|
||||
end, #{}, get_operations()).
|
||||
|
||||
build_full_paths([], BasePath, Path) ->
|
||||
[lists:append([BasePath, Path])];
|
||||
build_full_paths(Servers, _BasePath, Path) ->
|
||||
[lists:append([Server, Path]) || Server <- Servers ].
|
||||
|
||||
merge_paths(FullPaths, OperationID, Method, Handler, Acc) ->
|
||||
lists:foldl(
|
||||
fun(Path, Acc0) ->
|
||||
case maps:find(Path, Acc0) of
|
||||
{ok, PathInfo0 = #{operations := Operations0}} ->
|
||||
Operations = Operations0#{Method => OperationID},
|
||||
PathInfo = PathInfo0#{operations => Operations},
|
||||
Acc#{Path => PathInfo};
|
||||
Acc0#{Path => PathInfo};
|
||||
error ->
|
||||
Operations = #{Method => OperationID},
|
||||
PathInfo = #{handler => Handler, operations => Operations},
|
||||
Acc#{Path => PathInfo}
|
||||
Acc0#{Path => PathInfo}
|
||||
end
|
||||
end, #{}, get_operations()).
|
||||
end, Acc, FullPaths).
|
||||
|
||||
get_operations() ->
|
||||
#{
|
||||
'AddPet' => #{
|
||||
path => "/v2/pet",
|
||||
'AddPet' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'DeletePet' => #{
|
||||
path => "/v2/pet/:petId",
|
||||
'DeletePet' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet/:petId",
|
||||
method => <<"DELETE">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'FindPetsByStatus' => #{
|
||||
path => "/v2/pet/findByStatus",
|
||||
'FindPetsByStatus' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet/findByStatus",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'FindPetsByTags' => #{
|
||||
path => "/v2/pet/findByTags",
|
||||
'FindPetsByTags' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet/findByTags",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'GetPetById' => #{
|
||||
path => "/v2/pet/:petId",
|
||||
'GetPetById' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet/:petId",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'UpdatePet' => #{
|
||||
path => "/v2/pet",
|
||||
'UpdatePet' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet",
|
||||
method => <<"PUT">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'UpdatePetWithForm' => #{
|
||||
path => "/v2/pet/:petId",
|
||||
'UpdatePetWithForm' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet/:petId",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'UploadFile' => #{
|
||||
path => "/v2/pet/:petId/uploadImage",
|
||||
'UploadFile' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/pet/:petId/uploadImage",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_pet_handler'
|
||||
},
|
||||
'DeleteOrder' => #{
|
||||
path => "/v2/store/order/:orderId",
|
||||
'DeleteOrder' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/store/order/:orderId",
|
||||
method => <<"DELETE">>,
|
||||
handler => 'openapi_store_handler'
|
||||
},
|
||||
'GetInventory' => #{
|
||||
path => "/v2/store/inventory",
|
||||
'GetInventory' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/store/inventory",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_store_handler'
|
||||
},
|
||||
'GetOrderById' => #{
|
||||
path => "/v2/store/order/:orderId",
|
||||
'GetOrderById' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/store/order/:orderId",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_store_handler'
|
||||
},
|
||||
'PlaceOrder' => #{
|
||||
path => "/v2/store/order",
|
||||
'PlaceOrder' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/store/order",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_store_handler'
|
||||
},
|
||||
'CreateUser' => #{
|
||||
path => "/v2/user",
|
||||
'CreateUser' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'CreateUsersWithArrayInput' => #{
|
||||
path => "/v2/user/createWithArray",
|
||||
'CreateUsersWithArrayInput' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/createWithArray",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'CreateUsersWithListInput' => #{
|
||||
path => "/v2/user/createWithList",
|
||||
'CreateUsersWithListInput' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/createWithList",
|
||||
method => <<"POST">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'DeleteUser' => #{
|
||||
path => "/v2/user/:username",
|
||||
'DeleteUser' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/:username",
|
||||
method => <<"DELETE">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'GetUserByName' => #{
|
||||
path => "/v2/user/:username",
|
||||
'GetUserByName' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/:username",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'LoginUser' => #{
|
||||
path => "/v2/user/login",
|
||||
'LoginUser' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/login",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'LogoutUser' => #{
|
||||
path => "/v2/user/logout",
|
||||
'LogoutUser' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/logout",
|
||||
method => <<"GET">>,
|
||||
handler => 'openapi_user_handler'
|
||||
},
|
||||
'UpdateUser' => #{
|
||||
path => "/v2/user/:username",
|
||||
'UpdateUser' => #{
|
||||
servers => [],
|
||||
base_path => "/v2",
|
||||
path => "/user/:username",
|
||||
method => <<"PUT">>,
|
||||
handler => 'openapi_user_handler'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
-module(openapi_server).
|
||||
-moduledoc """
|
||||
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
""".
|
||||
|
||||
-define(DEFAULT_LOGIC_HANDLER, openapi_logic_handler).
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
%% basic handler
|
||||
-module(openapi_store_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `DELETE` to `/store/order/:orderId`, OperationId: `DeleteOrder`:
|
||||
Delete purchase order by ID.
|
||||
|
||||
- `GET` to `/store/inventory`, OperationId: `GetInventory`:
|
||||
Returns pet inventories by status.
|
||||
|
||||
- `GET` to `/store/order/:orderId`, OperationId: `GetOrderById`:
|
||||
Find purchase order by ID.
|
||||
|
||||
- `POST` to `/store/order`, OperationId: `PlaceOrder`:
|
||||
Place an order for a pet.
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
%% basic handler
|
||||
-module(openapi_user_handler).
|
||||
-moduledoc """
|
||||
Exposes the following operation IDs:
|
||||
|
||||
- `POST` to `/user`, OperationId: `CreateUser`:
|
||||
Create user.
|
||||
|
||||
- `POST` to `/user/createWithArray`, OperationId: `CreateUsersWithArrayInput`:
|
||||
Creates list of users with given input array.
|
||||
|
||||
- `POST` to `/user/createWithList`, OperationId: `CreateUsersWithListInput`:
|
||||
Creates list of users with given input array.
|
||||
|
||||
- `DELETE` to `/user/:username`, OperationId: `DeleteUser`:
|
||||
Delete user.
|
||||
|
||||
- `GET` to `/user/:username`, OperationId: `GetUserByName`:
|
||||
Get user by user name.
|
||||
|
||||
- `GET` to `/user/login`, OperationId: `LoginUser`:
|
||||
Logs user into the system.
|
||||
|
||||
- `GET` to `/user/logout`, OperationId: `LogoutUser`:
|
||||
Logs out current logged in user session.
|
||||
|
||||
- `PUT` to `/user/:username`, OperationId: `UpdateUser`:
|
||||
Updated user.
|
||||
|
||||
""".
|
||||
|
||||
-behaviour(cowboy_rest).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user