Browse Source

Merge pull request #7027 from DDDHuang/error_code

feat: support http error code & error code api
zhongwencool 4 years ago
parent
commit
b7f808db9e

+ 0 - 1
.github/workflows/run_api_tests.yaml

@@ -74,7 +74,6 @@ jobs:
           - api_publish
           - api_publish
           - api_user
           - api_user
           - api_login
           - api_login
-          - api_banned
           - api_alarms
           - api_alarms
           - api_nodes
           - api_nodes
           - api_topic_metrics
           - api_topic_metrics

+ 76 - 0
apps/emqx/include/http_api.hrl

@@ -0,0 +1,76 @@
+%%--------------------------------------------------------------------
+%% 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(DEPENDENCY_EXISTS,        'DEPENDENCY_EXISTS').
+-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">>}
+    , {'DEPENDENCY_EXISTS',         <<"Resource is dependent by another resource">>}
+    , {'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">>}
+    ]).

+ 3 - 1
apps/emqx/src/emqx_banned.erl

@@ -127,7 +127,9 @@ parse(Params) ->
                         until  = Until
                         until  = Until
                     };
                     };
                 false ->
                 false ->
-                    {error, "already_expired"}
+                    ErrorReason =
+                        io_lib:format("Cannot create expired banned, ~p to ~p", [At, Until]),
+                    {error, ErrorReason}
             end
             end
     end.
     end.
 pares_who(#{as := As, who := Who}) ->
 pares_who(#{as := As, who := Who}) ->

+ 24 - 13
apps/emqx_bridge/src/emqx_bridge_api.erl

@@ -23,11 +23,17 @@
 -import(hoconsc, [mk/2, array/1, enum/1]).
 -import(hoconsc, [mk/2, array/1, enum/1]).
 
 
 %% Swagger specs from hocon schema
 %% Swagger specs from hocon schema
--export([api_spec/0, paths/0, schema/1, namespace/0]).
+-export([ api_spec/0
+        , paths/0
+        , schema/1
+        , namespace/0
+        ]).
 
 
 %% API callbacks
 %% API callbacks
--export(['/bridges'/2, '/bridges/:id'/2,
-         '/bridges/:id/operation/:operation'/2]).
+-export([ '/bridges'/2
+        , '/bridges/:id'/2
+        , '/bridges/:id/operation/:operation'/2
+        ]).
 
 
 -export([ lookup_from_local_node/2
 -export([ lookup_from_local_node/2
         ]).
         ]).
@@ -70,10 +76,12 @@ api_spec() ->
 
 
 paths() -> ["/bridges", "/bridges/:id", "/bridges/:id/operation/:operation"].
 paths() -> ["/bridges", "/bridges/:id", "/bridges/:id/operation/:operation"].
 
 
-error_schema(Code, Message) ->
-    [ {code, mk(string(), #{example => Code})}
-    , {message, mk(string(), #{example => Message})}
-    ].
+error_schema(Code, Message) when is_atom(Code) ->
+    error_schema([Code], Message);
+error_schema(Codes, Message) when is_list(Message) ->
+    error_schema(Codes, list_to_binary(Message));
+error_schema(Codes, Message) when is_list(Codes) andalso is_binary(Message) ->
+    emqx_dashboard_swagger:error_codes(Codes, Message).
 
 
 get_response_body_schema() ->
 get_response_body_schema() ->
     emqx_dashboard_swagger:schema_with_examples(emqx_bridge_schema:get_response(),
     emqx_dashboard_swagger:schema_with_examples(emqx_bridge_schema:get_response(),
@@ -214,7 +222,7 @@ schema("/bridges") ->
                             bridge_info_examples(post)),
                             bridge_info_examples(post)),
             responses => #{
             responses => #{
                 201 => get_response_body_schema(),
                 201 => get_response_body_schema(),
-                400 => error_schema('BAD_ARG', "Create bridge failed")
+                400 => error_schema('BAD_REQUEST', "Create bridge failed")
             }
             }
         }
         }
     };
     };
