Jelajahi Sumber

feat: support http error code & error code api

DDDHuang 4 tahun lalu
induk
melakukan
90ee450a84

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

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

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

+ 1 - 1
apps/emqx_management/src/emqx_mgmt_api_banned.erl

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

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

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

+ 1 - 2
apps/emqx_prometheus/rebar.config

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

+ 1 - 1
mix.exs

@@ -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},

+ 1 - 1
rebar.config

@@ -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"}}}