feat: support http error code & error code api

This commit is contained in:
DDDHuang 2022-02-16 16:08:45 +08:00
parent 9b53d36571
commit 90ee450a84
9 changed files with 365 additions and 16 deletions

View File

@ -0,0 +1,74 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2017-2022 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.
%%--------------------------------------------------------------------
%% Bad Request
-define(BAD_REQUEST, 'BAD_REQUEST').
-define(ALREADY_EXISTED, 'ALREADY_EXISTED').
-define(BAD_CONFIG_SCHEMA, 'BAD_CONFIG_SCHEMA').
-define(BAD_LISTENER_ID, 'BAD_LISTENER_ID').
-define(BAD_NODE_NAME, 'BAD_NODE_NAME').
-define(BAD_RPC, 'BAD_RPC').
-define(BAD_TOPIC, 'BAD_TOPIC').
-define(EXCEED_LIMIT, 'EXCEED_LIMIT').
-define(INVALID_PARAMETER, 'INVALID_PARAMETER').
-define(CONFLICT, 'CONFLICT').
-define(NO_DEFAULT_VALUE, 'NO_DEFAULT_VALUE').
-define(MESSAGE_ID_SCHEMA_ERROR, 'MESSAGE_ID_SCHEMA_ERROR').
%% Resource Not Found
-define(NOT_FOUND, 'NOT_FOUND').
-define(CLIENTID_NOT_FOUND, 'CLIENTID_NOT_FOUND').
-define(CLIENT_NOT_FOUND, 'CLIENT_NOT_FOUND').
-define(MESSAGE_ID_NOT_FOUND, 'MESSAGE_ID_NOT_FOUND').
-define(RESOURCE_NOT_FOUND, 'RESOURCE_NOT_FOUND').
-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND').
-define(USER_NOT_FOUND, 'USER_NOT_FOUND').
%% Internal error
-define(INTERNAL_ERROR, 'INTERNAL_ERROR').
-define(SOURCE_ERROR, 'SOURCE_ERROR').
-define(UPDATE_FAILED, 'UPDATE_FAILED').
-define(REST_FAILED, 'REST_FAILED').
-define(CLIENT_NOT_RESPONSE, 'CLIENT_NOT_RESPONSE').
%% All codes
-define(ERROR_CODES,
[ {'BAD_REQUEST', <<"Request parameters are not legal">>}
, {'ALREADY_EXISTED', <<"Resource already existed">>}
, {'BAD_CONFIG_SCHEMA', <<"Configuration data is not legal">>}
, {'BAD_LISTENER_ID', <<"Bad listener ID">>}
, {'BAD_NODE_NAME', <<"Bad Node Name">>}
, {'BAD_RPC', <<"RPC Failed. Check the cluster status and the requested node status">>}
, {'BAD_TOPIC', <<"Topic syntax error, Topic needs to comply with the MQTT protocol standard">>}
, {'EXCEED_LIMIT', <<"Create resources that exceed the maximum limit or minimum limit">>}
, {'INVALID_PARAMETER', <<"Request parameters is not legal and exceeds the boundary value">>}
, {'CONFLICT', <<"Conflicting request resources">>}
, {'NO_DEFAULT_VALUE', <<"Request parameters do not use default values">>}
, {'MESSAGE_ID_SCHEMA_ERROR', <<"Message ID parsing error">>}
, {'MESSAGE_ID_NOT_FOUND', <<"Message ID does not exist">>}
, {'NOT_FOUND', <<"Resource was not found or does not exist">>}
, {'CLIENTID_NOT_FOUND', <<"Client ID was not found or does not exist">>}
, {'CLIENT_NOT_FOUND', <<"Client was not found or does not exist(usually not a MQTT client)">>}
, {'RESOURCE_NOT_FOUND', <<"Resource not found">>}
, {'TOPIC_NOT_FOUND', <<"Topic not found">>}
, {'USER_NOT_FOUND', <<"User not found">>}
, {'INTERNAL_ERROR', <<"Server inter error">>}
, {'SOURCE_ERROR', <<"Source error">>}
, {'UPDATE_FAILED', <<"Update failed">>}
, {'REST_FAILED', <<"Reset source or config failed">>}
, {'CLIENT_NOT_RESPONSE', <<"Client not responding">>}
]).