@@ -242,7 +250,8 @@ schema("/bridges/:id") ->
                             bridge_info_examples(put)),
                             bridge_info_examples(put)),
             responses => #{
             responses => #{
                 200 => get_response_body_schema(),
                 200 => get_response_body_schema(),
-                400 => error_schema('BAD_ARG', "Update bridge failed")
+                404 => error_schema('NOT_FOUND', "Bridge not found"),
+                400 => error_schema('BAD_REQUEST', "Update bridge failed")
             }
             }
         },
         },
         delete => #{
         delete => #{
@@ -299,8 +308,10 @@ schema("/bridges/:id/operation/:operation") ->
         case emqx_bridge:lookup(BridgeType, BridgeName) of
         case emqx_bridge:lookup(BridgeType, BridgeName) of
             {ok, _} ->
             {ok, _} ->
                 case ensure_bridge_created(BridgeType, BridgeName, Conf) of
                 case ensure_bridge_created(BridgeType, BridgeName, Conf) of
-                    ok -> lookup_from_all_nodes(BridgeType, BridgeName, 200);
-                    {error, Error} -> {400, Error}
+                    ok ->
+                        lookup_from_all_nodes(BridgeType, BridgeName, 200);
+                    {error, Error} ->
+                        {400, Error}
                 end;
                 end;
             {error, not_found} ->
             {error, not_found} ->
                 {404, error_msg('NOT_FOUND',<<"bridge not found">>)}
                 {404, error_msg('NOT_FOUND',<<"bridge not found">>)}
@@ -335,7 +346,7 @@ lookup_from_local_node(BridgeType, BridgeName) ->
 '/bridges/:id/operation/:operation'(post, #{bindings :=
 '/bridges/:id/operation/:operation'(post, #{bindings :=
         #{id := Id, operation := Op}}) ->
         #{id := Id, operation := Op}}) ->
     ?TRY_PARSE_ID(Id, case operation_to_conf_req(Op) of
     ?TRY_PARSE_ID(Id, case operation_to_conf_req(Op) of
-        invalid -> {404, error_msg('BAD_ARG', <<"invalid operation">>)};
+        invalid -> {400, error_msg('BAD_REQUEST', <<"invalid operation">>)};
         UpReq ->
         UpReq ->
             case emqx_conf:update(emqx_bridge:config_key_path() ++ [BridgeType, BridgeName],
             case emqx_conf:update(emqx_bridge:config_key_path() ++ [BridgeType, BridgeName],
                     {UpReq, BridgeType, BridgeName}, #{override_to => cluster}) of
                     {UpReq, BridgeType, BridgeName}, #{override_to => cluster}) of
@@ -357,7 +368,7 @@ ensure_bridge_created(BridgeType, BridgeName, Conf) ->
             Conf, #{override_to => cluster}) of
             Conf, #{override_to => cluster}) of
         {ok, _} -> ok;
         {ok, _} -> ok;
         {error, Reason} ->
         {error, Reason} ->
-            {error, error_msg('BAD_ARG', Reason)}
+            {error, error_msg('BAD_REQUEST', Reason)}
     end.
     end.
 
 
 zip_bridges([BridgesFirstNode | _] = BridgesAllNodes) ->
 zip_bridges([BridgesFirstNode | _] = BridgesAllNodes) ->

+ 8 - 1
apps/emqx_conf/src/emqx_cluster_rpc.erl

@@ -75,7 +75,14 @@ start_link() ->
     start_link(node(), ?MODULE, get_retry_ms()).
     start_link(node(), ?MODULE, get_retry_ms()).
 
 
 start_link(Node, Name, RetryMs) ->
 start_link(Node, Name, RetryMs) ->
-    gen_server:start_link({local, Name}, ?MODULE, [Node, RetryMs], []).
+    case gen_server:start_link({local, Name}, ?MODULE, [Node, RetryMs], []) of
+        {ok, Pid} ->
+            {ok, Pid};
+        {error, {already_started, Pid}} ->
+            {ok, Pid};
+        {error, Reason} ->
+            {error, Reason}
+    end.
 
 
 %% @doc return {ok, TnxId, MFARes} the first MFA result when all MFA run ok.
 %% @doc return {ok, TnxId, MFARes} the first MFA result when all MFA run ok.
 %% return {error, MFARes} when the first MFA result is no ok or {ok, term()}.
 %% return {error, MFARes} when the first MFA result is no ok or {ok, term()}.

+ 24 - 18
apps/emqx_connector/src/emqx_connector_api.erl

@@ -50,10 +50,10 @@ api_spec() ->
 
 
 paths() -> ["/connectors_test", "/connectors", "/connectors/:id"].
 paths() -> ["/connectors_test", "/connectors", "/connectors/:id"].
 
 
-error_schema(Code, Message) ->
-    [ {code, mk(string(), #{example => Code})}
-    , {message, mk(string(), #{example => Message})}
-    ].
+error_schema(Codes, Message) when is_list(Message) ->
+    error_schema(Codes, list_to_binary(Message));
+error_schema(Codes, Message) when is_binary(Message) ->
+    emqx_dashboard_swagger:error_codes(Codes, Message).
 
 
 put_request_body_schema() ->
 put_request_body_schema() ->
     emqx_dashboard_swagger:schema_with_examples(
     emqx_dashboard_swagger:schema_with_examples(
@@ -134,8 +134,8 @@ schema("/connectors_test") ->
             summary => <<"Test creating connector">>,
             summary => <<"Test creating connector">>,
             requestBody => post_request_body_schema(),
             requestBody => post_request_body_schema(),
             responses => #{
             responses => #{
-                200 => <<"Test connector OK">>,
-                400 => error_schema('TEST_FAILED', "connector test failed")
+                204 => <<"Test connector OK">>,
+                400 => error_schema(['TEST_FAILED'], "connector test failed")
             }
             }
         }
         }
     };
     };
@@ -160,7 +160,7 @@ schema("/connectors") ->
             requestBody => post_request_body_schema(),
             requestBody => post_request_body_schema(),
             responses => #{
             responses => #{
                 201 => get_response_body_schema(),
                 201 => get_response_body_schema(),
-                400 => error_schema('ALREADY_EXISTS', "connector already exists")
+                400 => error_schema(['ALREADY_EXISTS'], "connector already exists")
             }
             }
         }
         }
     };
     };
