Merge pull request #7027 from DDDHuang/error_code

feat: support http error code & error code api
This commit is contained in:
zhongwencool 2022-02-23 16:12:20 +08:00 committed by GitHub
commit b7f808db9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 437 additions and 59 deletions

View File

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

View File

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

View File

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

View File

@ -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, -export([ '/bridges'/2
'/bridges/:id/operation/:operation'/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) -> error_schema(Code, Message) when is_atom(Code) ->
[ {code, mk(string(), #{example => Code})} error_schema([Code], Message);
, {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_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); ok ->
{error, Error} -> {400, Error} 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) ->

View File

@ -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()}.

View File

@ -50,10 +50,10 @@ api_spec() ->
paths() -> ["/connectors_test", "/connectors", "/connectors/:id"]. paths() -> ["/connectors_test", "/connectors", "/connectors/:id"].
error_schema(Code, Message) -> error_schema(Codes, Message) when is_list(Message) ->
[ {code, mk(string(), #{example => Code})} error_schema(Codes, list_to_binary(Message));
, {message, mk(string(), #{example => 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">>, 204 => <<"Test connector OK">>,
400 => error_schema('TEST_FAILED', "connector test failed") 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">>)}

View File

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

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

@ -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'], 400 => emqx_dashboard_swagger:error_codes(
<<"Banned already existed">>) ['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'], 404 => emqx_dashboard_swagger:error_codes(
<<"Banned not found">>) ['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}

View File

@ -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">>) , 409 => emqx_dashboard_swagger:error_codes([?EXCEED_LIMIT],
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST, ?BAD_TOPIC], <<"Topic metrics already existed or bad topic">>) <<"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

View File

@ -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"}}}, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.24.0"}}}
{minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.11"}}}
]}. ]}.
{edoc_opts, [{preprocess, true}]}. {edoc_opts, [{preprocess, true}]}.

View File

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

View File

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