feat(swagger): move public page/limit/error_code to emqx_dashboard_swagger module
This commit is contained in:
parent
7e494afd98
commit
cae79a0584
|
@ -6,6 +6,11 @@
|
||||||
%% API
|
%% API
|
||||||
-export([spec/1, spec/2]).
|
-export([spec/1, spec/2]).
|
||||||
-export([translate_req/2]).
|
-export([translate_req/2]).
|
||||||
|
-export([namespace/0, fields/1]).
|
||||||
|
-export([error_codes/1, error_codes/2]).
|
||||||
|
-define(MAX_ROW_LIMIT, 100).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
|
@ -73,6 +78,30 @@ translate_req(Request, #{module := Module, path := Path, method := Method}) ->
|
||||||
{400, 'BAD_REQUEST', iolist_to_binary(io_lib:format("~s : ~p", [Key, Reason]))}
|
{400, 'BAD_REQUEST', iolist_to_binary(io_lib:format("~s : ~p", [Key, Reason]))}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
namespace() -> "public".
|
||||||
|
|
||||||
|
fields(page) ->
|
||||||
|
Desc = <<"Page number of the results to fetch.">>,
|
||||||
|
Meta = #{in => query, desc => Desc, default => 1, example => 1},
|
||||||
|
[{page, hoconsc:mk(integer(), Meta)}];
|
||||||
|
fields(limit) ->
|
||||||
|
Desc = iolist_to_binary([<<"Results per page(max ">>,
|
||||||
|
integer_to_binary(?MAX_ROW_LIMIT), <<")">>]),
|
||||||
|
Meta = #{in => query, desc => Desc, default => ?MAX_ROW_LIMIT, example => 50},
|
||||||
|
[{limit, hoconsc:mk(range(1, ?MAX_ROW_LIMIT), Meta)}].
|
||||||
|
|
||||||
|
error_codes(Codes) ->
|
||||||
|
error_codes(Codes, <<"Error code to troubleshoot problems.">>).
|
||||||
|
|
||||||
|
error_codes(Codes = [_ | _], MsgExample) ->
|
||||||
|
[
|
||||||
|
{code, hoconsc:mk(hoconsc:enum(Codes))},
|
||||||
|
{message, hoconsc:mk(string(), #{
|
||||||
|
desc => <<"Details description of the error.">>,
|
||||||
|
example => MsgExample
|
||||||
|
})}
|
||||||
|
].
|
||||||
|
|
||||||
support_check_schema(#{check_schema := true}) -> ?DEFAULT_FILTER;
|
support_check_schema(#{check_schema := true}) -> ?DEFAULT_FILTER;
|
||||||
support_check_schema(#{check_schema := Func})when is_function(Func, 2) -> #{filter => Func};
|
support_check_schema(#{check_schema := Func})when is_function(Func, 2) -> #{filter => Func};
|
||||||
support_check_schema(_) -> #{filter => undefined}.
|
support_check_schema(_) -> #{filter => undefined}.
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%
|
|
||||||
%% 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.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-module(emqx_swagger).
|
|
||||||
|
|
||||||
-include_lib("typerefl/include/types.hrl").
|
|
||||||
-import(hoconsc, [mk/2]).
|
|
||||||
|
|
||||||
-define(MAX_ROW_LIMIT, 100).
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([fields/1]).
|
|
||||||
-export([error_codes/1, error_codes/2]).
|
|
||||||
|
|
||||||
fields(page) ->
|
|
||||||
[{page,
|
|
||||||
mk(integer(),
|
|
||||||
#{
|
|
||||||
in => query,
|
|
||||||
desc => <<"Page number of the results to fetch.">>,
|
|
||||||
default => 1,
|
|
||||||
example => 1})
|
|
||||||
}];
|
|
||||||
fields(limit) ->
|
|
||||||
[{limit,
|
|
||||||
mk(range(1, ?MAX_ROW_LIMIT),
|
|
||||||
#{
|
|
||||||
in => query,
|
|
||||||
desc => iolist_to_binary([<<"Results per page(max ">>,
|
|
||||||
integer_to_binary(?MAX_ROW_LIMIT), <<")">>]),
|
|
||||||
default => ?MAX_ROW_LIMIT,
|
|
||||||
example => 50
|
|
||||||
})
|
|
||||||
}].
|
|
||||||
|
|
||||||
error_codes(Codes) ->
|
|
||||||
error_codes(Codes, <<"Error code to troubleshoot problems.">>).
|
|
||||||
|
|
||||||
error_codes(Codes = [_ | _], MsgExample) ->
|
|
||||||
[code(Codes), message(MsgExample)].
|
|
||||||
|
|
||||||
message(Example) ->
|
|
||||||
{message, mk(string(), #{
|
|
||||||
desc => <<"Detailed description of the error.">>,
|
|
||||||
example => Example
|
|
||||||
})}.
|
|
||||||
|
|
||||||
code(Codes) ->
|
|
||||||
{code, mk(hoconsc:enum(Codes), #{})}.
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([paths/0, api_spec/0, schema/1, fields/1]).
|
-export([paths/0, api_spec/0, schema/1, fields/1]).
|
||||||
-export([t_in_path/1, t_in_query/1, t_in_mix/1, t_without_in/1, t_ref/1]).
|
-export([t_in_path/1, t_in_query/1, t_in_mix/1, t_without_in/1, t_ref/1, t_public_ref/1]).
|
||||||
-export([t_require/1, t_nullable/1, t_method/1, t_api_spec/1]).
|
-export([t_require/1, t_nullable/1, t_method/1, t_api_spec/1]).
|
||||||
-export([t_in_path_trans/1, t_in_query_trans/1, t_in_mix_trans/1, t_ref_trans/1]).
|
-export([t_in_path_trans/1, t_in_query_trans/1, t_in_mix_trans/1, t_ref_trans/1]).
|
||||||
-export([t_in_path_trans_error/1, t_in_query_trans_error/1, t_in_mix_trans_error/1]).
|
-export([t_in_path_trans_error/1, t_in_query_trans_error/1, t_in_mix_trans_error/1]).
|
||||||
|
@ -21,7 +21,7 @@ all() -> [{group, spec}, {group, validation}].
|
||||||
suite() -> [{timetrap, {minutes, 1}}].
|
suite() -> [{timetrap, {minutes, 1}}].
|
||||||
groups() -> [
|
groups() -> [
|
||||||
{spec, [parallel], [t_api_spec, t_in_path, t_ref, t_in_query, t_in_mix,
|
{spec, [parallel], [t_api_spec, t_in_path, t_ref, t_in_query, t_in_mix,
|
||||||
t_without_in, t_require, t_nullable, t_method]},
|
t_without_in, t_require, t_nullable, t_method, t_public_ref]},
|
||||||
{validation, [parallel], [t_in_path_trans, t_ref_trans, t_in_query_trans, t_in_mix_trans,
|
{validation, [parallel], [t_in_path_trans, t_ref_trans, t_in_query_trans, t_in_mix_trans,
|
||||||
t_in_path_trans_error, t_in_query_trans_error, t_in_mix_trans_error]}
|
t_in_path_trans_error, t_in_query_trans_error, t_in_mix_trans_error]}
|
||||||
].
|
].
|
||||||
|
@ -56,6 +56,29 @@ t_ref(_Config) ->
|
||||||
?assertEqual([{?MODULE, page, parameter}], Refs),
|
?assertEqual([{?MODULE, page, parameter}], Refs),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_public_ref(_Config) ->
|
||||||
|
Path = "/test/in/ref/public",
|
||||||
|
Expect = [
|
||||||
|
#{<<"$ref">> => <<"#/components/parameters/public.page">>},
|
||||||
|
#{<<"$ref">> => <<"#/components/parameters/public.limit">>}
|
||||||
|
],
|
||||||
|
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||||
|
?assertEqual(test, OperationId),
|
||||||
|
Params = maps:get(parameters, maps:get(post, Spec)),
|
||||||
|
?assertEqual(Expect, Params),
|
||||||
|
?assertEqual([
|
||||||
|
{emqx_dashboard_swagger, limit, parameter},
|
||||||
|
{emqx_dashboard_swagger, page, parameter}
|
||||||
|
], Refs),
|
||||||
|
ExpectRefs = [
|
||||||
|
#{<<"public.limit">> => #{description => <<"Results per page(max 100)">>, example => 50,in => query,name => limit,
|
||||||
|
schema => #{default => 100,example => 1,maximum => 100, minimum => 1,type => integer}}},
|
||||||
|
#{<<"public.page">> => #{description => <<"Page number of the results to fetch.">>,
|
||||||
|
example => 1,in => query,name => page,
|
||||||
|
schema => #{default => 1,example => 100,type => integer}}}],
|
||||||
|
?assertEqual(ExpectRefs, emqx_dashboard_swagger:components(Refs)),
|
||||||
|
ok.
|
||||||
|
|
||||||
t_in_mix(_Config) ->
|
t_in_mix(_Config) ->
|
||||||
Expect =
|
Expect =
|
||||||
[#{description => <<"Indicates which sorts of issues to return">>,
|
[#{description => <<"Indicates which sorts of issues to return">>,
|
||||||
|
@ -253,6 +276,17 @@ schema("/test/in/ref") ->
|
||||||
responses => #{200 => <<"ok">>}
|
responses => #{200 => <<"ok">>}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
schema("/test/in/ref/public") ->
|
||||||
|
#{
|
||||||
|
operationId => test,
|
||||||
|
post => #{
|
||||||
|
parameters => [
|
||||||
|
hoconsc:ref(emqx_dashboard_swagger, page),
|
||||||
|
hoconsc:ref(emqx_dashboard_swagger, limit)
|
||||||
|
],
|
||||||
|
responses => #{200 => <<"ok">>}
|
||||||
|
}
|
||||||
|
};
|
||||||
schema("/test/in/mix/:state") ->
|
schema("/test/in/mix/:state") ->
|
||||||
#{
|
#{
|
||||||
operationId => test,
|
operationId => test,
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
-export([all/0, suite/0, groups/0]).
|
-export([all/0, suite/0, groups/0]).
|
||||||
-export([paths/0, api_spec/0, schema/1, fields/1]).
|
-export([paths/0, api_spec/0, schema/1, fields/1]).
|
||||||
-export([t_simple_binary/1, t_object/1, t_nest_object/1, t_empty/1,
|
-export([t_simple_binary/1, t_object/1, t_nest_object/1, t_empty/1, t_error/1,
|
||||||
t_raw_local_ref/1, t_raw_remote_ref/1, t_hocon_schema_function/1,
|
t_raw_local_ref/1, t_raw_remote_ref/1, t_hocon_schema_function/1,
|
||||||
t_local_ref/1, t_remote_ref/1, t_bad_ref/1, t_none_ref/1, t_nest_ref/1,
|
t_local_ref/1, t_remote_ref/1, t_bad_ref/1, t_none_ref/1, t_nest_ref/1,
|
||||||
t_ref_array_with_key/1, t_ref_array_without_key/1, t_api_spec/1]).
|
t_ref_array_with_key/1, t_ref_array_without_key/1, t_api_spec/1]).
|
||||||
|
@ -21,7 +21,7 @@ all() -> [{group, spec}].
|
||||||
suite() -> [{timetrap, {minutes, 1}}].
|
suite() -> [{timetrap, {minutes, 1}}].
|
||||||
groups() -> [
|
groups() -> [
|
||||||
{spec, [parallel], [
|
{spec, [parallel], [
|
||||||
t_api_spec, t_simple_binary, t_object, t_nest_object,
|
t_api_spec, t_simple_binary, t_object, t_nest_object, t_error,
|
||||||
t_raw_local_ref, t_raw_remote_ref, t_empty, t_hocon_schema_function,
|
t_raw_local_ref, t_raw_remote_ref, t_empty, t_hocon_schema_function,
|
||||||
t_local_ref, t_remote_ref, t_bad_ref, t_none_ref,
|
t_local_ref, t_remote_ref, t_bad_ref, t_none_ref,
|
||||||
t_ref_array_with_key, t_ref_array_without_key, t_nest_ref]}
|
t_ref_array_with_key, t_ref_array_without_key, t_nest_ref]}
|
||||||
|
@ -48,6 +48,33 @@ t_object(_config) ->
|
||||||
validate(Path, Object, ExpectRefs),
|
validate(Path, Object, ExpectRefs),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_error(_Config) ->
|
||||||
|
Path = "/error",
|
||||||
|
Error400 = #{<<"content">> =>
|
||||||
|
#{<<"application/json">> => #{<<"schema">> => #{<<"type">> => object,
|
||||||
|
<<"properties">> =>
|
||||||
|
[
|
||||||
|
{<<"code">>, #{enum => ['Bad1','Bad2'], type => string}},
|
||||||
|
{<<"message">>, #{description => <<"Details description of the error.">>,
|
||||||
|
example => <<"Bad request desc">>, type => string}}]
|
||||||
|
}}}},
|
||||||
|
Error404 = #{<<"content">> =>
|
||||||
|
#{<<"application/json">> => #{<<"schema">> => #{<<"type">> => object,
|
||||||
|
<<"properties">> =>
|
||||||
|
[
|
||||||
|
{<<"code">>, #{enum => ['Not-Found'], type => string}},
|
||||||
|
{<<"message">>, #{description => <<"Details description of the error.">>,
|
||||||
|
example => <<"Error code to troubleshoot problems.">>, type => string}}]
|
||||||
|
}}}},
|
||||||
|
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||||
|
?assertEqual(test, OperationId),
|
||||||
|
Response = maps:get(responses, maps:get(get, Spec)),
|
||||||
|
?assertEqual(Error400, maps:get(<<"400">>, Response)),
|
||||||
|
?assertEqual(Error404, maps:get(<<"404">>, Response)),
|
||||||
|
?assertEqual(#{}, maps:without([<<"400">>, <<"404">>], Response)),
|
||||||
|
?assertEqual([], Refs),
|
||||||
|
ok.
|
||||||
|
|
||||||
t_nest_object(_Config) ->
|
t_nest_object(_Config) ->
|
||||||
Path = "/nest/object",
|
Path = "/nest/object",
|
||||||
Object =
|
Object =
|
||||||
|
@ -255,7 +282,15 @@ schema("/ref/array/with/key") ->
|
||||||
schema("/ref/array/without/key") ->
|
schema("/ref/array/without/key") ->
|
||||||
to_schema(mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{}));
|
to_schema(mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{}));
|
||||||
schema("/ref/hocon/schema/function") ->
|
schema("/ref/hocon/schema/function") ->
|
||||||
to_schema(mk(hoconsc:ref(emqx_swagger_remote_schema, "root"), #{})).
|
to_schema(mk(hoconsc:ref(emqx_swagger_remote_schema, "root"), #{}));
|
||||||
|
schema("/error") ->
|
||||||
|
#{
|
||||||
|
operationId => test,
|
||||||
|
get => #{responses => #{
|
||||||
|
400 => emqx_dashboard_swagger:error_codes(['Bad1', 'Bad2'], <<"Bad request desc">>),
|
||||||
|
404 => emqx_dashboard_swagger:error_codes(['Not-Found'])
|
||||||
|
}}
|
||||||
|
}.
|
||||||
|
|
||||||
validate(Path, ExpectObject, ExpectRefs) ->
|
validate(Path, ExpectObject, ExpectRefs) ->
|
||||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||||
|
|
|
@ -124,7 +124,7 @@ t_catch_up_status_handle_next_commit(_Config) ->
|
||||||
t_commit_ok_apply_fail_on_other_node_then_recover(_Config) ->
|
t_commit_ok_apply_fail_on_other_node_then_recover(_Config) ->
|
||||||
emqx_cluster_rpc:reset(),
|
emqx_cluster_rpc:reset(),
|
||||||
{atomic, []} = emqx_cluster_rpc:status(),
|
{atomic, []} = emqx_cluster_rpc:status(),
|
||||||
Now = erlang:system_time(second),
|
Now = erlang:system_time(millisecond),
|
||||||
{M, F, A} = {?MODULE, failed_on_other_recover_after_5_second, [erlang:whereis(?NODE1), Now]},
|
{M, F, A} = {?MODULE, failed_on_other_recover_after_5_second, [erlang:whereis(?NODE1), Now]},
|
||||||
{ok, _, ok} = emqx_cluster_rpc:multicall(M, F, A, 1, 1000),
|
{ok, _, ok} = emqx_cluster_rpc:multicall(M, F, A, 1, 1000),
|
||||||
{ok, _, ok} = emqx_cluster_rpc:multicall(io, format, ["test"], 1, 1000),
|
{ok, _, ok} = emqx_cluster_rpc:multicall(io, format, ["test"], 1, 1000),
|
||||||
|
@ -132,10 +132,10 @@ t_commit_ok_apply_fail_on_other_node_then_recover(_Config) ->
|
||||||
?assertEqual([], L),
|
?assertEqual([], L),
|
||||||
?assertEqual({io, format, ["test"]}, maps:get(mfa, Status)),
|
?assertEqual({io, format, ["test"]}, maps:get(mfa, Status)),
|
||||||
?assertEqual(node(), maps:get(node, Status)),
|
?assertEqual(node(), maps:get(node, Status)),
|
||||||
sleep(3000),
|
sleep(2300),
|
||||||
{atomic, [Status1]} = emqx_cluster_rpc:status(),
|
{atomic, [Status1]} = emqx_cluster_rpc:status(),
|
||||||
?assertEqual(Status, Status1),
|
?assertEqual(Status, Status1),
|
||||||
sleep(2600),
|
sleep(3600),
|
||||||
{atomic, NewStatus} = emqx_cluster_rpc:status(),
|
{atomic, NewStatus} = emqx_cluster_rpc:status(),
|
||||||
?assertEqual(3, length(NewStatus)),
|
?assertEqual(3, length(NewStatus)),
|
||||||
Pid = self(),
|
Pid = self(),
|
||||||
|
@ -243,11 +243,11 @@ failed_on_node_by_odd(Pid) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
failed_on_other_recover_after_5_second(Pid, CreatedAt) ->
|
failed_on_other_recover_after_5_second(Pid, CreatedAt) ->
|
||||||
Now = erlang:system_time(second),
|
Now = erlang:system_time(millisecond),
|
||||||
case Pid =:= self() of
|
case Pid =:= self() of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false ->
|
false ->
|
||||||
case Now < CreatedAt + 5 of
|
case Now < CreatedAt + 5001 of
|
||||||
true -> "MFA return not ok";
|
true -> "MFA return not ok";
|
||||||
false -> ok
|
false -> ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,7 +68,7 @@ schema("/mqtt/delayed") ->
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => mk(ref(emqx_modules_schema, "delayed"),
|
200 => mk(ref(emqx_modules_schema, "delayed"),
|
||||||
#{desc => <<"Enable or disable delayed successfully">>}),
|
#{desc => <<"Enable or disable delayed successfully">>}),
|
||||||
400 => emqx_swagger:error_codes([?BAD_REQUEST], <<"Max limit illegality">>)
|
400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Max limit illegality">>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -81,8 +81,8 @@ schema("/mqtt/delayed/messages/:msgid") ->
|
||||||
parameters => [{msgid, mk(binary(), #{in => path, desc => <<"delay message ID">>})}],
|
parameters => [{msgid, mk(binary(), #{in => path, desc => <<"delay message ID">>})}],
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => ref("message_without_payload"),
|
200 => ref("message_without_payload"),
|
||||||
400 => emqx_swagger:error_codes([?MESSAGE_ID_SCHEMA_ERROR], <<"Bad MsgId format">>),
|
400 => emqx_dashboard_swagger:error_codes([?MESSAGE_ID_SCHEMA_ERROR], <<"Bad MsgId format">>),
|
||||||
404 => emqx_swagger:error_codes([?MESSAGE_ID_NOT_FOUND], <<"MsgId not found">>)
|
404 => emqx_dashboard_swagger:error_codes([?MESSAGE_ID_NOT_FOUND], <<"MsgId not found">>)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
delete => #{
|
delete => #{
|
||||||
|
@ -91,8 +91,8 @@ schema("/mqtt/delayed/messages/:msgid") ->
|
||||||
parameters => [{msgid, mk(binary(), #{in => path, desc => <<"delay message ID">>})}],
|
parameters => [{msgid, mk(binary(), #{in => path, desc => <<"delay message ID">>})}],
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => <<"Delete delayed message success">>,
|
200 => <<"Delete delayed message success">>,
|
||||||
400 => emqx_swagger:error_codes([?MESSAGE_ID_SCHEMA_ERROR], <<"Bad MsgId format">>),
|
400 => emqx_dashboard_swagger:error_codes([?MESSAGE_ID_SCHEMA_ERROR], <<"Bad MsgId format">>),
|
||||||
404 => emqx_swagger:error_codes([?MESSAGE_ID_NOT_FOUND], <<"MsgId not found">>)
|
404 => emqx_dashboard_swagger:error_codes([?MESSAGE_ID_NOT_FOUND], <<"MsgId not found">>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -102,7 +102,7 @@ schema("/mqtt/delayed/messages") ->
|
||||||
get => #{
|
get => #{
|
||||||
tags => [<<"mqtt">>],
|
tags => [<<"mqtt">>],
|
||||||
description => <<"List delayed messages">>,
|
description => <<"List delayed messages">>,
|
||||||
parameters => [ref(emqx_swagger, page), ref(emqx_swagger, limit)],
|
parameters => [ref(emqx_dashboard_swagger, page), ref(emqx_dashboard_swagger, limit)],
|
||||||
responses => #{
|
responses => #{
|
||||||
200 =>
|
200 =>
|
||||||
[
|
[
|
||||||
|
|
Loading…
Reference in New Issue