@@ -175,7 +175,7 @@ schema("/connectors/:id") ->
             parameters => param_path_id(),
             parameters => param_path_id(),
             responses => #{
             responses => #{
                 200 => get_response_body_schema(),
                 200 => get_response_body_schema(),
-                404 => error_schema('NOT_FOUND', "Connector not found")
+                404 => error_schema(['NOT_FOUND'], "Connector not found")
             }
             }
         },
         },
         put => #{
         put => #{
@@ -186,8 +186,7 @@ schema("/connectors/:id") ->
             requestBody => put_request_body_schema(),
             requestBody => put_request_body_schema(),
             responses => #{
             responses => #{
                 200 => get_response_body_schema(),
                 200 => get_response_body_schema(),
-                400 => error_schema('UPDATE_FAIL', "Update failed"),
-                404 => error_schema('NOT_FOUND', "Connector not found")
+                404 => error_schema(['NOT_FOUND'], "Connector not found")
             }},
             }},
         delete => #{
         delete => #{
             tags => [<<"connectors">>],
             tags => [<<"connectors">>],
@@ -196,15 +195,17 @@ schema("/connectors/:id") ->
             parameters => param_path_id(),
             parameters => param_path_id(),
             responses => #{
             responses => #{
                 204 => <<"Delete connector successfully">>,
                 204 => <<"Delete connector successfully">>,
