feat: support http error code & error code api
This commit is contained in:
parent
9b53d36571
commit
90ee450a84
|
@ -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">>}
|
||||
]).
|
|
@ -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
|
||||
}.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}]}.
|
||||
|
|
2
mix.exs
2
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},
|
||||
|
|
|
@ -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"}}}
|
||||
|
|
Loading…
Reference in New Issue