View File

@ -0,0 +1,56 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2022 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_dashboard_error_code).
-include_lib("emqx/include/http_api.hrl").
-export([ all/0
, list/0
, look_up/1
, description/1
, format/1
]).
all() ->
[Name || {Name, _Description} <- ?ERROR_CODES].
list() ->
[format(Code) || Code <- ?ERROR_CODES].
look_up(Code) ->
look_up(Code, ?ERROR_CODES).
look_up(_Code, []) ->
{error, not_found};
look_up(Code, [{Code, Description} | _List]) ->
{ok, format({Code, Description})};
look_up(Code, [_ | List]) ->
look_up(Code, List).
description(Code) ->
description(Code, ?ERROR_CODES).
description(_Code, []) ->
{error, not_found};
description(Code, [{Code, Description} | _List]) ->
{ok, Description};
description(Code, [_ | List]) ->
description(Code, List).
format({Code, Description}) ->
#{
code => Code,
description => Description
}.

View File

@ -0,0 +1,91 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2022 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_dashboard_error_code_api).
-behaviour(minirest_api).
-include_lib("emqx/include/http_api.hrl").
-include("emqx_dashboard.hrl").
-include_lib("typerefl/include/types.hrl").
-export([ api_spec/0
, fields/1
, paths/0
, schema/1
, namespace/0
]).
-export([ error_codes/2
, error_code/2
]).
namespace() -> "dashboard".
api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
paths() ->
[ "/error_codes"
, "/error_codes/:code"
].
schema("/error_codes") ->
#{
'operationId' => error_codes,
get => #{
security => [],
description => <<"API Error Codes">>,
responses => #{
200 => hoconsc:array(hoconsc:ref(?MODULE, error_code))
}
}
};
schema("/error_codes/:code") ->
#{
'operationId' => error_code,
get => #{
security => [],
description => <<"API Error Codes">>,
parameters => [
{code, hoconsc:mk(hoconsc:enum(emqx_dashboard_error_code:all()), #{
desc => <<"API Error Codes">>,
in => path,
example => hd(emqx_dashboard_error_code:all())})}
],
responses => #{
200 => hoconsc:ref(?MODULE, error_code)
}
}
}.
fields(error_code) ->
[
{code, hoconsc:mk(string(), #{desc => <<"Code Name">>})},
{description, hoconsc:mk(string(), #{desc => <<"Description">>})}
].
error_codes(_, _) ->
{200, emqx_dashboard_error_code:list()}.
error_code(_, #{bindings := #{code := Name}}) ->
case emqx_dashboard_error_code:look_up(Name) of
{ok, Code} ->
{200, Code};
{error, not_found} ->
Message = list_to_binary(io_lib:format("Code name ~p not found", [Name])),
{404, ?NOT_FOUND, Message}
end.

View File

@ -0,0 +1,121 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2022 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_dashboard_error_code_SUITE).
-compile(nowarn_export_all).
-compile(export_all).
-include_lib("emqx/include/http_api.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(SERVER, "http://127.0.0.1:18083/api/v5").
all() ->
emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) ->
mria:start(),
application:load(emqx_dashboard),
emqx_common_test_helpers:start_apps([emqx_conf, emqx_dashboard], fun set_special_configs/1),
Config.
set_special_configs(emqx_dashboard) ->
Config = #{
default_username => <<"admin">>,
default_password => <<"public">>,
listeners => [#{
protocol => http,
port => 18083
}]
},
emqx_config:put([emqx_dashboard], Config),
ok;
set_special_configs(_) ->
ok.
end_per_suite(Config) ->
end_suite(),
Config.
end_suite() ->
application:unload(emqx_management),
emqx_common_test_helpers:stop_apps([emqx_dashboard]).
t_all_code(_) ->
HrlDef = ?ERROR_CODES,
All = emqx_dashboard_error_code:all(),
?assertEqual(length(HrlDef), length(All)),
ok.
t_list_code(_) ->
List = emqx_dashboard_error_code:list(),
[?assert(exist(CodeName, List)) || CodeName <- emqx_dashboard_error_code:all()],
ok.
t_look_up_code(_) ->
Fun =
fun(CodeName) ->
{ok, _} = emqx_dashboard_error_code:look_up(CodeName)
end,
lists:foreach(Fun, emqx_dashboard_error_code:all()),
{error, not_found} = emqx_dashboard_error_code:look_up('_____NOT_EXIST_NAME'),
ok.
t_description_code(_) ->
{error, not_found} = emqx_dashboard_error_code:description('_____NOT_EXIST_NAME'),
{ok, <<"Request parameters are not legal">>} =
emqx_dashboard_error_code:description('BAD_REQUEST'),
ok.
t_format_code(_) ->
#{code := 'TEST_SUITE_CODE', description := <<"for test suite">>} =
emqx_dashboard_error_code:format({'TEST_SUITE_CODE', <<"for test suite">>}),
ok.
t_api_codes(_) ->
Url = ?SERVER ++ "/error_codes",
{ok, List} = request(Url),
[?assert(exist(atom_to_binary(CodeName, utf8), List)) || CodeName <- emqx_dashboard_error_code:all()],
ok.
t_api_code(_) ->
Url = ?SERVER ++ "/error_codes/BAD_REQUEST",
{ok, #{<<"code">> := <<"BAD_REQUEST">>,
<<"description">> := <<"Request parameters are not legal">>}} = request(Url),
ok.
exist(_CodeName, []) ->
false;
exist(CodeName, [#{code := CodeName, description := _} | _List]) ->
true;
exist(CodeName, [#{<<"code">> := CodeName, <<"description">> := _} | _List]) ->
true;
exist(CodeName, [_ | List]) ->
exist(CodeName, List).
request(Url) ->
Request = {Url, []},
case httpc:request(get, Request, [], []) of
{error, Reason} ->
{error, Reason};
{ok, {{"HTTP/1.1", Code, _}, _, Return} }
when Code >= 200 andalso Code =< 299 ->
{ok, emqx_json:decode(Return, [return_maps])};
{ok, {Reason, _, _}} ->
{error, Reason}
end.

View File

@ -104,7 +104,7 @@ fields(ban) ->
{who, hoconsc:mk(binary(), #{
desc => <<"Client info as banned type">>,
nullable => false,
example => <<"Badass坏"/utf8>>})},
example => <<"Banned name"/utf8>>})},
{by, hoconsc:mk(binary(), #{
desc => <<"Commander">>,
nullable => true,

View File

@ -41,10 +41,9 @@
, fields/1
]).
-define(ERROR_TOPIC, 'ERROR_TOPIC').
-define(EXCEED_LIMIT, 'EXCEED_LIMIT').
-define(BAD_TOPIC, 'BAD_TOPIC').
-define(BAD_RPC, 'BAD_RPC').
-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND').
-define(BAD_REQUEST, 'BAD_REQUEST').
api_spec() ->
@ -62,7 +61,8 @@ schema("/mqtt/topic_metrics") ->
#{ description => <<"List topic metrics">>
, tags => ?API_TAG_MQTT
, responses =>
#{200 => mk(array(hoconsc:ref(topic_metrics)), #{ desc => <<"List all topic metrics">>})}
#{200 =>
mk(array(hoconsc:ref(topic_metrics)), #{ desc => <<"List all topic metrics">>})}
}
, put =>
#{ description => <<"Reset topic metrics by topic name. Or reset all Topic Metrics">>
@ -72,7 +72,9 @@ schema("/mqtt/topic_metrics") ->
reset_examples())
, responses =>
#{ 204 => <<"Reset topic metrics successfully">>
, 404 => emqx_dashboard_swagger:error_codes([?ERROR_TOPIC], <<"Topic not found">>)
, 404 =>
emqx_dashboard_swagger:error_codes(
[?TOPIC_NOT_FOUND], <<"Topic not found">>)
}
}
, post =>
@ -81,8 +83,10 @@ schema("/mqtt/topic_metrics") ->
, 'requestBody' => [topic(body)]
, responses =>
#{ 204 => <<"Create topic metrics success">>
, 409 => emqx_dashboard_swagger:error_codes([?EXCEED_LIMIT], <<"Topic metrics exceeded max limit 512">>)
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST, ?BAD_TOPIC], <<"Topic metrics already existed or bad topic">>)
, 409 => emqx_dashboard_swagger:error_codes([?EXCEED_LIMIT],
<<"Topic metrics exceeded max limit 512">>)
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST, ?BAD_TOPIC],
<<"Topic metrics already existed or bad topic">>)
}
}
};
@ -94,7 +98,8 @@ schema("/mqtt/topic_metrics/:topic") ->
, parameters => [topic(path)]
, responses =>
#{ 200 => mk(ref(topic_metrics), #{ desc => <<"Topic metrics">> })
, 404 => emqx_dashboard_swagger:error_codes([?ERROR_TOPIC], <<"Topic not found">>)
, 404 => emqx_dashboard_swagger:error_codes([?TOPIC_NOT_FOUND],
<<"Topic not found">>)
}
}
, delete =>
@ -103,7 +108,8 @@ schema("/mqtt/topic_metrics/:topic") ->
, parameters => [topic(path)]
, responses =>
#{ 204 => <<"Removed topic metrics successfully">>,
404 => emqx_dashboard_swagger:error_codes([?ERROR_TOPIC], <<"Topic not found">>)
404 => emqx_dashboard_swagger:error_codes([?TOPIC_NOT_FOUND],
<<"Topic not found">>)
}
}
}.
@ -111,7 +117,9 @@ schema("/mqtt/topic_metrics/:topic") ->
fields(reset) ->
[ {topic
, mk( binary()
, #{ desc => <<"Topic Name. If this parameter is not present, all created topic metrics will be reset">>
, #{ desc =>
<<"Topic Name. If this parameter is not present,"
" all created topic metrics will be reset">>
, example => <<"testtopic/1">>
, nullable => true})}
, {action
@ -399,10 +407,10 @@ reason2httpresp(already_existed) ->
{400, #{code => ?BAD_TOPIC, message => Msg}};
reason2httpresp(topic_not_found) ->
Msg = <<"Topic not found">>,
{404, #{code => ?ERROR_TOPIC, message => Msg}};
{404, #{code => ?TOPIC_NOT_FOUND, message => Msg}};
reason2httpresp(not_found) ->
Msg = <<"Topic not found">>,
{404, #{code => ?ERROR_TOPIC, message => Msg}}.
{404, #{code => ?TOPIC_NOT_FOUND, message => Msg}}.
get_cluster_response(Args) ->
case erlang:apply(?MODULE, cluster_accumulation_metrics, Args) of

View File

@ -4,8 +4,7 @@
[ {emqx, {path, "../emqx"}},
%% FIXME: tag this as v3.1.3
{prometheus, {git, "https://github.com/deadtrickster/prometheus.erl", {tag, "v4.8.1"}}},
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.24.0"}}},
{minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.11"}}}
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.24.0"}}}
]}.
{edoc_opts, [{preprocess, true}]}.

View File

@ -57,7 +57,7 @@ defmodule EMQXUmbrella.MixProject do
{:mria, github: "emqx/mria", tag: "0.2.0", override: true},
{:ekka, github: "emqx/ekka", tag: "0.12.1", override: true},
{:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.0", override: true},
{:minirest, github: "emqx/minirest", tag: "1.2.11", override: true},
{:minirest, github: "emqx/minirest", tag: "1.2.12", override: true},
{:ecpool, github: "emqx/ecpool", tag: "0.5.2"},
{:replayq, "0.3.3", override: true},
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},

View File

@ -56,7 +56,7 @@
, {mria, {git, "https://github.com/emqx/mria", {tag, "0.2.0"}}}
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.12.1"}}}
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.0"}}}
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.11"}}}
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.12"}}}
, {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.2"}}}
, {replayq, "0.3.3"}
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}