-                400 => error_schema('DELETE_FAIL', "Delete failed")
+                403 => error_schema(['DEPENDENCY_EXISTS'], "Cannot remove dependent connector"),
+                404 => error_schema(['NOT_FOUND'], "Delete failed, not found")
             }}
             }}
     }.
     }.
 
 
 '/connectors_test'(post, #{body := #{<<"type">> := ConnType} = Params}) ->
 '/connectors_test'(post, #{body := #{<<"type">> := ConnType} = Params}) ->
     case emqx_connector:create_dry_run(ConnType, maps:remove(<<"type">>, Params)) of
     case emqx_connector:create_dry_run(ConnType, maps:remove(<<"type">>, Params)) of
-        ok -> {200};
+        ok ->
+            {204};
         {error, Error} ->
         {error, Error} ->
-            {400, error_msg('BAD_ARG', Error)}
+            {400, error_msg(['TEST_FAILED'], Error)}
     end.
     end.
 
 
 '/connectors'(get, _Request) ->
 '/connectors'(get, _Request) ->
@@ -221,14 +222,16 @@ schema("/connectors/:id") ->
                 {ok, #{raw_config := RawConf}} ->
                 {ok, #{raw_config := RawConf}} ->
                     Id = emqx_connector:connector_id(ConnType, ConnName),
                     Id = emqx_connector:connector_id(ConnType, ConnName),
                     {201, format_resp(Id, RawConf)};
                     {201, format_resp(Id, RawConf)};
-                {error, Error} -> {400, error_msg('BAD_ARG', Error)}
+                {error, Error} ->
+                    {400, error_msg('ALREADY_EXISTS', Error)}
             end
             end
     end.
     end.
 
 
 '/connectors/:id'(get, #{bindings := #{id := Id}}) ->
 '/connectors/:id'(get, #{bindings := #{id := Id}}) ->
     ?TRY_PARSE_ID(Id,
     ?TRY_PARSE_ID(Id,
         case emqx_connector:lookup(ConnType, ConnName) of
         case emqx_connector:lookup(ConnType, ConnName) of
-            {ok, Conf} -> {200, format_resp(Id, Conf)};
+            {ok, Conf} ->
+                {200, format_resp(Id, Conf)};
             {error, not_found} ->
             {error, not_found} ->
                 {404, error_msg('NOT_FOUND', <<"connector not found">>)}
                 {404, error_msg('NOT_FOUND', <<"connector not found">>)}
         end);
         end);
@@ -241,7 +244,8 @@ schema("/connectors/:id") ->
                 case emqx_connector:update(ConnType, ConnName, Params) of
                 case emqx_connector:update(ConnType, ConnName, Params) of
                     {ok, #{raw_config := RawConf}} ->
                     {ok, #{raw_config := RawConf}} ->
                         {200, format_resp(Id, RawConf)};
                         {200, format_resp(Id, RawConf)};
-                    {error, Error} -> {400, error_msg('BAD_ARG', Error)}
+                    {error, Error} ->
+                        {500, error_msg('INTERNAL_ERROR', Error)}
                 end;
                 end;
             {error, not_found} ->
             {error, not_found} ->
                 {404, error_msg('NOT_FOUND', <<"connector not found">>)}
                 {404, error_msg('NOT_FOUND', <<"connector not found">>)}
@@ -252,12 +256,14 @@ schema("/connectors/:id") ->
         case emqx_connector:lookup(ConnType, ConnName) of
         case emqx_connector:lookup(ConnType, ConnName) of
             {ok, _} ->
             {ok, _} ->
                 case emqx_connector:delete(ConnType, ConnName) of
                 case emqx_connector:delete(ConnType, ConnName) of
-                    {ok, _} -> {204};
+                    {ok, _} ->
+                        {204};
                     {error, {post_config_update, _, {dependency_bridges_exist, BridgeID}}} ->
                     {error, {post_config_update, _, {dependency_bridges_exist, BridgeID}}} ->
                         {403, error_msg('DEPENDENCY_EXISTS',
                         {403, error_msg('DEPENDENCY_EXISTS',
                              <<"Cannot remove the connector as it's in use by a bridge: ",
                              <<"Cannot remove the connector as it's in use by a bridge: ",
                                 BridgeID/binary>>)};
                                 BridgeID/binary>>)};
-                    {error, Error} -> {400, error_msg('BAD_ARG', Error)}
+                    {error, Error} ->
+                        {500, error_msg('INTERNAL_ERROR', Error)}
                 end;
                 end;
             {error, not_found} ->
             {error, not_found} ->
                 {404, error_msg('NOT_FOUND', <<"connector not found">>)}
                 {404, error_msg('NOT_FOUND', <<"connector not found">>)}

+ 2 - 2
apps/emqx_connector/test/emqx_connector_api_SUITE.erl

@@ -377,7 +377,7 @@ t_mqtt_conn_update(_) ->
 
 
     %% then we try to update 'server' of the connector, to an unavailable IP address
     %% then we try to update 'server' of the connector, to an unavailable IP address
     %% the update should fail because of 'unreachable' or 'connrefused'
     %% the update should fail because of 'unreachable' or 'connrefused'
-    {ok, 400, _ErrorMsg} = request(put, uri(["connectors", ConnctorID]),
+    {ok, 500, _ErrorMsg} = request(put, uri(["connectors", ConnctorID]),
                                  ?MQTT_CONNECTOR2(<<"127.0.0.1:2603">>)),
                                  ?MQTT_CONNECTOR2(<<"127.0.0.1:2603">>)),
     %% we fix the 'server' parameter to a normal one, it should work
     %% we fix the 'server' parameter to a normal one, it should work
     {ok, 200, _} = request(put, uri(["connectors", ConnctorID]),
     {ok, 200, _} = request(put, uri(["connectors", ConnctorID]),
@@ -468,7 +468,7 @@ t_mqtt_conn_update3(_) ->
 t_mqtt_conn_testing(_) ->
 t_mqtt_conn_testing(_) ->
     %% APIs for testing the connectivity
     %% APIs for testing the connectivity
     %% then we add a mqtt connector, using POST
     %% then we add a mqtt connector, using POST
-    {ok, 200, <<>>} = request(post, uri(["connectors_test"]),
+    {ok, 204, <<>>} = request(post, uri(["connectors_test"]),
         ?MQTT_CONNECTOR2(<<"127.0.0.1:1883">>)#{
         ?MQTT_CONNECTOR2(<<"127.0.0.1:1883">>)#{
             <<"type">> => ?CONNECTR_TYPE,
             <<"type">> => ?CONNECTR_TYPE,
             <<"name">> => ?BRIDGE_NAME_EGRESS
             <<"name">> => ?BRIDGE_NAME_EGRESS

+ 56 - 0
apps/emqx_dashboard/src/emqx_dashboard_error_code.erl

@@ -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
+    }.

+ 91 - 0
apps/emqx_dashboard/src/emqx_dashboard_error_code_api.erl

@@ -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.

+ 121 - 0
apps/emqx_dashboard/test/emqx_dashboard_error_code_SUITE.erl

@@ -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.

+ 10 - 8
apps/emqx_management/src/emqx_mgmt_api_banned.erl

@@ -67,8 +67,9 @@ schema("/banned") ->
             'requestBody' => hoconsc:mk(hoconsc:ref(ban)),
             'requestBody' => hoconsc:mk(hoconsc:ref(ban)),
             responses => #{
             responses => #{
                 200 => [{data, hoconsc:mk(hoconsc:array(hoconsc:ref(ban)), #{})}],
                 200 => [{data, hoconsc:mk(hoconsc:array(hoconsc:ref(ban)), #{})}],
-                400 => emqx_dashboard_swagger:error_codes(['ALREADY_EXISTED'],
-                                                          <<"Banned already existed">>)
+                400 => emqx_dashboard_swagger:error_codes(
+                           ['ALREADY_EXISTED', 'BAD_REQUEST'],
+                            <<"Banned already existed, or bad args">>)
             }
             }
         }
         }
     };
     };
@@ -89,8 +90,9 @@ schema("/banned/:as/:who") ->
                 ],
                 ],
             responses => #{
             responses => #{
                 204 => <<"Delete banned success">>,
                 204 => <<"Delete banned success">>,
-                404 => emqx_dashboard_swagger:error_codes(['RESOURCE_NOT_FOUND'],
-                                                          <<"Banned not found">>)
+                404 => emqx_dashboard_swagger:error_codes(
+                           ['NOT_FOUND'],
+                            <<"Banned not found. May be the banned time has been exceeded">>)
             }
             }
         }
         }
     }.
     }.
@@ -104,7 +106,7 @@ fields(ban) ->
         {who, hoconsc:mk(binary(), #{
         {who, hoconsc:mk(binary(), #{
             desc => <<"Client info as banned type">>,
             desc => <<"Client info as banned type">>,
             nullable => false,
             nullable => false,
-            example => <<"Badass坏"/utf8>>})},
+            example => <<"Banned name"/utf8>>})},
         {by, hoconsc:mk(binary(), #{
         {by, hoconsc:mk(binary(), #{
             desc => <<"Commander">>,
             desc => <<"Commander">>,
             nullable => true,
             nullable => true,
@@ -134,12 +136,12 @@ banned(get, #{query_string := Params}) ->
 banned(post, #{body := Body}) ->
 banned(post, #{body := Body}) ->
     case emqx_banned:parse(Body) of
     case emqx_banned:parse(Body) of
         {error, Reason} ->
         {error, Reason} ->
-            {400, #{code => 'PARAMS_ERROR', message => list_to_binary(Reason)}};
+            {400, 'BAD_REQUEST', list_to_binary(Reason)};
         Ban ->
         Ban ->
             case emqx_banned:create(Ban) of
             case emqx_banned:create(Ban) of
                 {ok, Banned} -> {200, format(Banned)};
                 {ok, Banned} -> {200, format(Banned)};
                 {error, {already_exist, Old}} ->
                 {error, {already_exist, Old}} ->
-                    {400, #{code => 'ALREADY_EXISTED', message => format(Old)}}
+                    {400, 'ALREADY_EXISTED', format(Old)}
             end
             end
     end.
     end.
 
 
@@ -148,7 +150,7 @@ delete_banned(delete, #{bindings := Params}) ->
         [] ->
         [] ->
             #{as := As0, who := Who0} = Params,
             #{as := As0, who := Who0} = Params,
             Message = list_to_binary(io_lib:format("~p: ~s not found", [As0, Who0])),
             Message = list_to_binary(io_lib:format("~p: ~s not found", [As0, Who0])),
-            {404, #{code => 'RESOURCE_NOT_FOUND', message => Message}};
+            {404, 'NOT_FOUND', Message};
         _ ->
         _ ->
             ok = emqx_banned:delete(Params),
             ok = emqx_banned:delete(Params),
             {204}
             {204}

+ 19 - 11
apps/emqx_modules/src/emqx_topic_metrics_api.erl

@@ -41,10 +41,9 @@
         , fields/1
         , fields/1
         ]).
         ]).
 
 
--define(ERROR_TOPIC, 'ERROR_TOPIC').
 -define(EXCEED_LIMIT, 'EXCEED_LIMIT').
 -define(EXCEED_LIMIT, 'EXCEED_LIMIT').
 -define(BAD_TOPIC, 'BAD_TOPIC').
 -define(BAD_TOPIC, 'BAD_TOPIC').
--define(BAD_RPC, 'BAD_RPC').
+-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND').
 -define(BAD_REQUEST, 'BAD_REQUEST').
 -define(BAD_REQUEST, 'BAD_REQUEST').
 
 
 api_spec() ->
 api_spec() ->
@@ -62,7 +61,8 @@ schema("/mqtt/topic_metrics") ->
            #{ description => <<"List topic metrics">>
            #{ description => <<"List topic metrics">>
             , tags => ?API_TAG_MQTT
             , tags => ?API_TAG_MQTT
             , responses  =>
             , 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 =>
      , put =>
            #{ description => <<"Reset topic metrics by topic name. Or reset all Topic Metrics">>
            #{ description => <<"Reset topic metrics by topic name. Or reset all Topic Metrics">>
@@ -72,7 +72,9 @@ schema("/mqtt/topic_metrics") ->
                                  reset_examples())
                                  reset_examples())
             , responses =>
             , responses =>
                   #{ 204 => <<"Reset topic metrics successfully">>
                   #{ 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 =>
      , post =>
@@ -81,8 +83,10 @@ schema("/mqtt/topic_metrics") ->
             , 'requestBody' => [topic(body)]
             , 'requestBody' => [topic(body)]
             , responses =>
             , responses =>
                   #{ 204 => <<"Create topic metrics success">>
                   #{ 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)]
             , parameters => [topic(path)]
             , responses =>
             , responses =>
                   #{ 200 => mk(ref(topic_metrics), #{ desc => <<"Topic metrics">> })
                   #{ 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 =>
      , delete =>
@@ -103,7 +108,8 @@ schema("/mqtt/topic_metrics/:topic") ->
             , parameters => [topic(path)]
             , parameters => [topic(path)]
             , responses =>
             , responses =>
                   #{ 204 => <<"Removed topic metrics successfully">>,
                   #{ 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) ->
 fields(reset) ->
     [ {topic
     [ {topic
       , mk( binary()
       , 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">>
              , example => <<"testtopic/1">>
              , nullable => true})}
              , nullable => true})}
     , {action
     , {action
@@ -399,10 +407,10 @@ reason2httpresp(already_existed) ->
     {400, #{code => ?BAD_TOPIC, message => Msg}};
     {400, #{code => ?BAD_TOPIC, message => Msg}};
 reason2httpresp(topic_not_found) ->
 reason2httpresp(topic_not_found) ->
     Msg = <<"Topic not found">>,
     Msg = <<"Topic not found">>,
-    {404, #{code => ?ERROR_TOPIC, message => Msg}};
+    {404, #{code => ?TOPIC_NOT_FOUND, message => Msg}};
 reason2httpresp(not_found) ->
 reason2httpresp(not_found) ->
     Msg = <<"Topic not found">>,
     Msg = <<"Topic not found">>,
-    {404, #{code => ?ERROR_TOPIC, message => Msg}}.
+    {404, #{code => ?TOPIC_NOT_FOUND, message => Msg}}.
 
 
 get_cluster_response(Args) ->
 get_cluster_response(Args) ->
     case erlang:apply(?MODULE, cluster_accumulation_metrics, Args) of
     case erlang:apply(?MODULE, cluster_accumulation_metrics, Args) of

+ 1 - 2
apps/emqx_prometheus/rebar.config

@@ -4,8 +4,7 @@
  [ {emqx, {path, "../emqx"}},
  [ {emqx, {path, "../emqx"}},
    %% FIXME: tag this as v3.1.3
    %% FIXME: tag this as v3.1.3
    {prometheus, {git, "https://github.com/deadtrickster/prometheus.erl", {tag, "v4.8.1"}}},
    {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}]}.
 {edoc_opts, [{preprocess, true}]}.

+ 1 - 1
mix.exs

@@ -57,7 +57,7 @@ defmodule EMQXUmbrella.MixProject do
       {:mria, github: "emqx/mria", tag: "0.2.0", override: true},
       {:mria, github: "emqx/mria", tag: "0.2.0", override: true},
       {:ekka, github: "emqx/ekka", tag: "0.12.1", override: true},
       {:ekka, github: "emqx/ekka", tag: "0.12.1", override: true},
       {:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.0", 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"},
       {:ecpool, github: "emqx/ecpool", tag: "0.5.2"},
       {:replayq, "0.3.3", override: true},
       {:replayq, "0.3.3", override: true},
       {:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
       {:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},

+ 1 - 1
rebar.config

@@ -56,7 +56,7 @@
     , {mria, {git, "https://github.com/emqx/mria", {tag, "0.2.0"}}}
     , {mria, {git, "https://github.com/emqx/mria", {tag, "0.2.0"}}}
     , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.12.1"}}}
     , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.12.1"}}}
     , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.0"}}}
     , {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"}}}
     , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.2"}}}
     , {replayq, "0.3.3"}
     , {replayq, "0.3.3"}
     , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
     , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}