Skip to content

Commit 77e8346

Browse files
authored
[Erlang Server] Add more type information and fix minor bugs (#19792)
* Add type information about classes and operation-ids * Remove unused logger included header * Bugfix badmatch in delete_resource handler * Bugfix: respect original indentation of operation_ids * Bugfix json schema correct refs * Add a bit more documentation * Regenerate erlang-server handlers
1 parent 9163e00 commit 77e8346

19 files changed

+896
-631
lines changed

modules/openapi-generator/src/main/resources/erlang-server/api.mustache

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,32 @@ and `validate_response/4` respectively.
88

99
For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
1010
```
11-
-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
12-
{cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
13-
accept_callback(Class, OperationID, Req, Context) ->
11+
-spec accept_callback(
12+
Class :: openapi_api:class(),
13+
OperationID :: openapi_api:operation_id(),
14+
Req :: cowboy_req:req(),
15+
Context :: openapi_logic_handler:context()) ->
16+
{openapi_logic_handler:accept_callback_return(),
17+
cowboy_req:req(),
18+
openapi_logic_handler:context()}.
19+
accept_callback(Class, OperationID, Req0, Context0) ->
1420
ValidatorState = openapi_api:prepare_validator(),
1521
case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
16-
{ok, Populated, Req1} ->
17-
{Code, Headers, Body} = openapi_logic_handler:handle_request(
18-
LogicHandler,
19-
OperationID,
20-
Req1,
21-
maps:merge(State#state.context, Populated)
22-
),
23-
_ = openapi_api:validate_response(
24-
OperationID,
25-
Code,
26-
Body,
27-
ValidatorState
28-
),
29-
PreparedBody = prepare_body(Code, Body),
30-
Response = {ok, {Code, Headers, PreparedBody}},
31-
process_response(Response, Req1, State);
22+
{ok, Model, Req1} ->
23+
Context1 = maps:merge(Context0, Model),
24+
case do_accept_callback(Class, OperationID, Req1, Context1) of
25+
{false, Req2, Context2} ->
26+
{false, Req2, Context2};
27+
{{true, Code, Body}, Req2, Context2} ->
28+
case validate_response(OperationID, Code, Body, ValidatorState) of
29+
ok ->
30+
process_response({ok, Code, Body}, Req2, Context2);
31+
{error, Reason} ->
32+
process_response({error, Reason}, Req2, Context2)
33+
end
34+
end;
3235
{error, Reason, Req1} ->
33-
process_response({error, Reason}, Req1, State)
36+
process_response({error, Reason}, Req1, Context0)
3437
end.
3538
```
3639
""".
@@ -41,10 +44,17 @@ accept_callback(Class, OperationID, Req, Context) ->
4144
-ignore_xref([populate_request/3, validate_response/4]).
4245
-ignore_xref([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
4346

44-
-type operation_id() :: atom().
47+
-type class() ::
48+
{{#apiInfo}}{{#apis}}{{#operations}} {{^-first}}| {{/-first}}'{{pathPrefix}}'{{#-last}}.{{/-last}}
49+
{{/operations}}{{/apis}}{{/apiInfo}}
50+
51+
-type operation_id() ::
52+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} '{{operationIdOriginal}}' | %% {{summary}}
53+
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} {error, unknown_operation}.
54+
4555
-type request_param() :: atom().
4656

47-
-export_type([operation_id/0]).
57+
-export_type([class/0, operation_id/0]).
4858

4959
-dialyzer({nowarn_function, [validate_response_body/4]}).
5060

@@ -64,6 +74,7 @@ accept_callback(Class, OperationID, Req, Context) ->
6474
{max_length, MaxLength :: integer()} |
6575
{min_length, MaxLength :: integer()} |
6676
{pattern, Pattern :: string()} |
77+
{schema, object | list, binary()} |
6778
schema |
6879
required |
6980
not_required.
@@ -111,25 +122,25 @@ for the `OperationID` operation.
111122
Body :: jesse:json_term(),
112123
ValidatorState :: jesse_state:state()) ->
113124
ok | {ok, term()} | [ok | {ok, term()}] | no_return().
114-
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#responses}}validate_response('{{operationId}}', {{code}}, Body, ValidatorState) ->
125+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#responses}}validate_response('{{operationIdOriginal}}', {{code}}, Body, ValidatorState) ->
115126
validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState);
116127
{{/responses}}
117128
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
118129
ok.
119130

120131
%%%
121132
-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
122-
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}request_params('{{operationId}}') ->
133+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}request_params('{{operationIdOriginal}}') ->
123134
[{{#allParams}}{{^isBodyParam}}
124135
'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}
125-
'{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
136+
{{#content.application/json.schema.openApiType}}'{{content.application/json.schema.openApiType}}'{{/content.application/json.schema.openApiType}}{{^content.application/json.schema.openApiType}}'{{dataType}}'{{/content.application/json.schema.openApiType}}{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
126137
];
127138
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_params(_) ->
128139
error(unknown_operation).
129140

130141
-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) ->
131142
#{source => qs_val | binding | header | body, rules => [rule()]}.
132-
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#allParams}}request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
143+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#allParams}}request_param_info('{{operationIdOriginal}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}{{#content.application/json.schema.openApiType}}'{{content.application/json.schema.openApiType}}'{{/content.application/json.schema.openApiType}}{{^content.application/json.schema.openApiType}}'{{dataType}}'{{/content.application/json.schema.openApiType}}{{/isBodyParam}}) ->
133144
#{
134145
source => {{#isQueryParam}}qs_val{{/isQueryParam}}{{#isPathParam}}binding{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}},
135146
rules => [{{#isString}}
@@ -150,8 +161,9 @@ for the `OperationID` operation.
150161
{exclusive_min, {{minimum}}},{{/exclusiveMinimum}}{{/minimum}}{{#maxLength}}
151162
{max_length, {{maxLength}}},{{/maxLength}}{{#minLength}}
152163
{min_length, {{minLength}}},{{/minLength}}{{#pattern}}
153-
{pattern, "{{{pattern}}}"},{{/pattern}}{{#isBodyParam}}
154-
schema,{{/isBodyParam}}{{#required}}
164+
{pattern, "{{{pattern}}}"},{{/pattern}}{{#isBodyParam}}{{#content.application/json.schema.ref}}
165+
{schema, object, <<"{{content.application/json.schema.ref}}">>},{{/content.application/json.schema.ref}}{{#content.application/json.schema.items.ref}}
166+
{schema, list, <<"{{content.application/json.schema.items.ref}}">>},{{/content.application/json.schema.items.ref}}{{/isBodyParam}}{{#required}}
155167
required{{/required}}{{^required}}
156168
not_required{{/required}}
157169
]
@@ -189,8 +201,6 @@ populate_request_param(OperationID, ReqParamName, Req0, ValidatorState) ->
189201
end
190202
end.
191203

192-
-include_lib("kernel/include/logger.hrl").
193-
194204
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
195205
[
196206
validate(schema, Item, ReturnBaseType, ValidatorState)
@@ -281,8 +291,15 @@ validate(Rule = {pattern, Pattern}, Value, ReqParamName, _) ->
281291
{match, _} -> ok;
282292
_ -> validation_error(Rule, ReqParamName, Value)
283293
end;
284-
validate(Rule = schema, Value, ReqParamName, ValidatorState) ->
294+
validate(schema, Value, ReqParamName, ValidatorState) ->
285295
Definition = iolist_to_binary(["#/components/schemas/", atom_to_binary(ReqParamName, utf8)]),
296+
validate({schema, object, Definition}, Value, ReqParamName, ValidatorState);
297+
validate({schema, list, Definition}, Value, ReqParamName, ValidatorState) ->
298+
lists:foreach(
299+
fun(Item) ->
300+
validate({schema, object, Definition}, Item, ReqParamName, ValidatorState)
301+
end, Value);
302+
validate(Rule = {schema, object, Definition}, Value, ReqParamName, ValidatorState) ->
286303
try
287304
_ = validate_with_schema(Value, Definition, ValidatorState),
288305
ok

modules/openapi-generator/src/main/resources/erlang-server/handler.mustache

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
-moduledoc """
33
Exposes the following operation IDs:
44
{{#operations}}{{#operation}}
5-
- `{{httpMethod}}` to `{{path}}`, OperationId: `{{operationId}}`:
5+
- `{{httpMethod}}` to `{{path}}`, OperationId: `{{operationIdOriginal}}`:
66
{{summary}}.
7+
{{notes}}
78
{{/operation}}{{/operations}}
89
""".
910

@@ -23,8 +24,16 @@ Exposes the following operation IDs:
2324

2425
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
2526

27+
-export_type([class/0, operation_id/0]).
28+
29+
-type class() :: '{{operations.pathPrefix}}'.
30+
31+
-type operation_id() ::
32+
{{#operations}}{{#operation}} {{^-first}}| {{/-first}}'{{operationIdOriginal}}'{{#-last}}.{{/-last}} %% {{summary}}
33+
{{/operation}}{{/operations}}
34+
2635
-record(state,
27-
{operation_id :: {{packageName}}_api:operation_id(),
36+
{operation_id :: operation_id(),
2837
accept_callback :: {{packageName}}_logic_handler:accept_callback(),
2938
provide_callback :: {{packageName}}_logic_handler:provide_callback(),
3039
api_key_handler :: {{packageName}}_logic_handler:api_key_callback(),
@@ -48,7 +57,7 @@ init(Req, {Operations, Module}) ->
4857

4958
-spec allowed_methods(cowboy_req:req(), state()) ->
5059
{[binary()], cowboy_req:req(), state()}.
51-
{{#operations}}{{#operation}}allowed_methods(Req, #state{operation_id = '{{operationId}}'} = State) ->
60+
{{#operations}}{{#operation}}allowed_methods(Req, #state{operation_id = '{{operationIdOriginal}}'} = State) ->
5261
{[<<"{{httpMethod}}">>], Req, State};
5362
{{/operation}}{{/operations}}allowed_methods(Req, State) ->
5463
{[], Req, State}.
@@ -59,7 +68,7 @@ init(Req, {Operations, Module}) ->
5968
{{#operation}}
6069
{{#authMethods.size}}
6170
is_authorized(Req0,
62-
#state{operation_id = '{{operationId}}' = OperationID,
71+
#state{operation_id = '{{operationIdOriginal}}' = OperationID,
6372
api_key_handler = Handler} = State) ->
6473
case {{packageName}}_auth:authorize_api_key(Handler, OperationID, {{#isApiKey.isKeyInQuery}}qs_val, {{/isApiKey.isKeyInQuery}}{{^isApiKey.isKeyInQuery}}header, {{/isApiKey.isKeyInQuery}}{{#isApiKey}}"{{keyParamName}}", {{/isApiKey}}{{^isApiKey}}"authorization", {{/isApiKey}}Req0) of
6574
{true, Context, Req} ->
@@ -75,7 +84,7 @@ is_authorized(Req, State) ->
7584

7685
-spec content_types_accepted(cowboy_req:req(), state()) ->
7786
{[{binary(), atom()}], cowboy_req:req(), state()}.
78-
{{#operations}}{{#operation}}content_types_accepted(Req, #state{operation_id = '{{operationId}}'} = State) ->
87+
{{#operations}}{{#operation}}content_types_accepted(Req, #state{operation_id = '{{operationIdOriginal}}'} = State) ->
7988
{{^consumes.size}}
8089
{[], Req, State};
8190
{{/consumes.size}}
@@ -91,14 +100,14 @@ is_authorized(Req, State) ->
91100

92101
-spec valid_content_headers(cowboy_req:req(), state()) ->
93102
{boolean(), cowboy_req:req(), state()}.
94-
{{#operations}}{{#operation}}valid_content_headers(Req, #state{operation_id = '{{operationId}}'} = State) ->
103+
{{#operations}}{{#operation}}valid_content_headers(Req, #state{operation_id = '{{operationIdOriginal}}'} = State) ->
95104
{true, Req, State};
96105
{{/operation}}{{/operations}}valid_content_headers(Req, State) ->
97106
{false, Req, State}.
98107

99108
-spec content_types_provided(cowboy_req:req(), state()) ->
100109
{[{binary(), atom()}], cowboy_req:req(), state()}.
101-
{{#operations}}{{#operation}}content_types_provided(Req, #state{operation_id = '{{operationId}}'} = State) ->
110+
{{#operations}}{{#operation}}content_types_provided(Req, #state{operation_id = '{{operationIdOriginal}}'} = State) ->
102111
{{^produces.size}}
103112
{[], Req, State};
104113
{{/produces.size}}
@@ -115,8 +124,8 @@ is_authorized(Req, State) ->
115124
-spec delete_resource(cowboy_req:req(), state()) ->
116125
{boolean(), cowboy_req:req(), state()}.
117126
delete_resource(Req, State) ->
118-
{Res, Req1, State} = handle_type_accepted(Req, State),
119-
{true =:= Res, Req1, State}.
127+
{Res, Req1, State1} = handle_type_accepted(Req, State),
128+
{true =:= Res, Req1, State1}.
120129

121130
-spec handle_type_accepted(cowboy_req:req(), state()) ->
122131
{ {{packageName}}_logic_handler:accept_callback_return(), cowboy_req:req(), state()}.

modules/openapi-generator/src/main/resources/erlang-server/logic_handler.mustache

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
-type api_key_callback() ::
1212
fun(({{packageName}}_api:operation_id(), binary()) -> {true, context()} | {false, iodata()}).
1313
-type accept_callback() ::
14-
fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
14+
fun(({{packageName}}_api:class(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
1515
{accept_callback_return(), cowboy_req:req(), context()}).
1616
-type provide_callback() ::
17-
fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
17+
fun(({{packageName}}_api:class(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
1818
{cowboy_req:resp_body(), cowboy_req:req(), context()}).
1919
-type context() :: #{_ := _}.
2020

@@ -26,10 +26,10 @@
2626
-callback api_key_callback({{packageName}}_api:operation_id(), binary()) ->
2727
{true, context()} | {false, iodata()}.
2828

29-
-callback accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
29+
-callback accept_callback({{packageName}}_api:class(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
3030
{accept_callback_return(), cowboy_req:req(), context()}.
3131

32-
-callback provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
32+
-callback provide_callback({{packageName}}_api:class(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
3333
{cowboy_req:resp_body(), cowboy_req:req(), context()}.
3434

3535
-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
@@ -42,7 +42,7 @@ api_key_callback(OperationID, ApiKey) ->
4242
api_key => ApiKey}),
4343
{true, #{}}.
4444

45-
-spec accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
45+
-spec accept_callback({{packageName}}_api:class(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
4646
{accept_callback_return(), cowboy_req:req(), context()}.
4747
accept_callback(Class, OperationID, Req, Context) ->
4848
?LOG_ERROR(#{what => "Got not implemented request to process",
@@ -52,7 +52,7 @@ accept_callback(Class, OperationID, Req, Context) ->
5252
context => Context}),
5353
{false, Req, Context}.
5454

55-
-spec provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
55+
-spec provide_callback({{packageName}}_api:class(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
5656
{cowboy_req:resp_body(), cowboy_req:req(), context()}.
5757
provide_callback(Class, OperationID, Req, Context) ->
5858
?LOG_ERROR(#{what => "Got not implemented request to process",

modules/openapi-generator/src/main/resources/erlang-server/router.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ merge_paths(FullPaths, OperationID, Method, Handler, Acc) ->
4747

4848
get_operations() ->
4949
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
50-
'{{operationId}}' => #{
50+
'{{operationIdOriginal}}' => #{
5151
servers => [{{#servers}}"{{{url}}}"{{^-last}},{{/-last}}{{/servers}}],
5252
base_path => "{{{basePathWithoutHost}}}",
5353
path => "{{{path}}}",

0 commit comments

Comments
 (0)