feat: obfuscate sensitive values default_password
This commit is contained in:
parent
575d34cb69
commit
5223c3ee61
|
@ -29,7 +29,7 @@
|
|||
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.1"}}},
|
||||
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.12.3"}}},
|
||||
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}},
|
||||
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.27.2"}}},
|
||||
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.27.3"}}},
|
||||
{pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}},
|
||||
{recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}},
|
||||
{snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.18.0"}}}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
check_config/2,
|
||||
fill_defaults/1,
|
||||
fill_defaults/2,
|
||||
fill_defaults/3,
|
||||
save_configs/5,
|
||||
save_to_app_env/1,
|
||||
save_to_config_map/2,
|
||||
|
@ -246,7 +247,7 @@ get_default_value([RootName | _] = KeyPath) ->
|
|||
case find_raw([RootName]) of
|
||||
{ok, RawConf} ->
|
||||
RawConf1 = emqx_map_lib:deep_remove(BinKeyPath, #{bin(RootName) => RawConf}),
|
||||
try fill_defaults(get_schema_mod(RootName), RawConf1) of
|
||||
try fill_defaults(get_schema_mod(RootName), RawConf1, #{}) of
|
||||
FullConf ->
|
||||
case emqx_map_lib:deep_find(BinKeyPath, FullConf) of
|
||||
{not_found, _, _} -> {error, no_default_value};
|
||||
|
@ -360,15 +361,18 @@ check_config(SchemaMod, RawConf, Opts0) ->
|
|||
hocon_tconf:map_translate(SchemaMod, RawConf, Opts),
|
||||
{AppEnvs, emqx_map_lib:unsafe_atom_key_map(CheckedConf)}.
|
||||
|
||||
-spec fill_defaults(raw_config()) -> map().
|
||||
fill_defaults(RawConf) ->
|
||||
fill_defaults(RawConf, #{}).
|
||||
|
||||
-spec fill_defaults(raw_config(), hocon_tconf:opts()) -> map().
|
||||
fill_defaults(RawConf, Opts) ->
|
||||
RootNames = get_root_names(),
|
||||
maps:fold(
|
||||
fun(Key, Conf, Acc) ->
|
||||
SubMap = #{Key => Conf},
|
||||
WithDefaults =
|
||||
case lists:member(Key, RootNames) of
|
||||
true -> fill_defaults(get_schema_mod(Key), SubMap);
|
||||
true -> fill_defaults(get_schema_mod(Key), SubMap, Opts);
|
||||
false -> SubMap
|
||||
end,
|
||||
maps:merge(Acc, WithDefaults)
|
||||
|
@ -377,12 +381,13 @@ fill_defaults(RawConf) ->
|
|||
RawConf
|
||||
).
|
||||
|
||||
-spec fill_defaults(module(), raw_config()) -> map().
|
||||
fill_defaults(SchemaMod, RawConf) ->
|
||||
-spec fill_defaults(module(), raw_config(), hocon_tconf:opts()) -> map().
|
||||
fill_defaults(SchemaMod, RawConf, Opts0) ->
|
||||
Opts = maps:merge(#{required => false, only_fill_defaults => true}, Opts0),
|
||||
hocon_tconf:check_plain(
|
||||
SchemaMod,
|
||||
RawConf,
|
||||
#{required => false, only_fill_defaults => true},
|
||||
Opts,
|
||||
root_names_from_conf(RawConf)
|
||||
).
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ do_list_raw() ->
|
|||
Key = <<"listeners">>,
|
||||
Raw = emqx_config:get_raw([Key], #{}),
|
||||
SchemaMod = emqx_config:get_schema_mod(Key),
|
||||
#{Key := RawWithDefault} = emqx_config:fill_defaults(SchemaMod, #{Key => Raw}),
|
||||
#{Key := RawWithDefault} = emqx_config:fill_defaults(SchemaMod, #{Key => Raw}, #{}),
|
||||
Listeners = maps:to_list(RawWithDefault),
|
||||
lists:flatmap(fun format_raw_listeners/1, Listeners).
|
||||
|
||||
|
|
|
@ -532,7 +532,7 @@ format_metrics(#{
|
|||
|
||||
fill_defaults(Type, RawConf) ->
|
||||
PackedConf = pack_bridge_conf(Type, RawConf),
|
||||
FullConf = emqx_config:fill_defaults(emqx_bridge_schema, PackedConf),
|
||||
FullConf = emqx_config:fill_defaults(emqx_bridge_schema, PackedConf, #{}),
|
||||
unpack_bridge_conf(Type, FullConf).
|
||||
|
||||
pack_bridge_conf(Type, RawConf) ->
|
||||
|
|
|
@ -19,7 +19,7 @@ but use the same port."""
|
|||
desc {
|
||||
en: """How often to update metrics displayed in the dashboard.<br/>
|
||||
Note: `sample_interval` should be a divisor of 60."""
|
||||
zh: """更新仪表板中显示的指标的时间间隔。"""
|
||||
zh: """更新仪表板中显示的指标的时间间隔。必须小于60,且被60的整除。"""
|
||||
}
|
||||
}
|
||||
token_expired_time {
|
||||
|
@ -135,7 +135,7 @@ Note: `sample_interval` should be a divisor of 60."""
|
|||
bind {
|
||||
desc {
|
||||
en: "Port without IP(18083) or port with specified IP(127.0.0.1:18083)."
|
||||
zh: "监听的地址与端口"
|
||||
zh: "监听的地址与端口,在dashboard更新此配置时,会重启dashboard服务。"
|
||||
}
|
||||
label {
|
||||
en: "Bind"
|
||||
|
@ -180,7 +180,7 @@ its own from which a browser should permit loading resources."""
|
|||
i18n_lang {
|
||||
desc {
|
||||
en: "Internationalization language support."
|
||||
zh: "多语言支持"
|
||||
zh: "swagger多语言支持"
|
||||
}
|
||||
label {
|
||||
en: "I18n language"
|
|
@ -47,12 +47,10 @@
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
start_listeners() ->
|
||||
Listeners = emqx_conf:get([dashboard, listeners], []),
|
||||
start_listeners(Listeners).
|
||||
start_listeners(listeners()).
|
||||
|
||||
stop_listeners() ->
|
||||
Listeners = emqx_conf:get([dashboard, listeners], []),
|
||||
stop_listeners(Listeners).
|
||||
stop_listeners(listeners()).
|
||||
|
||||
start_listeners(Listeners) ->
|
||||
{ok, _} = application:ensure_all_started(minirest),
|
||||
|
@ -155,10 +153,13 @@ apps() ->
|
|||
].
|
||||
|
||||
listeners(Listeners) ->
|
||||
lists:map(fun({Protocol, Conf}) ->
|
||||
lists:map(
|
||||
fun({Protocol, Conf}) ->
|
||||
{Conf1, Bind} = ip_port(Conf),
|
||||
{listener_name(Protocol, Conf1), Protocol, Bind, ranch_opts(Conf1)}
|
||||
end, maps:to_list(Listeners)).
|
||||
end,
|
||||
maps:to_list(Listeners)
|
||||
).
|
||||
|
||||
ip_port(Opts) -> ip_port(maps:take(bind, Opts), Opts).
|
||||
|
||||
|
@ -268,3 +269,6 @@ i18n_file() ->
|
|||
undefined -> emqx:etc_file("i18n.conf");
|
||||
{ok, File} -> File
|
||||
end.
|
||||
|
||||
listeners() ->
|
||||
emqx_conf:get([dashboard, listeners], []).
|
||||
|
|
|
@ -32,7 +32,7 @@ fields("dashboard") ->
|
|||
{listeners,
|
||||
sc(
|
||||
ref("listeners"),
|
||||
#{ desc => ?DESC(listeners)}
|
||||
#{desc => ?DESC(listeners)}
|
||||
)},
|
||||
{default_username, fun default_username/1},
|
||||
{default_password, fun default_password/1},
|
||||
|
@ -146,38 +146,26 @@ bind(required) -> true;
|
|||
bind(desc) -> ?DESC(bind);
|
||||
bind(_) -> undefined.
|
||||
|
||||
default_username(type) -> string();
|
||||
default_username(type) -> binary();
|
||||
default_username(default) -> "admin";
|
||||
default_username(required) -> true;
|
||||
default_username(desc) -> ?DESC(default_username);
|
||||
default_username(desc) -> ?DESC(default_username);
|
||||
default_username('readOnly') -> true;
|
||||
default_username(_) -> undefined.
|
||||
|
||||
default_password(type) ->
|
||||
string();
|
||||
default_password(default) ->
|
||||
"public";
|
||||
default_password(required) ->
|
||||
true;
|
||||
default_password('readOnly') ->
|
||||
true;
|
||||
default_password(sensitive) ->
|
||||
true;
|
||||
default_password(desc) ->
|
||||
?DESC(default_password);
|
||||
default_password(_) ->
|
||||
undefined.
|
||||
default_password(type) -> binary();
|
||||
default_password(default) -> "public";
|
||||
default_password(required) -> true;
|
||||
default_password('readOnly') -> true;
|
||||
default_password(sensitive) -> true;
|
||||
default_password(desc) -> ?DESC(default_password);
|
||||
default_password(_) -> undefined.
|
||||
|
||||
cors(type) ->
|
||||
boolean();
|
||||
cors(default) ->
|
||||
false;
|
||||
cors(required) ->
|
||||
false;
|
||||
cors(desc) ->
|
||||
?DESC(cors);
|
||||
cors(_) ->
|
||||
undefined.
|
||||
cors(type) -> boolean();
|
||||
cors(default) -> false;
|
||||
cors(required) -> false;
|
||||
cors(desc) -> ?DESC(cors);
|
||||
cors(_) -> undefined.
|
||||
|
||||
i18n_lang(type) -> ?ENUM([en, zh]);
|
||||
i18n_lang(default) -> en;
|
||||
|
@ -187,8 +175,11 @@ i18n_lang(_) -> undefined.
|
|||
|
||||
validate_sample_interval(Second) ->
|
||||
case Second >= 1 andalso Second =< 60 andalso (60 rem Second =:= 0) of
|
||||
true -> ok;
|
||||
false -> error({"Sample interval must be between 1 and 60 and be a divisor of 60.", Second})
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
Msg = "must be between 1 and 60 and be a divisor of 60.",
|
||||
{error, Msg}
|
||||
end.
|
||||
|
||||
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
|
||||
|
|
|
@ -138,7 +138,7 @@ fields(limit) ->
|
|||
[{limit, hoconsc:mk(range(1, ?MAX_ROW_LIMIT), Meta)}];
|
||||
fields(count) ->
|
||||
Meta = #{desc => <<"Results count.">>, required => true},
|
||||
[{count, hoconsc:mk(range(0, inf), Meta)}];
|
||||
[{count, hoconsc:mk(non_neg_integer(), Meta)}];
|
||||
fields(meta) ->
|
||||
fields(page) ++ fields(limit) ++ fields(count).
|
||||
|
||||
|
@ -184,12 +184,14 @@ translate_req(Request, #{module := Module, path := Path, method := Method}, Chec
|
|||
NewBody = check_request_body(Request, Body, Module, CheckFun, hoconsc:is_schema(Body)),
|
||||
{ok, Request#{bindings => Bindings, query_string => QueryStr, body => NewBody}}
|
||||
catch
|
||||
throw:{_, ValidErrors} ->
|
||||
Msg = [
|
||||
io_lib:format("~ts : ~p", [Key, Reason])
|
||||
|| {validation_error, #{path := Key, reason := Reason}} <- ValidErrors
|
||||
],
|
||||
{400, 'BAD_REQUEST', iolist_to_binary(string:join(Msg, ","))}
|
||||
throw:HoconError ->
|
||||
Msg = serialize_hocon_error_msg(HoconError),
|
||||
%Msg = [
|
||||
% io_lib:format("~ts : ~p", [Key -- "root.", Reason])
|
||||
% || {validation_error, #{path := Key, reason := Reason}} <- ValidErrors
|
||||
% ],
|
||||
% iolist_to_binary(string:join(Msg, ",")
|
||||
{400, 'BAD_REQUEST', Msg}
|
||||
end.
|
||||
|
||||
check_and_translate(Schema, Map, Opts) ->
|
||||
|
@ -808,3 +810,18 @@ to_ref(Mod, StructName, Acc, RefsAcc) ->
|
|||
|
||||
schema_converter(Options) ->
|
||||
maps:get(schema_converter, Options, fun hocon_schema_to_spec/2).
|
||||
|
||||
serialize_hocon_error_msg({_Schema, Errors}) ->
|
||||
Msg = lists:map(fun hocon_error/1, Errors),
|
||||
iolist_to_binary(string:join(Msg, ",")).
|
||||
|
||||
hocon_error({validation_error, #{reason := #{exception := Exception}, path := Path}}) ->
|
||||
io_lib:format("~ts: ~p", [sub_path(Path), Exception]);
|
||||
hocon_error({validation_error, #{reason := Reason, path := Path, value := Value}}) ->
|
||||
io_lib:format("~ts: ~p ~p", [sub_path(Path), Value, Reason]);
|
||||
hocon_error({validation_error, #{reason := Reason, path := Path}}) ->
|
||||
io_lib:format("~ts: ~p", [sub_path(Path), Reason]);
|
||||
hocon_error({translation_error, #{reason := Reason, value_path := Path}}) ->
|
||||
io_lib:format("~ts: ~p", [sub_path(Path), Reason]).
|
||||
|
||||
sub_path(Path) -> string:trim(Path, leading, "root.").
|
||||
|
|
|
@ -19,11 +19,14 @@
|
|||
-compile(nowarn_export_all).
|
||||
-compile(export_all).
|
||||
|
||||
-import(emqx_common_test_http,
|
||||
[ request_api/3
|
||||
, request_api/5
|
||||
, get_http_data/1
|
||||
]).
|
||||
-import(
|
||||
emqx_common_test_http,
|
||||
[
|
||||
request_api/3,
|
||||
request_api/5,
|
||||
get_http_data/1
|
||||
]
|
||||
).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("emqx/include/emqx.hrl").
|
||||
|
@ -40,18 +43,19 @@
|
|||
-define(APP_DASHBOARD, emqx_dashboard).
|
||||
-define(APP_MANAGEMENT, emqx_management).
|
||||
|
||||
-define(OVERVIEWS, ['alarms/activated',
|
||||
'alarms/deactivated',
|
||||
banned,
|
||||
brokers,
|
||||
stats,
|
||||
metrics,
|
||||
listeners,
|
||||
clients,
|
||||
subscriptions,
|
||||
routes,
|
||||
plugins
|
||||
]).
|
||||
-define(OVERVIEWS, [
|
||||
'alarms/activated',
|
||||
'alarms/deactivated',
|
||||
banned,
|
||||
brokers,
|
||||
stats,
|
||||
metrics,
|
||||
listeners,
|
||||
clients,
|
||||
subscriptions,
|
||||
routes,
|
||||
plugins
|
||||
]).
|
||||
|
||||
all() ->
|
||||
%% TODO: V5 API
|
||||
|
@ -66,8 +70,10 @@ end_suite(Apps) ->
|
|||
emqx_common_test_helpers:stop_apps(Apps ++ [emqx_dashboard]).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
emqx_common_test_helpers:start_apps([emqx_management, emqx_dashboard],
|
||||
fun set_special_configs/1),
|
||||
emqx_common_test_helpers:start_apps(
|
||||
[emqx_management, emqx_dashboard],
|
||||
fun set_special_configs/1
|
||||
),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
|
@ -76,8 +82,10 @@ end_per_suite(_Config) ->
|
|||
|
||||
set_special_configs(emqx_management) ->
|
||||
Listeners = #{http => #{port => 8081}},
|
||||
Config = #{listeners => Listeners,
|
||||
applications => [#{id => "admin", secret => "public"}]},
|
||||
Config = #{
|
||||
listeners => Listeners,
|
||||
applications => [#{id => "admin", secret => "public"}]
|
||||
},
|
||||
emqx_config:put([emqx_management], Config),
|
||||
ok;
|
||||
set_special_configs(emqx_dashboard) ->
|
||||
|
@ -89,8 +97,16 @@ set_special_configs(_) ->
|
|||
t_overview(_) ->
|
||||
mnesia:clear_table(?ADMIN),
|
||||
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"simple_description">>),
|
||||
[?assert(request_dashboard(get, api_path(erlang:atom_to_list(Overview)),
|
||||
auth_header_())) || Overview <- ?OVERVIEWS].
|
||||
[
|
||||
?assert(
|
||||
request_dashboard(
|
||||
get,
|
||||
api_path(erlang:atom_to_list(Overview)),
|
||||
auth_header_()
|
||||
)
|
||||
)
|
||||
|| Overview <- ?OVERVIEWS
|
||||
].
|
||||
|
||||
t_admins_add_delete(_) ->
|
||||
mnesia:clear_table(?ADMIN),
|
||||
|
@ -102,9 +118,11 @@ t_admins_add_delete(_) ->
|
|||
ok = emqx_dashboard_admin:remove_user(<<"username1">>),
|
||||
Users = emqx_dashboard_admin:all_users(),
|
||||
?assertEqual(1, length(Users)),
|
||||
ok = emqx_dashboard_admin:change_password(<<"username">>,
|
||||
<<"password">>,
|
||||
<<"pwd">>),
|
||||
ok = emqx_dashboard_admin:change_password(
|
||||
<<"username">>,
|
||||
<<"password">>,
|
||||
<<"pwd">>
|
||||
),
|
||||
timer:sleep(10),
|
||||
Header = auth_header_(<<"username">>, <<"pwd">>),
|
||||
?assert(request_dashboard(get, api_path("brokers"), Header)),
|
||||
|
@ -117,25 +135,38 @@ t_rest_api(_Config) ->
|
|||
Desc = <<"administrator">>,
|
||||
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, Desc),
|
||||
{ok, 200, Res0} = http_get(["users"]),
|
||||
?assertEqual([#{<<"username">> => <<"admin">>,
|
||||
<<"description">> => <<"administrator">>}], get_http_data(Res0)),
|
||||
?assertEqual(
|
||||
[
|
||||
#{
|
||||
<<"username">> => <<"admin">>,
|
||||
<<"description">> => <<"administrator">>
|
||||
}
|
||||
],
|
||||
get_http_data(Res0)
|
||||
),
|
||||
{ok, 200, _} = http_put(["users", "admin"], #{<<"description">> => <<"a_new_description">>}),
|
||||
{ok, 200, _} = http_post(["users"], #{<<"username">> => <<"usera">>,
|
||||
<<"password">> => <<"passwd">>,
|
||||
<<"description">> => Desc}),
|
||||
{ok, 200, _} = http_post(["users"], #{
|
||||
<<"username">> => <<"usera">>,
|
||||
<<"password">> => <<"passwd">>,
|
||||
<<"description">> => Desc
|
||||
}),
|
||||
{ok, 204, _} = http_delete(["users", "usera"]),
|
||||
{ok, 404, _} = http_delete(["users", "usera"]),
|
||||
{ok, 204, _} = http_put( ["users", "admin", "change_pwd"]
|
||||
, #{<<"old_pwd">> => <<"public">>,
|
||||
<<"new_pwd">> => <<"newpwd">>}),
|
||||
{ok, 204, _} = http_put(
|
||||
["users", "admin", "change_pwd"],
|
||||
#{
|
||||
<<"old_pwd">> => <<"public">>,
|
||||
<<"new_pwd">> => <<"newpwd">>
|
||||
}
|
||||
),
|
||||
mnesia:clear_table(?ADMIN),
|
||||
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"administrator">>),
|
||||
ok.
|
||||
|
||||
t_cli(_Config) ->
|
||||
[mria:dirty_delete(?ADMIN, Admin) || Admin <- mnesia:dirty_all_keys(?ADMIN)],
|
||||
[mria:dirty_delete(?ADMIN, Admin) || Admin <- mnesia:dirty_all_keys(?ADMIN)],
|
||||
emqx_dashboard_cli:admins(["add", "username", "password"]),
|
||||
[#?ADMIN{ username = <<"username">>, pwdhash = <<Salt:4/binary, Hash/binary>>}] =
|
||||
[#?ADMIN{username = <<"username">>, pwdhash = <<Salt:4/binary, Hash/binary>>}] =
|
||||
emqx_dashboard_admin:lookup_user(<<"username">>),
|
||||
?assertEqual(Hash, crypto:hash(sha256, <<Salt/binary, <<"password">>/binary>>)),
|
||||
emqx_dashboard_cli:admins(["passwd", "username", "newpassword"]),
|
||||
|
@ -153,8 +184,10 @@ t_lookup_by_username_jwt(_Config) ->
|
|||
User = bin(["user-", integer_to_list(random_num())]),
|
||||
Pwd = bin(integer_to_list(random_num())),
|
||||
emqx_dashboard_token:sign(User, Pwd),
|
||||
?assertMatch([#?ADMIN_JWT{username = User}],
|
||||
emqx_dashboard_token:lookup_by_username(User)),
|
||||
?assertMatch(
|
||||
[#?ADMIN_JWT{username = User}],
|
||||
emqx_dashboard_token:lookup_by_username(User)
|
||||
),
|
||||
ok = emqx_dashboard_token:destroy_by_username(User),
|
||||
%% issue a gen_server call to sync the async destroy gen_server cast
|
||||
ok = gen_server:call(emqx_dashboard_token, dummy, infinity),
|
||||
|
@ -168,8 +201,10 @@ t_clean_expired_jwt(_Config) ->
|
|||
[#?ADMIN_JWT{username = User, exptime = ExpTime}] =
|
||||
emqx_dashboard_token:lookup_by_username(User),
|
||||
ok = emqx_dashboard_token:clean_expired_jwt(_Now1 = ExpTime),
|
||||
?assertMatch([#?ADMIN_JWT{username = User}],
|
||||
emqx_dashboard_token:lookup_by_username(User)),
|
||||
?assertMatch(
|
||||
[#?ADMIN_JWT{username = User}],
|
||||
emqx_dashboard_token:lookup_by_username(User)
|
||||
),
|
||||
ok = emqx_dashboard_token:clean_expired_jwt(_Now2 = ExpTime + 1),
|
||||
?assertMatch([], emqx_dashboard_token:lookup_by_username(User)),
|
||||
ok.
|
||||
|
@ -201,13 +236,14 @@ request_dashboard(Method, Url, Auth) ->
|
|||
request_dashboard(Method, Url, QueryParams, Auth) ->
|
||||
Request = {Url ++ "?" ++ QueryParams, [Auth]},
|
||||
do_request_dashboard(Method, Request).
|
||||
do_request_dashboard(Method, Request)->
|
||||
do_request_dashboard(Method, Request) ->
|
||||
ct:pal("Method: ~p, Request: ~p", [Method, Request]),
|
||||
case httpc:request(Method, Request, [], []) of
|
||||
{error, socket_closed_remotely} ->
|
||||
{error, socket_closed_remotely};
|
||||
{ok, {{"HTTP/1.1", Code, _}, _Headers, Return} }
|
||||
when Code >= 200 andalso Code =< 299 ->
|
||||
{ok, {{"HTTP/1.1", Code, _}, _Headers, Return}} when
|
||||
Code >= 200 andalso Code =< 299
|
||||
->
|
||||
{ok, Return};
|
||||
{ok, {Reason, _, _}} ->
|
||||
{error, Reason}
|
||||
|
@ -218,10 +254,11 @@ auth_header_() ->
|
|||
|
||||
auth_header_(Username, Password) ->
|
||||
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||
{"Authorization","Bearer " ++ binary_to_list(Token)}.
|
||||
{"Authorization", "Bearer " ++ binary_to_list(Token)}.
|
||||
|
||||
api_path(Parts) ->
|
||||
?HOST ++ filename:join([?BASE_PATH | Parts]).
|
||||
|
||||
json(Data) ->
|
||||
{ok, Jsx} = emqx_json:safe_decode(Data, [return_maps]), Jsx.
|
||||
{ok, Jsx} = emqx_json:safe_decode(Data, [return_maps]),
|
||||
Jsx.
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
|
||||
-module(emqx_dashboard_api_test_helpers).
|
||||
|
||||
-export([set_default_config/0,
|
||||
set_default_config/1,
|
||||
request/2,
|
||||
request/3,
|
||||
request/4,
|
||||
uri/0,
|
||||
uri/1]).
|
||||
-export([
|
||||
set_default_config/0,
|
||||
set_default_config/1,
|
||||
request/2,
|
||||
request/3,
|
||||
request/4,
|
||||
uri/0,
|
||||
uri/1
|
||||
]).
|
||||
|
||||
-define(HOST, "http://127.0.0.1:18083/").
|
||||
-define(API_VERSION, "v5").
|
||||
|
@ -32,15 +34,16 @@ set_default_config() ->
|
|||
set_default_config(<<"admin">>).
|
||||
|
||||
set_default_config(DefaultUsername) ->
|
||||
Config = #{listeners => #{
|
||||
http => #{
|
||||
port => 18083
|
||||
}
|
||||
},
|
||||
default_username => DefaultUsername,
|
||||
default_password => <<"public">>,
|
||||
i18n_lang => en
|
||||
},
|
||||
Config = #{
|
||||
listeners => #{
|
||||
http => #{
|
||||
port => 18083
|
||||
}
|
||||
},
|
||||
default_username => DefaultUsername,
|
||||
default_password => <<"public">>,
|
||||
i18n_lang => en
|
||||
},
|
||||
emqx_config:put([dashboard], Config),
|
||||
I18nFile = filename:join([
|
||||
filename:dirname(code:priv_dir(emqx_dashboard)),
|
||||
|
@ -57,17 +60,22 @@ request(Method, Url, Body) ->
|
|||
request(<<"admin">>, Method, Url, Body).
|
||||
|
||||
request(Username, Method, Url, Body) ->
|
||||
Request = case Body of
|
||||
[] when Method =:= get orelse Method =:= put orelse
|
||||
Method =:= head orelse Method =:= delete orelse
|
||||
Method =:= trace -> {Url, [auth_header(Username)]};
|
||||
_ -> {Url, [auth_header(Username)], "application/json", jsx:encode(Body)}
|
||||
end,
|
||||
Request =
|
||||
case Body of
|
||||
[] when
|
||||
Method =:= get orelse Method =:= put orelse
|
||||
Method =:= head orelse Method =:= delete orelse
|
||||
Method =:= trace
|
||||
->
|
||||
{Url, [auth_header(Username)]};
|
||||
_ ->
|
||||
{Url, [auth_header(Username)], "application/json", jsx:encode(Body)}
|
||||
end,
|
||||
ct:pal("Method: ~p, Request: ~p", [Method, Request]),
|
||||
case httpc:request(Method, Request, [], [{body_format, binary}]) of
|
||||
{error, socket_closed_remotely} ->
|
||||
{error, socket_closed_remotely};
|
||||
{ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } ->
|
||||
{ok, {{"HTTP/1.1", Code, _}, _Headers, Return}} ->
|
||||
{ok, Code, Return};
|
||||
{ok, {Reason, _, _}} ->
|
||||
{error, Reason}
|
||||
|
|
|
@ -50,7 +50,7 @@ end_suite() ->
|
|||
|
||||
t_bad_api_path(_) ->
|
||||
Url = ?SERVER ++ "/for/test/some/path/not/exist",
|
||||
{error,{"HTTP/1.1", 404, "Not Found"}} = request(Url),
|
||||
{error, {"HTTP/1.1", 404, "Not Found"}} = request(Url),
|
||||
ok.
|
||||
|
||||
request(Url) ->
|
||||
|
@ -58,8 +58,9 @@ 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, {{"HTTP/1.1", Code, _}, _, Return}} when
|
||||
Code >= 200 andalso Code =< 299
|
||||
->
|
||||
{ok, emqx_json:decode(Return, [return_maps])};
|
||||
{ok, {Reason, _, _}} ->
|
||||
{error, Reason}
|
||||
|
|
|
@ -82,13 +82,18 @@ t_format_code(_) ->
|
|||
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()],
|
||||
[
|
||||
?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, #{
|
||||
<<"code">> := <<"BAD_REQUEST">>,
|
||||
<<"description">> := <<"Request parameters are not legal">>
|
||||
}} = request(Url),
|
||||
ok.
|
||||
|
||||
exist(_CodeName, []) ->
|
||||
|
@ -105,8 +110,9 @@ 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, {{"HTTP/1.1", Code, _}, _, Return}} when
|
||||
Code >= 200 andalso Code =< 299
|
||||
->
|
||||
{ok, emqx_json:decode(Return, [return_maps])};
|
||||
{ok, {Reason, _, _}} ->
|
||||
{error, Reason}
|
||||
|
|
|
@ -47,10 +47,10 @@ set_special_configs(_) ->
|
|||
|
||||
t_monitor_samplers_all(_Config) ->
|
||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
||||
Size = mnesia:table_info(emqx_dashboard_monitor,size),
|
||||
All = emqx_dashboard_monitor:samplers(all, infinity),
|
||||
Size = mnesia:table_info(emqx_dashboard_monitor, size),
|
||||
All = emqx_dashboard_monitor:samplers(all, infinity),
|
||||
All2 = emqx_dashboard_monitor:samplers(),
|
||||
?assert(erlang:length(All) == Size),
|
||||
?assert(erlang:length(All) == Size),
|
||||
?assert(erlang:length(All2) == Size),
|
||||
ok.
|
||||
|
||||
|
@ -87,18 +87,24 @@ t_monitor_api(_) ->
|
|||
t_monitor_current_api(_) ->
|
||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
||||
{ok, Rate} = request(["monitor_current"]),
|
||||
[?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST],
|
||||
[
|
||||
?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST
|
||||
],
|
||||
{ok, NodeRate} = request(["monitor_current", "nodes", node()]),
|
||||
[?assert(maps:is_key(atom_to_binary(Key, utf8), NodeRate))
|
||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST],
|
||||
[
|
||||
?assert(maps:is_key(atom_to_binary(Key, utf8), NodeRate))
|
||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST
|
||||
],
|
||||
ok.
|
||||
|
||||
t_monitor_reset(_) ->
|
||||
restart_monitor(),
|
||||
{ok, Rate} = request(["monitor_current"]),
|
||||
[?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST],
|
||||
[
|
||||
?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST
|
||||
],
|
||||
{ok, Samplers} = request(["monitor"], "latest=1"),
|
||||
?assertEqual(1, erlang:length(Samplers)),
|
||||
ok.
|
||||
|
@ -121,7 +127,7 @@ request(Path, QS) ->
|
|||
Url = url(Path, QS),
|
||||
do_request_api(get, {Url, [auth_header_()]}).
|
||||
|
||||
url(Parts, QS)->
|
||||
url(Parts, QS) ->
|
||||
case QS of
|
||||
"" ->
|
||||
?SERVER ++ filename:join([?BASE_PATH | Parts]);
|
||||
|
@ -129,16 +135,17 @@ url(Parts, QS)->
|
|||
?SERVER ++ filename:join([?BASE_PATH | Parts]) ++ "?" ++ QS
|
||||
end.
|
||||
|
||||
do_request_api(Method, Request)->
|
||||
do_request_api(Method, Request) ->
|
||||
ct:pal("Req ~p ~p~n", [Method, Request]),
|
||||
case httpc:request(Method, Request, [], []) of
|
||||
{error, socket_closed_remotely} ->
|
||||
{error, socket_closed_remotely};
|
||||
{ok, {{"HTTP/1.1", Code, _}, _, Return} }
|
||||
when Code >= 200 andalso Code =< 299 ->
|
||||
{ok, {{"HTTP/1.1", Code, _}, _, Return}} when
|
||||
Code >= 200 andalso Code =< 299
|
||||
->
|
||||
ct:pal("Resp ~p ~p~n", [Code, Return]),
|
||||
{ok, emqx_json:decode(Return, [return_maps])};
|
||||
{ok, {{"HTTP/1.1", Code, _}, _, Return} } ->
|
||||
{ok, {{"HTTP/1.1", Code, _}, _, Return}} ->
|
||||
ct:pal("Resp ~p ~p~n", [Code, Return]),
|
||||
{error, {Code, emqx_json:decode(Return, [return_maps])}};
|
||||
{error, Reason} ->
|
||||
|
@ -158,7 +165,8 @@ wait_new_monitor(_OldMonitor, Count) when Count =< 0 -> timeout;
|
|||
wait_new_monitor(OldMonitor, Count) ->
|
||||
NewMonitor = erlang:whereis(emqx_dashboard_monitor),
|
||||
case is_pid(NewMonitor) andalso NewMonitor =/= OldMonitor of
|
||||
true -> ok;
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
timer:sleep(100),
|
||||
wait_new_monitor(OldMonitor, Count - 1)
|
||||
|
|
|
@ -20,12 +20,30 @@
|
|||
|
||||
all() -> [{group, spec}, {group, validation}].
|
||||
suite() -> [{timetrap, {minutes, 1}}].
|
||||
groups() -> [
|
||||
{spec, [parallel], [t_api_spec, t_in_path, t_ref, t_in_query, t_in_mix,
|
||||
t_without_in, t_require, t_nullable, t_method, t_public_ref]},
|
||||
{validation, [parallel], [t_in_path_trans, t_ref_trans, t_in_query_trans, t_in_mix_trans,
|
||||
t_in_path_trans_error, t_in_query_trans_error, t_in_mix_trans_error]}
|
||||
].
|
||||
groups() ->
|
||||
[
|
||||
{spec, [parallel], [
|
||||
t_api_spec,
|
||||
t_in_path,
|
||||
t_ref,
|
||||
t_in_query,
|
||||
t_in_mix,
|
||||
t_without_in,
|
||||
t_require,
|
||||
t_nullable,
|
||||
t_method,
|
||||
t_public_ref
|
||||
]},
|
||||
{validation, [parallel], [
|
||||
t_in_path_trans,
|
||||
t_ref_trans,
|
||||
t_in_query_trans,
|
||||
t_in_mix_trans,
|
||||
t_in_path_trans_error,
|
||||
t_in_query_trans_error,
|
||||
t_in_mix_trans_error
|
||||
]}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
mria:start(),
|
||||
|
@ -50,21 +68,36 @@ end_suite() ->
|
|||
|
||||
t_in_path(_Config) ->
|
||||
Expect =
|
||||
[#{description => <<"Indicates which sorts of issues to return">>,
|
||||
example => <<"all">>, in => path, name => filter,
|
||||
required => true,
|
||||
schema => #{enum => [assigned, created, mentioned, all], type => string}}
|
||||
[
|
||||
#{
|
||||
description => <<"Indicates which sorts of issues to return">>,
|
||||
example => <<"all">>,
|
||||
in => path,
|
||||
name => filter,
|
||||
required => true,
|
||||
schema => #{enum => [assigned, created, mentioned, all], type => string}
|
||||
}
|
||||
],
|
||||
validate("/test/in/:filter", Expect),
|
||||
ok.
|
||||
|
||||
t_in_query(_Config) ->
|
||||
Expect =
|
||||
[#{description => <<"results per page (max 100)">>,
|
||||
example => 1, in => query, name => per_page,
|
||||
schema => #{maximum => 100, minimum => 1, type => integer}},
|
||||
#{description => <<"QOS">>, in => query, name => qos,
|
||||
schema => #{enum => [0, 1, 2], type => string}}],
|
||||
[
|
||||
#{
|
||||
description => <<"results per page (max 100)">>,
|
||||
example => 1,
|
||||
in => query,
|
||||
name => per_page,
|
||||
schema => #{maximum => 100, minimum => 1, type => integer}
|
||||
},
|
||||
#{
|
||||
description => <<"QOS">>,
|
||||
in => query,
|
||||
name => qos,
|
||||
schema => #{enum => [0, 1, 2], type => string}
|
||||
}
|
||||
],
|
||||
validate("/test/in/query", Expect),
|
||||
ok.
|
||||
|
||||
|
@ -85,68 +118,131 @@ t_public_ref(_Config) ->
|
|||
Expect = [
|
||||
#{<<"$ref">> => <<"#/components/parameters/public.page">>},
|
||||
#{<<"$ref">> => <<"#/components/parameters/public.limit">>}
|
||||
],
|
||||
],
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Params = maps:get(parameters, maps:get(post, Spec)),
|
||||
?assertEqual(Expect, Params),
|
||||
?assertEqual([
|
||||
{emqx_dashboard_swagger, limit, parameter},
|
||||
{emqx_dashboard_swagger, page, parameter}
|
||||
], Refs),
|
||||
?assertEqual(
|
||||
[
|
||||
{emqx_dashboard_swagger, limit, parameter},
|
||||
{emqx_dashboard_swagger, page, parameter}
|
||||
],
|
||||
Refs
|
||||
),
|
||||
ExpectRefs = [
|
||||
#{<<"public.limit">> => #{description => <<"Results per page(max 1000)">>,
|
||||
in => query,name => limit, example => 50,
|
||||
schema => #{default => 100,maximum => 1000,
|
||||
minimum => 1,type => integer}}},
|
||||
#{<<"public.page">> => #{description => <<"Page number of the results to fetch.">>,
|
||||
in => query,name => page,example => 1,
|
||||
schema => #{default => 1,minimum => 1,type => integer}}}],
|
||||
?assertEqual(ExpectRefs, emqx_dashboard_swagger:components(Refs,#{})),
|
||||
#{
|
||||
<<"public.limit">> => #{
|
||||
description => <<"Results per page(max 1000)">>,
|
||||
in => query,
|
||||
name => limit,
|
||||
example => 50,
|
||||
schema => #{
|
||||
default => 100,
|
||||
maximum => 1000,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"public.page">> => #{
|
||||
description => <<"Page number of the results to fetch.">>,
|
||||
in => query,
|
||||
name => page,
|
||||
example => 1,
|
||||
schema => #{default => 1, minimum => 1, type => integer}
|
||||
}
|
||||
}
|
||||
],
|
||||
?assertEqual(ExpectRefs, emqx_dashboard_swagger:components(Refs, #{})),
|
||||
ok.
|
||||
|
||||
t_in_mix(_Config) ->
|
||||
Expect =
|
||||
[#{description => <<"Indicates which sorts of issues to return">>,
|
||||
example => <<"all">>,in => query,name => filter,
|
||||
schema => #{enum => [assigned,created,mentioned,all],type => string}},
|
||||
#{description => <<"Indicates the state of the issues to return.">>,
|
||||
example => <<"12m">>,in => path,name => state,required => true,
|
||||
schema => #{example => <<"1h">>,type => string}},
|
||||
#{example => 10,in => query,name => per_page, required => false,
|
||||
schema => #{default => 5,maximum => 50,minimum => 1, type => integer}},
|
||||
#{in => query,name => is_admin, schema => #{type => boolean}},
|
||||
#{in => query,name => timeout,
|
||||
schema => #{<<"oneOf">> => [#{enum => [infinity],type => string},
|
||||
#{maximum => 60,minimum => 30, type => integer}]}}],
|
||||
[
|
||||
#{
|
||||
description => <<"Indicates which sorts of issues to return">>,
|
||||
example => <<"all">>,
|
||||
in => query,
|
||||
name => filter,
|
||||
schema => #{enum => [assigned, created, mentioned, all], type => string}
|
||||
},
|
||||
#{
|
||||
description => <<"Indicates the state of the issues to return.">>,
|
||||
example => <<"12m">>,
|
||||
in => path,
|
||||
name => state,
|
||||
required => true,
|
||||
schema => #{example => <<"1h">>, type => string}
|
||||
},
|
||||
#{
|
||||
example => 10,
|
||||
in => query,
|
||||
name => per_page,
|
||||
required => false,
|
||||
schema => #{default => 5, maximum => 50, minimum => 1, type => integer}
|
||||
},
|
||||
#{in => query, name => is_admin, schema => #{type => boolean}},
|
||||
#{
|
||||
in => query,
|
||||
name => timeout,
|
||||
schema => #{
|
||||
<<"oneOf">> => [
|
||||
#{enum => [infinity], type => string},
|
||||
#{maximum => 60, minimum => 30, type => integer}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
ExpectMeta = #{
|
||||
tags => [tags, good],
|
||||
description => <<"good description">>,
|
||||
summary => <<"good summary">>,
|
||||
security => [],
|
||||
deprecated => true,
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}},
|
||||
tags => [tags, good],
|
||||
description => <<"good description">>,
|
||||
summary => <<"good summary">>,
|
||||
security => [],
|
||||
deprecated => true,
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
},
|
||||
GotSpec = validate("/test/in/mix/:state", Expect),
|
||||
?assertEqual(ExpectMeta, maps:without([parameters], maps:get(post, GotSpec))),
|
||||
ok.
|
||||
|
||||
t_without_in(_Config) ->
|
||||
?assertThrow({error, <<"missing in:path/query field in parameters">>},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, "/test/without/in", #{})),
|
||||
?assertThrow(
|
||||
{error, <<"missing in:path/query field in parameters">>},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, "/test/without/in", #{})
|
||||
),
|
||||
ok.
|
||||
|
||||
t_require(_Config) ->
|
||||
ExpectSpec = [#{
|
||||
in => query,name => userid, required => false,
|
||||
schema => #{type => string}}],
|
||||
ExpectSpec = [
|
||||
#{
|
||||
in => query,
|
||||
name => userid,
|
||||
required => false,
|
||||
schema => #{type => string}
|
||||
}
|
||||
],
|
||||
validate("/required/false", ExpectSpec),
|
||||
ok.
|
||||
|
||||
t_nullable(_Config) ->
|
||||
NullableFalse = [#{in => query,name => userid, required => true,
|
||||
schema => #{type => string}}],
|
||||
NullableTrue = [#{in => query,name => userid,
|
||||
schema => #{type => string}, required => false}],
|
||||
NullableFalse = [
|
||||
#{
|
||||
in => query,
|
||||
name => userid,
|
||||
required => true,
|
||||
schema => #{type => string}
|
||||
}
|
||||
],
|
||||
NullableTrue = [
|
||||
#{
|
||||
in => query,
|
||||
name => userid,
|
||||
schema => #{type => string},
|
||||
required => false
|
||||
}
|
||||
],
|
||||
validate("/nullable/false", NullableFalse),
|
||||
validate("/nullable/true", NullableTrue),
|
||||
ok.
|
||||
|
@ -156,35 +252,49 @@ t_method(_Config) ->
|
|||
PathError = "/method/error",
|
||||
{test, Spec, []} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathOk, #{}),
|
||||
?assertEqual(lists:sort(?METHODS), lists:sort(maps:keys(Spec))),
|
||||
?assertThrow({error, #{module := ?MODULE, path := PathError, method := bar}},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathError, #{})),
|
||||
?assertThrow(
|
||||
{error, #{module := ?MODULE, path := PathError, method := bar}},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathError, #{})
|
||||
),
|
||||
ok.
|
||||
|
||||
t_in_path_trans(_Config) ->
|
||||
Path = "/test/in/:filter",
|
||||
Bindings = #{filter => <<"created">>},
|
||||
Expect = {ok,#{bindings => #{filter => created},
|
||||
body => #{}, query_string => #{}}},
|
||||
Expect =
|
||||
{ok, #{
|
||||
bindings => #{filter => created},
|
||||
body => #{},
|
||||
query_string => #{}
|
||||
}},
|
||||
?assertEqual(Expect, trans_parameters(Path, Bindings, #{})),
|
||||
ok.
|
||||
|
||||
t_in_query_trans(_Config) ->
|
||||
Path = "/test/in/query",
|
||||
Expect = {ok, #{bindings => #{},body => #{},
|
||||
query_string => #{<<"per_page">> => 100, <<"qos">> => 1}}},
|
||||
Expect =
|
||||
{ok, #{
|
||||
bindings => #{},
|
||||
body => #{},
|
||||
query_string => #{<<"per_page">> => 100, <<"qos">> => 1}
|
||||
}},
|
||||
?assertEqual(Expect, trans_parameters(Path, #{}, #{<<"per_page">> => 100, <<"qos">> => 1})),
|
||||
ok.
|
||||
|
||||
t_ref_trans(_Config) ->
|
||||
LocalPath = "/test/in/ref/local",
|
||||
Path = "/test/in/ref",
|
||||
Expect = {ok, #{bindings => #{},body => #{},
|
||||
query_string => #{<<"per_page">> => 100}}},
|
||||
Expect =
|
||||
{ok, #{
|
||||
bindings => #{},
|
||||
body => #{},
|
||||
query_string => #{<<"per_page">> => 100}
|
||||
}},
|
||||
?assertEqual(Expect, trans_parameters(Path, #{}, #{<<"per_page">> => 100})),
|
||||
?assertEqual(Expect, trans_parameters(LocalPath, #{}, #{<<"per_page">> => 100})),
|
||||
{400,'BAD_REQUEST', Reason} = trans_parameters(Path, #{}, #{<<"per_page">> => 1010}),
|
||||
{400, 'BAD_REQUEST', Reason} = trans_parameters(Path, #{}, #{<<"per_page">> => 1010}),
|
||||
?assertNotEqual(nomatch, binary:match(Reason, [<<"per_page">>])),
|
||||
{400,'BAD_REQUEST', Reason} = trans_parameters(LocalPath, #{}, #{<<"per_page">> => 1010}),
|
||||
{400, 'BAD_REQUEST', Reason} = trans_parameters(LocalPath, #{}, #{<<"per_page">> => 1010}),
|
||||
ok.
|
||||
|
||||
t_in_mix_trans(_Config) ->
|
||||
|
@ -198,24 +308,29 @@ t_in_mix_trans(_Config) ->
|
|||
<<"is_admin">> => true,
|
||||
<<"timeout">> => <<"34">>
|
||||
},
|
||||
Expect = {ok,
|
||||
#{body => #{},
|
||||
Expect =
|
||||
{ok, #{
|
||||
body => #{},
|
||||
bindings => #{state => 720},
|
||||
query_string => #{<<"filter">> => created,<<"is_admin">> => true,
|
||||
<<"per_page">> => 5,<<"timeout">> => 34}}},
|
||||
query_string => #{
|
||||
<<"filter">> => created,
|
||||
<<"is_admin">> => true,
|
||||
<<"per_page">> => 5,
|
||||
<<"timeout">> => 34
|
||||
}
|
||||
}},
|
||||
?assertEqual(Expect, trans_parameters(Path, Bindings, Query)),
|
||||
ok.
|
||||
|
||||
t_in_path_trans_error(_Config) ->
|
||||
Path = "/test/in/:filter",
|
||||
Bindings = #{filter => <<"created1">>},
|
||||
Expect = {400,'BAD_REQUEST', <<"filter : unable_to_convert_to_enum_symbol">>},
|
||||
?assertEqual(Expect, trans_parameters(Path, Bindings, #{})),
|
||||
?assertMatch({400, 'BAD_REQUEST', _}, trans_parameters(Path, Bindings, #{})),
|
||||
ok.
|
||||
|
||||
t_in_query_trans_error(_Config) ->
|
||||
Path = "/test/in/query",
|
||||
{400,'BAD_REQUEST', Reason} = trans_parameters(Path, #{}, #{<<"per_page">> => 101}),
|
||||
{400, 'BAD_REQUEST', Reason} = trans_parameters(Path, #{}, #{<<"per_page">> => 101}),
|
||||
?assertNotEqual(nomatch, binary:match(Reason, [<<"per_page">>])),
|
||||
ok.
|
||||
|
||||
|
@ -230,8 +345,7 @@ t_in_mix_trans_error(_Config) ->
|
|||
<<"is_admin">> => true,
|
||||
<<"timeout">> => <<"34">>
|
||||
},
|
||||
Expect = {400,'BAD_REQUEST', <<"filter : unable_to_convert_to_enum_symbol">>},
|
||||
?assertEqual(Expect, trans_parameters(Path, Bindings, Query)),
|
||||
?assertMatch({400, 'BAD_REQUEST', _}, trans_parameters(Path, Bindings, Query)),
|
||||
ok.
|
||||
|
||||
t_api_spec(_Config) ->
|
||||
|
@ -253,14 +367,16 @@ t_api_spec(_Config) ->
|
|||
|
||||
?assertMatch(
|
||||
{ok, #{bindings := #{filter := created}}},
|
||||
trans_parameters(Path, Bindings, #{}, Filter)).
|
||||
trans_parameters(Path, Bindings, #{}, Filter)
|
||||
).
|
||||
|
||||
assert_all_filters_equal(Spec, Filter) ->
|
||||
lists:foreach(
|
||||
fun({_, _, _, #{filter := F}}) ->
|
||||
?assertEqual(Filter, F)
|
||||
end,
|
||||
Spec).
|
||||
Spec
|
||||
).
|
||||
|
||||
validate(Path, ExpectParams) ->
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
|
@ -284,8 +400,17 @@ trans_parameters(Path, Bindings, QueryStr, Filter) ->
|
|||
|
||||
api_spec() -> emqx_dashboard_swagger:spec(?MODULE).
|
||||
|
||||
paths() -> ["/test/in/:filter", "/test/in/query", "/test/in/mix/:state", "/test/in/ref",
|
||||
"/required/false", "/nullable/false", "/nullable/true", "/method/ok"].
|
||||
paths() ->
|
||||
[
|
||||
"/test/in/:filter",
|
||||
"/test/in/query",
|
||||
"/test/in/mix/:state",
|
||||
"/test/in/ref",
|
||||
"/required/false",
|
||||
"/nullable/false",
|
||||
"/nullable/true",
|
||||
"/method/ok"
|
||||
].
|
||||
|
||||
schema("/test/in/:filter") ->
|
||||
#{
|
||||
|
@ -293,11 +418,14 @@ schema("/test/in/:filter") ->
|
|||
post => #{
|
||||
parameters => [
|
||||
{filter,
|
||||
mk(hoconsc:enum([assigned, created, mentioned, all]),
|
||||
#{in => path,
|
||||
desc => <<"Indicates which sorts of issues to return">>,
|
||||
example => "all"
|
||||
})}
|
||||
mk(
|
||||
hoconsc:enum([assigned, created, mentioned, all]),
|
||||
#{
|
||||
in => path,
|
||||
desc => <<"Indicates which sorts of issues to return">>,
|
||||
example => "all"
|
||||
}
|
||||
)}
|
||||
],
|
||||
responses => #{200 => <<"ok">>}
|
||||
}
|
||||
|
@ -308,10 +436,14 @@ schema("/test/in/query") ->
|
|||
post => #{
|
||||
parameters => [
|
||||
{per_page,
|
||||
mk(range(1, 100),
|
||||
#{in => query,
|
||||
desc => <<"results per page (max 100)">>,
|
||||
example => 1})},
|
||||
mk(
|
||||
range(1, 100),
|
||||
#{
|
||||
in => query,
|
||||
desc => <<"results per page (max 100)">>,
|
||||
example => 1
|
||||
}
|
||||
)},
|
||||
{qos, mk(emqx_schema:qos(), #{in => query, desc => <<"QOS">>})}
|
||||
],
|
||||
responses => #{200 => <<"ok">>}
|
||||
|
@ -354,14 +486,30 @@ schema("/test/in/mix/:state") ->
|
|||
security => [],
|
||||
deprecated => true,
|
||||
parameters => [
|
||||
{filter, hoconsc:mk(hoconsc:enum([assigned, created, mentioned, all]),
|
||||
#{in => query, desc => <<"Indicates which sorts of issues to return">>,
|
||||
example => "all"})},
|
||||
{state, mk(emqx_schema:duration_s(),
|
||||
#{in => path, required => true, example => "12m",
|
||||
desc => <<"Indicates the state of the issues to return.">>})},
|
||||
{per_page, mk(range(1, 50),
|
||||
#{in => query, required => false, example => 10, default => 5})},
|
||||
{filter,
|
||||
hoconsc:mk(
|
||||
hoconsc:enum([assigned, created, mentioned, all]),
|
||||
#{
|
||||
in => query,
|
||||
desc => <<"Indicates which sorts of issues to return">>,
|
||||
example => "all"
|
||||
}
|
||||
)},
|
||||
{state,
|
||||
mk(
|
||||
emqx_schema:duration_s(),
|
||||
#{
|
||||
in => path,
|
||||
required => true,
|
||||
example => "12m",
|
||||
desc => <<"Indicates the state of the issues to return.">>
|
||||
}
|
||||
)},
|
||||
{per_page,
|
||||
mk(
|
||||
range(1, 50),
|
||||
#{in => query, required => false, example => 10, default => 5}
|
||||
)},
|
||||
{is_admin, mk(boolean(), #{in => query})},
|
||||
{timeout, mk(hoconsc:union([range(30, 60), infinity]), #{in => query})}
|
||||
],
|
||||
|
@ -386,16 +534,21 @@ schema("/nullable/true") ->
|
|||
to_schema([{'userid', mk(binary(), #{in => query, required => false})}]);
|
||||
schema("/method/ok") ->
|
||||
Response = #{responses => #{200 => <<"ok">>}},
|
||||
lists:foldl(fun(Method, Acc) -> Acc#{Method => Response} end,
|
||||
#{operationId => test}, ?METHODS);
|
||||
lists:foldl(
|
||||
fun(Method, Acc) -> Acc#{Method => Response} end,
|
||||
#{operationId => test},
|
||||
?METHODS
|
||||
);
|
||||
schema("/method/error") ->
|
||||
#{operationId => test, bar => #{200 => <<"ok">>}}.
|
||||
|
||||
fields(page) ->
|
||||
[
|
||||
{per_page,
|
||||
mk(range(1, 100),
|
||||
#{in => query, desc => <<"results per page (max 100)">>, example => 1})}
|
||||
mk(
|
||||
range(1, 100),
|
||||
#{in => query, desc => <<"results per page (max 100)">>, example => 1}
|
||||
)}
|
||||
].
|
||||
to_schema(Params) ->
|
||||
#{
|
||||
|
|
|
@ -17,36 +17,39 @@
|
|||
|
||||
-include_lib("typerefl/include/types.hrl").
|
||||
|
||||
-export([ roots/0, fields/1]).
|
||||
-export([roots/0, fields/1]).
|
||||
-import(hoconsc, [mk/2]).
|
||||
roots() -> ["root"].
|
||||
|
||||
fields("root") ->
|
||||
[
|
||||
{listeners, hoconsc:array(hoconsc:union([hoconsc:ref(?MODULE, "ref1"),
|
||||
hoconsc:ref(?MODULE, "ref2")]))},
|
||||
{listeners,
|
||||
hoconsc:array(
|
||||
hoconsc:union([
|
||||
hoconsc:ref(?MODULE, "ref1"),
|
||||
hoconsc:ref(?MODULE, "ref2")
|
||||
])
|
||||
)},
|
||||
{default_username, fun default_username/1},
|
||||
{default_password, fun default_password/1},
|
||||
{sample_interval, mk(emqx_schema:duration_s(), #{default => "10s"})},
|
||||
{token_expired_time, mk(emqx_schema:duration(), #{default => "30m"})}
|
||||
];
|
||||
|
||||
fields("ref1") ->
|
||||
[
|
||||
{"protocol", hoconsc:enum([http, https])},
|
||||
{"port", mk(integer(), #{default => 18083})}
|
||||
];
|
||||
|
||||
fields("ref2") ->
|
||||
[
|
||||
{page, mk(range(1,100), #{desc => <<"good page">>})},
|
||||
{page, mk(range(1, 100), #{desc => <<"good page">>})},
|
||||
{another_ref, hoconsc:ref(?MODULE, "ref3")}
|
||||
];
|
||||
fields("ref3") ->
|
||||
[
|
||||
{ip, mk(emqx_schema:ip_port(), #{desc => <<"IP:Port">>, example => "127.0.0.1:80"})},
|
||||
{version, mk(string(), #{desc => "a good version", example => "1.0.0"})}
|
||||
].
|
||||
].
|
||||
|
||||
default_username(type) -> string();
|
||||
default_username(default) -> "admin";
|
||||
|
|
|
@ -36,125 +36,262 @@ end_suite() ->
|
|||
|
||||
t_object(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> =>
|
||||
#{<<"application/json">> =>
|
||||
#{<<"schema">> =>
|
||||
#{required => [<<"timeout">>, <<"per_page">>],
|
||||
<<"properties">> =>[
|
||||
{<<"per_page">>, #{description => <<"good per page desc">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
||||
[#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}]}},
|
||||
{<<"inner_ref">>,
|
||||
#{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
|
||||
<<"type">> => object}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> =>
|
||||
#{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> =>
|
||||
#{
|
||||
required => [<<"timeout">>, <<"per_page">>],
|
||||
<<"properties">> => [
|
||||
{<<"per_page">>, #{
|
||||
description => <<"good per page desc">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"timeout">>, #{
|
||||
default => 5,
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}
|
||||
]
|
||||
}},
|
||||
{<<"inner_ref">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, good_ref}],
|
||||
validate("/object", Spec, Refs),
|
||||
ok.
|
||||
|
||||
t_nest_object(_Config) ->
|
||||
GoodRef = <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>,
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> =>
|
||||
#{required => [<<"timeout">>],
|
||||
<<"properties">> =>
|
||||
[{<<"per_page">>, #{description => <<"good per page desc">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
||||
[#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}]}},
|
||||
{<<"nest_object">>,
|
||||
#{<<"properties">> =>
|
||||
[{<<"good_nest_1">>, #{type => integer}},
|
||||
{<<"good_nest_2">>, #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
|
||||
<<"type">> => object}},
|
||||
{<<"inner_ref">>,
|
||||
#{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
|
||||
<<"type">> => object}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> =>
|
||||
#{
|
||||
required => [<<"timeout">>],
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"per_page">>, #{
|
||||
description => <<"good per page desc">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"timeout">>, #{
|
||||
default => 5,
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}
|
||||
]
|
||||
}},
|
||||
{<<"nest_object">>, #{
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"good_nest_1">>, #{type => integer}},
|
||||
{<<"good_nest_2">>, #{
|
||||
<<"$ref">> => GoodRef
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}},
|
||||
{<<"inner_ref">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, good_ref}],
|
||||
validate("/nest/object", Spec, Refs),
|
||||
ok.
|
||||
|
||||
t_local_ref(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, good_ref}],
|
||||
validate("/ref/local", Spec, Refs),
|
||||
ok.
|
||||
|
||||
t_remote_ref(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref2">>}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref2">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{emqx_swagger_remote_schema, "ref2"}],
|
||||
{_, Components} = validate("/ref/remote", Spec, Refs),
|
||||
ExpectComponents = [
|
||||
#{<<"emqx_swagger_remote_schema.ref2">> => #{<<"properties">> => [
|
||||
{<<"page">>, #{description => <<"good page">>,
|
||||
maximum => 100,minimum => 1,type => integer}},
|
||||
{<<"another_ref">>, #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}}], <<"type">> => object}},
|
||||
#{<<"emqx_swagger_remote_schema.ref3">> => #{<<"properties">> => [
|
||||
{<<"ip">>, #{description => <<"IP:Port">>,
|
||||
example => <<"127.0.0.1:80">>,type => string}},
|
||||
{<<"version">>, #{description => <<"a good version">>,
|
||||
example => <<"1.0.0">>,type => string}}],
|
||||
<<"type">> => object}}],
|
||||
#{
|
||||
<<"emqx_swagger_remote_schema.ref2">> => #{
|
||||
<<"properties">> => [
|
||||
{<<"page">>, #{
|
||||
description => <<"good page">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"another_ref">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref3">>
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"emqx_swagger_remote_schema.ref3">> => #{
|
||||
<<"properties">> => [
|
||||
{<<"ip">>, #{
|
||||
description => <<"IP:Port">>,
|
||||
example => <<"127.0.0.1:80">>,
|
||||
type => string
|
||||
}},
|
||||
{<<"version">>, #{
|
||||
description => <<"a good version">>,
|
||||
example => <<"1.0.0">>,
|
||||
type => string
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
],
|
||||
?assertEqual(ExpectComponents, Components),
|
||||
ok.
|
||||
|
||||
t_nest_ref(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.nest_ref">>}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.nest_ref">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, nest_ref}],
|
||||
ExpectComponents = lists:sort([
|
||||
#{<<"emqx_swagger_requestBody_SUITE.nest_ref">> => #{<<"properties">> => [
|
||||
{<<"env">>, #{enum => [test,dev,prod],type => string}},
|
||||
{<<"another_ref">>, #{description => <<"nest ref">>,
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
|
||||
<<"type">> => object}},
|
||||
#{<<"emqx_swagger_requestBody_SUITE.good_ref">> => #{<<"properties">> => [
|
||||
{<<"webhook-host">>, #{default => <<"127.0.0.1:80">>,
|
||||
example => <<"127.0.0.1:80">>,type => string}},
|
||||
{<<"log_dir">>, #{example => <<"var/log/emqx">>,type => string}},
|
||||
{<<"tag">>, #{description => <<"tag">>,type => string}}],
|
||||
<<"type">> => object}}]),
|
||||
#{
|
||||
<<"emqx_swagger_requestBody_SUITE.nest_ref">> => #{
|
||||
<<"properties">> => [
|
||||
{<<"env">>, #{enum => [test, dev, prod], type => string}},
|
||||
{<<"another_ref">>, #{
|
||||
description => <<"nest ref">>,
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"emqx_swagger_requestBody_SUITE.good_ref">> => #{
|
||||
<<"properties">> => [
|
||||
{<<"webhook-host">>, #{
|
||||
default => <<"127.0.0.1:80">>,
|
||||
example => <<"127.0.0.1:80">>,
|
||||
type => string
|
||||
}},
|
||||
{<<"log_dir">>, #{example => <<"var/log/emqx">>, type => string}},
|
||||
{<<"tag">>, #{description => <<"tag">>, type => string}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
]),
|
||||
{_, Components} = validate("/ref/nest/ref", Spec, Refs),
|
||||
?assertEqual(ExpectComponents, Components),
|
||||
ok.
|
||||
|
||||
t_none_ref(_Config) ->
|
||||
Path = "/ref/none",
|
||||
?assertThrow({error, #{mfa := {?MODULE, schema, [Path]}}},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{})),
|
||||
?assertThrow(
|
||||
{error, #{mfa := {?MODULE, schema, [Path]}}},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{})
|
||||
),
|
||||
ok.
|
||||
|
||||
t_sub_fields(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.sub_fields">>}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.sub_fields">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, sub_fields}],
|
||||
validate("/fields/sub", Spec, Refs),
|
||||
ok.
|
||||
|
@ -162,44 +299,97 @@ t_sub_fields(_Config) ->
|
|||
t_bad_ref(_Config) ->
|
||||
Path = "/ref/bad",
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
||||
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.bad_ref">>}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> =>
|
||||
#{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.bad_ref">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, bad_ref}],
|
||||
Fields = fields(bad_ref),
|
||||
?assertThrow({error, #{msg := <<"Object only supports not empty proplists">>, args := Fields}},
|
||||
validate(Path, Spec, Refs)),
|
||||
?assertThrow(
|
||||
{error, #{msg := <<"Object only supports not empty proplists">>, args := Fields}},
|
||||
validate(Path, Spec, Refs)
|
||||
),
|
||||
ok.
|
||||
|
||||
t_ref_array_with_key(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{required => [<<"timeout">>],
|
||||
<<"type">> => object, <<"properties">> =>
|
||||
[
|
||||
{<<"per_page">>, #{description => <<"good per page desc">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
||||
[#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}]}},
|
||||
{<<"array_refs">>, #{items => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>},
|
||||
type => array}}
|
||||
]}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
required => [<<"timeout">>],
|
||||
<<"type">> => object,
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"per_page">>, #{
|
||||
description => <<"good per page desc">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"timeout">>, #{
|
||||
default => 5,
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}
|
||||
]
|
||||
}},
|
||||
{<<"array_refs">>, #{
|
||||
items => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
|
||||
},
|
||||
type => array
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, good_ref}],
|
||||
validate("/ref/array/with/key", Spec, Refs),
|
||||
ok.
|
||||
|
||||
t_ref_array_without_key(_Config) ->
|
||||
Spec = #{
|
||||
post => #{parameters => [],
|
||||
requestBody => #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
||||
#{items => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}, type => array}}}},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}}},
|
||||
post => #{
|
||||
parameters => [],
|
||||
requestBody => #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> =>
|
||||
#{
|
||||
items => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
|
||||
},
|
||||
type => array
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{<<"200">> => #{description => <<"ok">>}}
|
||||
}
|
||||
},
|
||||
Refs = [{?MODULE, good_ref}],
|
||||
validate("/ref/array/without/key", Spec, Refs),
|
||||
ok.
|
||||
|
@ -220,15 +410,18 @@ t_api_spec(_Config) ->
|
|||
Filter0 = filter(Spec0, Path),
|
||||
?assertMatch(
|
||||
{ok, #{body := #{<<"timeout">> := <<"infinity">>}}},
|
||||
trans_requestBody(Path, Body, Filter0)),
|
||||
trans_requestBody(Path, Body, Filter0)
|
||||
),
|
||||
|
||||
{Spec1, _} = emqx_dashboard_swagger:spec(?MODULE,
|
||||
#{check_schema => true, translate_body => true}),
|
||||
{Spec1, _} = emqx_dashboard_swagger:spec(
|
||||
?MODULE,
|
||||
#{check_schema => true, translate_body => true}
|
||||
),
|
||||
Filter1 = filter(Spec1, Path),
|
||||
?assertMatch(
|
||||
{ok, #{body := #{<<"timeout">> := infinity}}},
|
||||
trans_requestBody(Path, Body, Filter1)).
|
||||
|
||||
trans_requestBody(Path, Body, Filter1)
|
||||
).
|
||||
|
||||
t_object_trans(_Config) ->
|
||||
Path = "/object",
|
||||
|
@ -246,14 +439,15 @@ t_object_trans(_Config) ->
|
|||
bindings => #{},
|
||||
query_string => #{},
|
||||
body =>
|
||||
#{
|
||||
<<"per_page">> => 1,
|
||||
<<"timeout">> => infinity,
|
||||
<<"inner_ref">> => #{
|
||||
<<"log_dir">> => "var/log/test",
|
||||
<<"tag">> => <<"god_tag">>,
|
||||
<<"webhook-host">> => {{127, 0, 0, 1}, 80}}
|
||||
}
|
||||
#{
|
||||
<<"per_page">> => 1,
|
||||
<<"timeout">> => infinity,
|
||||
<<"inner_ref">> => #{
|
||||
<<"log_dir">> => "var/log/test",
|
||||
<<"tag">> => <<"god_tag">>,
|
||||
<<"webhook-host">> => {{127, 0, 0, 1}, 80}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ok, ActualBody} = trans_requestBody(Path, Body),
|
||||
?assertEqual(Expect, ActualBody),
|
||||
|
@ -270,8 +464,11 @@ t_object_notrans(_Config) ->
|
|||
<<"tag">> => <<"god_tag">>
|
||||
}
|
||||
},
|
||||
{ok, #{body := ActualBody}} = trans_requestBody(Path, Body,
|
||||
fun emqx_dashboard_swagger:filter_check_request/2),
|
||||
{ok, #{body := ActualBody}} = trans_requestBody(
|
||||
Path,
|
||||
Body,
|
||||
fun emqx_dashboard_swagger:filter_check_request/2
|
||||
),
|
||||
?assertEqual(Body, ActualBody),
|
||||
ok.
|
||||
|
||||
|
@ -297,8 +494,10 @@ todo_t_nest_object_check(_Config) ->
|
|||
Expect = #{
|
||||
bindings => #{},
|
||||
query_string => #{},
|
||||
body => #{<<"per_page">> => 10,
|
||||
<<"timeout">> => 600}
|
||||
body => #{
|
||||
<<"per_page">> => 10,
|
||||
<<"timeout">> => 600
|
||||
}
|
||||
},
|
||||
{ok, NewRequest} = check_requestBody(Path, Body),
|
||||
?assertEqual(Expect, NewRequest),
|
||||
|
@ -330,7 +529,8 @@ t_remote_ref_trans(_Config) ->
|
|||
<<"page">> => 10,
|
||||
<<"another_ref">> => #{
|
||||
<<"version">> => "2.1.0",
|
||||
<<"ip">> => <<"198.12.2.1:89">>}
|
||||
<<"ip">> => <<"198.12.2.1:89">>
|
||||
}
|
||||
},
|
||||
Expect = #{
|
||||
bindings => #{},
|
||||
|
@ -339,7 +539,8 @@ t_remote_ref_trans(_Config) ->
|
|||
<<"page">> => 10,
|
||||
<<"another_ref">> => #{
|
||||
<<"version">> => "2.1.0",
|
||||
<<"ip">> => {{198,12,2,1}, 89}}
|
||||
<<"ip">> => {{198, 12, 2, 1}, 89}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ok, NewRequest} = trans_requestBody(Path, Body),
|
||||
|
@ -348,20 +549,25 @@ t_remote_ref_trans(_Config) ->
|
|||
|
||||
t_nest_ref_trans(_Config) ->
|
||||
Path = "/ref/nest/ref",
|
||||
Body = #{<<"env">> => <<"prod">>,
|
||||
Body = #{
|
||||
<<"env">> => <<"prod">>,
|
||||
<<"another_ref">> => #{
|
||||
<<"log_dir">> => "var/log/dev",
|
||||
<<"tag">> => <<"A">>,
|
||||
<<"webhook-host">> => "127.0.0.1:80"
|
||||
}},
|
||||
}
|
||||
},
|
||||
Expect = #{
|
||||
bindings => #{},
|
||||
query_string => #{},
|
||||
body => #{
|
||||
<<"another_ref">> => #{
|
||||
<<"log_dir">> => "var/log/dev", <<"tag">> => <<"A">>,
|
||||
<<"webhook-host">> => {{127, 0, 0, 1}, 80}},
|
||||
<<"env">> => prod}
|
||||
<<"log_dir">> => "var/log/dev",
|
||||
<<"tag">> => <<"A">>,
|
||||
<<"webhook-host">> => {{127, 0, 0, 1}, 80}
|
||||
},
|
||||
<<"env">> => prod
|
||||
}
|
||||
},
|
||||
{ok, NewRequest} = trans_requestBody(Path, Body),
|
||||
?assertEqual(Expect, NewRequest),
|
||||
|
@ -382,7 +588,8 @@ t_ref_array_with_key_trans(_Config) ->
|
|||
<<"log_dir">> => "var/log/test",
|
||||
<<"tag">> => <<"B">>,
|
||||
<<"webhook-host">> => "127.0.0.1:81"
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
Expect = #{
|
||||
bindings => #{},
|
||||
|
@ -410,16 +617,18 @@ t_ref_array_with_key_trans(_Config) ->
|
|||
|
||||
t_ref_array_without_key_trans(_Config) ->
|
||||
Path = "/ref/array/without/key",
|
||||
Body = [#{
|
||||
<<"log_dir">> => "var/log/dev",
|
||||
<<"tag">> => <<"A">>,
|
||||
<<"webhook-host">> => "127.0.0.1:80"
|
||||
},
|
||||
Body = [
|
||||
#{
|
||||
<<"log_dir">> => "var/log/dev",
|
||||
<<"tag">> => <<"A">>,
|
||||
<<"webhook-host">> => "127.0.0.1:80"
|
||||
},
|
||||
#{
|
||||
<<"log_dir">> => "var/log/test",
|
||||
<<"tag">> => <<"B">>,
|
||||
<<"webhook-host">> => "127.0.0.1:81"
|
||||
}],
|
||||
}
|
||||
],
|
||||
Expect = #{
|
||||
bindings => #{},
|
||||
query_string => #{},
|
||||
|
@ -433,7 +642,8 @@ t_ref_array_without_key_trans(_Config) ->
|
|||
<<"log_dir">> => "var/log/test",
|
||||
<<"tag">> => <<"B">>,
|
||||
<<"webhook-host">> => {{127, 0, 0, 1}, 81}
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ok, NewRequest} = trans_requestBody(Path, Body),
|
||||
?assertEqual(Expect, NewRequest),
|
||||
|
@ -441,12 +651,14 @@ t_ref_array_without_key_trans(_Config) ->
|
|||
|
||||
t_ref_trans_error(_Config) ->
|
||||
Path = "/ref/nest/ref",
|
||||
Body = #{<<"env">> => <<"prod">>,
|
||||
Body = #{
|
||||
<<"env">> => <<"prod">>,
|
||||
<<"another_ref">> => #{
|
||||
<<"log_dir">> => "var/log/dev",
|
||||
<<"tag">> => <<"A">>,
|
||||
<<"webhook-host">> => "127.0..0.1:80"
|
||||
}},
|
||||
}
|
||||
},
|
||||
{400, 'BAD_REQUEST', _} = trans_requestBody(Path, Body),
|
||||
ok.
|
||||
|
||||
|
@ -472,18 +684,23 @@ validate(Path, ExpectSpec, ExpectRefs) ->
|
|||
?assertEqual(ExpectRefs, Refs),
|
||||
{Spec, emqx_dashboard_swagger:components(Refs, #{})}.
|
||||
|
||||
|
||||
filter(ApiSpec, Path) ->
|
||||
[Filter] = [F || {P, _, _, #{filter := F}} <- ApiSpec, P =:= Path],
|
||||
Filter.
|
||||
|
||||
trans_requestBody(Path, Body) ->
|
||||
trans_requestBody(Path, Body,
|
||||
fun emqx_dashboard_swagger:filter_check_request_and_translate_body/2).
|
||||
trans_requestBody(
|
||||
Path,
|
||||
Body,
|
||||
fun emqx_dashboard_swagger:filter_check_request_and_translate_body/2
|
||||
).
|
||||
|
||||
check_requestBody(Path, Body) ->
|
||||
trans_requestBody(Path, Body,
|
||||
fun emqx_dashboard_swagger:filter_check_request/2).
|
||||
trans_requestBody(
|
||||
Path,
|
||||
Body,
|
||||
fun emqx_dashboard_swagger:filter_check_request/2
|
||||
).
|
||||
|
||||
trans_requestBody(Path, Body, Filter) ->
|
||||
Meta = #{module => ?MODULE, method => post, path => Path},
|
||||
|
@ -492,21 +709,34 @@ trans_requestBody(Path, Body, Filter) ->
|
|||
|
||||
api_spec() -> emqx_dashboard_swagger:spec(?MODULE).
|
||||
paths() ->
|
||||
["/object", "/nest/object", "/ref/local", "/ref/nest/ref", "/fields/sub",
|
||||
"/ref/array/with/key", "/ref/array/without/key"].
|
||||
[
|
||||
"/object",
|
||||
"/nest/object",
|
||||
"/ref/local",
|
||||
"/ref/nest/ref",
|
||||
"/fields/sub",
|
||||
"/ref/array/with/key",
|
||||
"/ref/array/without/key"
|
||||
].
|
||||
|
||||
schema("/object") ->
|
||||
to_schema([
|
||||
{per_page, mk(range(1, 100), #{required => true, desc => <<"good per page desc">>})},
|
||||
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true})},
|
||||
{timeout,
|
||||
mk(
|
||||
hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true}
|
||||
)},
|
||||
{inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
||||
]);
|
||||
schema("/nest/object") ->
|
||||
to_schema([
|
||||
{per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
|
||||
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true})},
|
||||
{timeout,
|
||||
mk(
|
||||
hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true}
|
||||
)},
|
||||
{nest_object, [
|
||||
{good_nest_1, mk(integer(), #{})},
|
||||
{good_nest_2, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
||||
|
@ -526,8 +756,11 @@ schema("/ref/nest/ref") ->
|
|||
schema("/ref/array/with/key") ->
|
||||
to_schema([
|
||||
{per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
|
||||
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true})},
|
||||
{timeout,
|
||||
mk(
|
||||
hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true}
|
||||
)},
|
||||
{array_refs, mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{})}
|
||||
]);
|
||||
schema("/ref/array/without/key") ->
|
||||
|
@ -550,18 +783,20 @@ fields(nest_ref) ->
|
|||
{env, mk(hoconsc:enum([test, dev, prod]), #{})},
|
||||
{another_ref, mk(hoconsc:ref(good_ref), #{desc => "nest ref"})}
|
||||
];
|
||||
|
||||
fields(bad_ref) -> %% don't support maps
|
||||
%% don't support maps
|
||||
fields(bad_ref) ->
|
||||
#{
|
||||
username => mk(string(), #{}),
|
||||
is_admin => mk(boolean(), #{})
|
||||
};
|
||||
fields(sub_fields) ->
|
||||
#{fields => [
|
||||
{enable, fun enable/1},
|
||||
{init_file, fun init_file/1}
|
||||
],
|
||||
desc => <<"test sub fields">>}.
|
||||
#{
|
||||
fields => [
|
||||
{enable, fun enable/1},
|
||||
{init_file, fun init_file/1}
|
||||
],
|
||||
desc => <<"test sub fields">>
|
||||
}.
|
||||
|
||||
enable(type) -> boolean();
|
||||
enable(desc) -> <<"Whether to enable tls psk support">>;
|
||||
|
|
|
@ -46,37 +46,78 @@ t_simple_binary(_config) ->
|
|||
t_object(_config) ->
|
||||
Path = "/object",
|
||||
Object =
|
||||
#{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{required => [<<"timeout">>, <<"per_page">>],
|
||||
<<"properties">> => [
|
||||
{<<"per_page">>, #{description => <<"good per page desc">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
||||
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
|
||||
{<<"inner_ref">>, #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}],
|
||||
<<"type">> => object}}}},
|
||||
#{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
required => [<<"timeout">>, <<"per_page">>],
|
||||
<<"properties">> => [
|
||||
{<<"per_page">>, #{
|
||||
description => <<"good per page desc">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"timeout">>, #{
|
||||
default => 5,
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}
|
||||
]
|
||||
}},
|
||||
{<<"inner_ref">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, good_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_error(_Config) ->
|
||||
Path = "/error",
|
||||
Error400 = #{<<"content">> =>
|
||||
#{<<"application/json">> => #{<<"schema">> => #{<<"type">> => object,
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"code">>, #{enum => ['Bad1', 'Bad2'], type => string}},
|
||||
{<<"message">>, #{description => <<"Bad request desc">>, type => string}}]
|
||||
}}}},
|
||||
Error404 = #{<<"content">> =>
|
||||
#{<<"application/json">> => #{<<"schema">> => #{<<"type">> => object,
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"code">>, #{enum => ['Not-Found'], type => string}},
|
||||
{<<"message">>, #{
|
||||
description => <<"Error code to troubleshoot problems.">>, type => string}}]
|
||||
}}}},
|
||||
Error400 = #{
|
||||
<<"content">> =>
|
||||
#{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"type">> => object,
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"code">>, #{enum => ['Bad1', 'Bad2'], type => string}},
|
||||
{<<"message">>, #{
|
||||
description => <<"Bad request desc">>, type => string
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Error404 = #{
|
||||
<<"content">> =>
|
||||
#{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"type">> => object,
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"code">>, #{enum => ['Not-Found'], type => string}},
|
||||
{<<"message">>, #{
|
||||
description => <<"Error code to troubleshoot problems.">>,
|
||||
type => string
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Response = maps:get(responses, maps:get(get, Spec)),
|
||||
|
@ -89,121 +130,243 @@ t_error(_Config) ->
|
|||
t_nest_object(_Config) ->
|
||||
Path = "/nest/object",
|
||||
Object =
|
||||
#{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
||||
#{required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [
|
||||
{<<"per_page">>, #{description => <<"good per page desc">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
||||
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
|
||||
{<<"nest_object">>, #{<<"type">> => object, <<"properties">> => [
|
||||
{<<"good_nest_1">>, #{type => integer}},
|
||||
{<<"good_nest_2">>, #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}
|
||||
}]}},
|
||||
{<<"inner_ref">>, #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}]
|
||||
}}}},
|
||||
#{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> =>
|
||||
#{
|
||||
required => [<<"timeout">>],
|
||||
<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"per_page">>, #{
|
||||
description => <<"good per page desc">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"timeout">>, #{
|
||||
default => 5,
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}
|
||||
]
|
||||
}},
|
||||
{<<"nest_object">>, #{
|
||||
<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"good_nest_1">>, #{type => integer}},
|
||||
{<<"good_nest_2">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
}}
|
||||
]
|
||||
}},
|
||||
{<<"inner_ref">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, good_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_empty(_Config) ->
|
||||
?assertThrow({error,
|
||||
#{msg := <<"Object only supports not empty proplists">>,
|
||||
args := [], module := ?MODULE}}, validate("/empty", error, [])),
|
||||
?assertThrow(
|
||||
{error, #{
|
||||
msg := <<"Object only supports not empty proplists">>,
|
||||
args := [],
|
||||
module := ?MODULE
|
||||
}},
|
||||
validate("/empty", error, [])
|
||||
),
|
||||
ok.
|
||||
|
||||
t_raw_local_ref(_Config) ->
|
||||
Path = "/raw/ref/local",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, good_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_raw_remote_ref(_Config) ->
|
||||
Path = "/raw/ref/remote",
|
||||
Object = #{<<"content">> =>
|
||||
#{<<"application/json">> => #{<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}}}},
|
||||
Object = #{
|
||||
<<"content">> =>
|
||||
#{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{emqx_swagger_remote_schema, "ref1"}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_local_ref(_Config) ->
|
||||
Path = "/ref/local",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, good_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_remote_ref(_Config) ->
|
||||
Path = "/ref/remote",
|
||||
Object = #{<<"content">> =>
|
||||
#{<<"application/json">> => #{<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}}}},
|
||||
Object = #{
|
||||
<<"content">> =>
|
||||
#{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{emqx_swagger_remote_schema, "ref1"}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_bad_ref(_Config) ->
|
||||
Path = "/ref/bad",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
||||
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.bad_ref">>}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> =>
|
||||
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.bad_ref">>}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, bad_ref}],
|
||||
?assertThrow({error, #{module := ?MODULE,
|
||||
msg := <<"Object only supports not empty proplists">>}},
|
||||
validate(Path, Object, ExpectRefs)),
|
||||
?assertThrow(
|
||||
{error, #{
|
||||
module := ?MODULE,
|
||||
msg := <<"Object only supports not empty proplists">>
|
||||
}},
|
||||
validate(Path, Object, ExpectRefs)
|
||||
),
|
||||
ok.
|
||||
|
||||
t_none_ref(_Config) ->
|
||||
Path = "/ref/none",
|
||||
?assertThrow({error, #{mfa := {?MODULE, schema, ["/ref/none"]},
|
||||
reason := function_clause}}, validate(Path, #{}, [])),
|
||||
?assertThrow(
|
||||
{error, #{
|
||||
mfa := {?MODULE, schema, ["/ref/none"]},
|
||||
reason := function_clause
|
||||
}},
|
||||
validate(Path, #{}, [])
|
||||
),
|
||||
ok.
|
||||
|
||||
t_nest_ref(_Config) ->
|
||||
Path = "/ref/nest/ref",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.nest_ref">>}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.nest_ref">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, nest_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_sub_fields(_Config) ->
|
||||
Path = "/fields/sub",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.sub_fields">>}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.sub_fields">>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, sub_fields}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_complicated_type(_Config) ->
|
||||
Path = "/ref/complicated_type",
|
||||
Object = #{<<"content">> => #{<<"application/json">> =>
|
||||
#{<<"schema">> => #{<<"properties">> =>
|
||||
[
|
||||
{<<"no_neg_integer">>, #{minimum => 0, type => integer}},
|
||||
{<<"url">>, #{example => <<"http://127.0.0.1">>, type => string}},
|
||||
{<<"server">>, #{example => <<"127.0.0.1:80">>, type => string}},
|
||||
{<<"connect_timeout">>, #{example => infinity, <<"oneOf">> => [
|
||||
#{example => infinity, type => string},
|
||||
#{type => integer}]}},
|
||||
{<<"pool_type">>, #{enum => [random, hash], type => string}},
|
||||
{<<"timeout">>, #{example => infinity,
|
||||
<<"oneOf">> => [#{example => infinity, type => string}, #{type => integer}]}},
|
||||
{<<"bytesize">>, #{example => <<"32MB">>, type => string}},
|
||||
{<<"wordsize">>, #{example => <<"1024KB">>, type => string}},
|
||||
{<<"maps">>, #{example => #{}, type => object}},
|
||||
{<<"comma_separated_list">>, #{example => <<"item1,item2">>, type => string}},
|
||||
{<<"comma_separated_atoms">>, #{example => <<"item1,item2">>, type => string}},
|
||||
{<<"log_level">>,
|
||||
#{enum => [debug, info, notice, warning, error, critical, alert, emergency, all],
|
||||
type => string}},
|
||||
{<<"fix_integer">>, #{default => 100, enum => [100],type => integer}}
|
||||
],
|
||||
<<"type">> => object}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> =>
|
||||
#{
|
||||
<<"schema">> => #{
|
||||
<<"properties">> =>
|
||||
[
|
||||
{<<"no_neg_integer">>, #{minimum => 0, type => integer}},
|
||||
{<<"url">>, #{example => <<"http://127.0.0.1">>, type => string}},
|
||||
{<<"server">>, #{example => <<"127.0.0.1:80">>, type => string}},
|
||||
{<<"connect_timeout">>, #{
|
||||
example => infinity,
|
||||
<<"oneOf">> => [
|
||||
#{example => infinity, type => string},
|
||||
#{type => integer}
|
||||
]
|
||||
}},
|
||||
{<<"pool_type">>, #{enum => [random, hash], type => string}},
|
||||
{<<"timeout">>, #{
|
||||
example => infinity,
|
||||
<<"oneOf">> => [
|
||||
#{example => infinity, type => string}, #{type => integer}
|
||||
]
|
||||
}},
|
||||
{<<"bytesize">>, #{example => <<"32MB">>, type => string}},
|
||||
{<<"wordsize">>, #{example => <<"1024KB">>, type => string}},
|
||||
{<<"maps">>, #{example => #{}, type => object}},
|
||||
{<<"comma_separated_list">>, #{
|
||||
example => <<"item1,item2">>, type => string
|
||||
}},
|
||||
{<<"comma_separated_atoms">>, #{
|
||||
example => <<"item1,item2">>, type => string
|
||||
}},
|
||||
{<<"log_level">>, #{
|
||||
enum => [
|
||||
debug,
|
||||
info,
|
||||
notice,
|
||||
warning,
|
||||
error,
|
||||
critical,
|
||||
alert,
|
||||
emergency,
|
||||
all
|
||||
],
|
||||
type => string
|
||||
}},
|
||||
{<<"fix_integer">>, #{
|
||||
default => 100, enum => [100], type => integer
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Response = maps:get(responses, maps:get(post, Spec)),
|
||||
|
@ -211,79 +374,165 @@ t_complicated_type(_Config) ->
|
|||
?assertEqual([], Refs),
|
||||
ok.
|
||||
|
||||
|
||||
t_ref_array_with_key(_Config) ->
|
||||
Path = "/ref/array/with/key",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
||||
required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [
|
||||
{<<"per_page">>, #{description => <<"good per page desc">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
||||
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
|
||||
{<<"assert">>, #{description => <<"money">>, type => number}},
|
||||
{<<"number_ex">>, #{description => <<"number example">>, type => number}},
|
||||
{<<"percent_ex">>, #{description => <<"percent example">>,
|
||||
example => <<"12%">>, type => number}},
|
||||
{<<"duration_ms_ex">>, #{description => <<"duration ms example">>,
|
||||
example => <<"32s">>, type => string}},
|
||||
{<<"atom_ex">>, #{description => <<"atom ex">>, type => string}},
|
||||
{<<"array_refs">>, #{items => #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}, type => array}}
|
||||
]}
|
||||
}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
required => [<<"timeout">>],
|
||||
<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"per_page">>, #{
|
||||
description => <<"good per page desc">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"timeout">>, #{
|
||||
default => 5,
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{example => <<"1h">>, type => string},
|
||||
#{enum => [infinity], type => string}
|
||||
]
|
||||
}},
|
||||
{<<"assert">>, #{description => <<"money">>, type => number}},
|
||||
{<<"number_ex">>, #{description => <<"number example">>, type => number}},
|
||||
{<<"percent_ex">>, #{
|
||||
description => <<"percent example">>,
|
||||
example => <<"12%">>,
|
||||
type => number
|
||||
}},
|
||||
{<<"duration_ms_ex">>, #{
|
||||
description => <<"duration ms example">>,
|
||||
example => <<"32s">>,
|
||||
type => string
|
||||
}},
|
||||
{<<"atom_ex">>, #{description => <<"atom ex">>, type => string}},
|
||||
{<<"array_refs">>, #{
|
||||
items => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
},
|
||||
type => array
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, good_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
|
||||
t_ref_array_without_key(_Config) ->
|
||||
Path = "/ref/array/without/key",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
||||
items => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>},
|
||||
type => array}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> => #{
|
||||
items => #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>
|
||||
},
|
||||
type => array
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectRefs = [{?MODULE, good_ref}],
|
||||
validate(Path, Object, ExpectRefs),
|
||||
ok.
|
||||
t_hocon_schema_function(_Config) ->
|
||||
Path = "/ref/hocon/schema/function",
|
||||
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
||||
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.root">>}}}},
|
||||
Object = #{
|
||||
<<"content">> => #{
|
||||
<<"application/json">> => #{
|
||||
<<"schema">> =>
|
||||
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.root">>}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpectComponents = [
|
||||
#{<<"emqx_swagger_remote_schema.ref1">> => #{<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"protocol">>, #{enum => [http, https], type => string}},
|
||||
{<<"port">>, #{default => 18083, type => integer}}]
|
||||
}},
|
||||
#{<<"emqx_swagger_remote_schema.ref2">> => #{<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"page">>, #{description => <<"good page">>,
|
||||
maximum => 100, minimum => 1, type => integer}},
|
||||
{<<"another_ref">>, #{<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}}
|
||||
]
|
||||
}},
|
||||
#{<<"emqx_swagger_remote_schema.ref3">> => #{<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"ip">>, #{description => <<"IP:Port">>,
|
||||
example => <<"127.0.0.1:80">>, type => string}},
|
||||
{<<"version">>, #{description => <<"a good version">>,
|
||||
example => <<"1.0.0">>, type => string}}]
|
||||
}},
|
||||
#{<<"emqx_swagger_remote_schema.root">> =>
|
||||
#{required => [<<"default_password">>, <<"default_username">>],
|
||||
<<"properties">> => [{<<"listeners">>, #{items =>
|
||||
#{<<"oneOf">> =>
|
||||
[#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref2">>},
|
||||
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}]},
|
||||
type => array}},
|
||||
{<<"default_username">>,
|
||||
#{default => <<"admin">>, type => string}},
|
||||
{<<"default_password">>,
|
||||
#{default => <<"public">>, type => string}},
|
||||
{<<"sample_interval">>,
|
||||
#{default => <<"10s">>, example => <<"1h">>, type => string}},
|
||||
{<<"token_expired_time">>,
|
||||
#{default => <<"30m">>, example => <<"12m">>, type => string}}],
|
||||
<<"type">> => object}}],
|
||||
#{
|
||||
<<"emqx_swagger_remote_schema.ref1">> => #{
|
||||
<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"protocol">>, #{enum => [http, https], type => string}},
|
||||
{<<"port">>, #{default => 18083, type => integer}}
|
||||
]
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"emqx_swagger_remote_schema.ref2">> => #{
|
||||
<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"page">>, #{
|
||||
description => <<"good page">>,
|
||||
maximum => 100,
|
||||
minimum => 1,
|
||||
type => integer
|
||||
}},
|
||||
{<<"another_ref">>, #{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref3">>
|
||||
}}
|
||||
]
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"emqx_swagger_remote_schema.ref3">> => #{
|
||||
<<"type">> => object,
|
||||
<<"properties">> => [
|
||||
{<<"ip">>, #{
|
||||
description => <<"IP:Port">>,
|
||||
example => <<"127.0.0.1:80">>,
|
||||
type => string
|
||||
}},
|
||||
{<<"version">>, #{
|
||||
description => <<"a good version">>,
|
||||
example => <<"1.0.0">>,
|
||||
type => string
|
||||
}}
|
||||
]
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"emqx_swagger_remote_schema.root">> =>
|
||||
#{
|
||||
required => [<<"default_password">>, <<"default_username">>],
|
||||
<<"properties">> => [
|
||||
{<<"listeners">>, #{
|
||||
items =>
|
||||
#{
|
||||
<<"oneOf">> =>
|
||||
[
|
||||
#{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref2">>
|
||||
},
|
||||
#{
|
||||
<<"$ref">> =>
|
||||
<<"#/components/schemas/emqx_swagger_remote_schema.ref1">>
|
||||
}
|
||||
]
|
||||
},
|
||||
type => array
|
||||
}},
|
||||
{<<"default_username">>, #{default => <<"admin">>, type => string}},
|
||||
{<<"default_password">>, #{default => <<"public">>, type => string}},
|
||||
{<<"sample_interval">>, #{
|
||||
default => <<"10s">>, example => <<"1h">>, type => string
|
||||
}},
|
||||
{<<"token_expired_time">>, #{
|
||||
default => <<"30m">>, example => <<"12m">>, type => string
|
||||
}}
|
||||
],
|
||||
<<"type">> => object
|
||||
}
|
||||
}
|
||||
],
|
||||
ExpectRefs = [{emqx_swagger_remote_schema, "root"}],
|
||||
{_, Components} = validate(Path, Object, ExpectRefs),
|
||||
?assertEqual(ExpectComponents, Components),
|
||||
|
@ -296,31 +545,46 @@ t_api_spec(_Config) ->
|
|||
api_spec() -> emqx_dashboard_swagger:spec(?MODULE).
|
||||
|
||||
paths() ->
|
||||
["/simple/bin", "/object", "/nest/object", "/ref/local",
|
||||
"/ref/nest/ref", "/raw/ref/local", "/raw/ref/remote",
|
||||
"/ref/array/with/key", "/ref/array/without/key",
|
||||
"/ref/hocon/schema/function"].
|
||||
[
|
||||
"/simple/bin",
|
||||
"/object",
|
||||
"/nest/object",
|
||||
"/ref/local",
|
||||
"/ref/nest/ref",
|
||||
"/raw/ref/local",
|
||||
"/raw/ref/remote",
|
||||
"/ref/array/with/key",
|
||||
"/ref/array/without/key",
|
||||
"/ref/hocon/schema/function"
|
||||
].
|
||||
|
||||
schema("/simple/bin") ->
|
||||
to_schema(<<"binary ok">>);
|
||||
schema("/object") ->
|
||||
Object = [
|
||||
{per_page, mk(range(1, 100), #{required => true, desc => <<"good per page desc">>})},
|
||||
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true})},
|
||||
{timeout,
|
||||
mk(
|
||||
hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true}
|
||||
)},
|
||||
{inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
||||
],
|
||||
to_schema(Object);
|
||||
schema("/nest/object") ->
|
||||
Response = [
|
||||
{per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
|
||||
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true})},
|
||||
{timeout,
|
||||
mk(
|
||||
hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true}
|
||||
)},
|
||||
{nest_object, [
|
||||
{good_nest_1, mk(integer(), #{})},
|
||||
{good_nest_2, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
||||
]},
|
||||
{inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}],
|
||||
{inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
||||
],
|
||||
to_schema(Response);
|
||||
schema("/empty") ->
|
||||
to_schema([]);
|
||||
|
@ -339,8 +603,11 @@ schema("/ref/nest/ref") ->
|
|||
schema("/ref/array/with/key") ->
|
||||
to_schema([
|
||||
{per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
|
||||
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true})},
|
||||
{timeout,
|
||||
mk(
|
||||
hoconsc:union([infinity, emqx_schema:duration_s()]),
|
||||
#{default => 5, required => true}
|
||||
)},
|
||||
{assert, mk(float(), #{desc => <<"money">>})},
|
||||
{number_ex, mk(number(), #{desc => <<"number example">>})},
|
||||
{percent_ex, mk(emqx_schema:percent(), #{desc => <<"percent example">>})},
|
||||
|
@ -355,31 +622,35 @@ schema("/ref/hocon/schema/function") ->
|
|||
schema("/error") ->
|
||||
#{
|
||||
operationId => test,
|
||||
get => #{responses => #{
|
||||
400 => emqx_dashboard_swagger:error_codes(['Bad1', 'Bad2'], <<"Bad request desc">>),
|
||||
404 => emqx_dashboard_swagger:error_codes(['Not-Found'])
|
||||
}}
|
||||
get => #{
|
||||
responses => #{
|
||||
400 => emqx_dashboard_swagger:error_codes(['Bad1', 'Bad2'], <<"Bad request desc">>),
|
||||
404 => emqx_dashboard_swagger:error_codes(['Not-Found'])
|
||||
}
|
||||
}
|
||||
};
|
||||
schema("/ref/complicated_type") ->
|
||||
#{
|
||||
operationId => test,
|
||||
post => #{responses => #{
|
||||
200 => [
|
||||
{no_neg_integer, hoconsc:mk(non_neg_integer(), #{})},
|
||||
{url, hoconsc:mk(emqx_connector_http:url(), #{})},
|
||||
{server, hoconsc:mk(emqx_schema:ip_port(), #{})},
|
||||
{connect_timeout, hoconsc:mk(emqx_connector_http:connect_timeout(), #{})},
|
||||
{pool_type, hoconsc:mk(emqx_connector_http:pool_type(), #{})},
|
||||
{timeout, hoconsc:mk(timeout(), #{})},
|
||||
{bytesize, hoconsc:mk(emqx_schema:bytesize(), #{})},
|
||||
{wordsize, hoconsc:mk(emqx_schema:wordsize(), #{})},
|
||||
{maps, hoconsc:mk(map(), #{})},
|
||||
{comma_separated_list, hoconsc:mk(emqx_schema:comma_separated_list(), #{})},
|
||||
{comma_separated_atoms, hoconsc:mk(emqx_schema:comma_separated_atoms(), #{})},
|
||||
{log_level, hoconsc:mk(emqx_conf_schema:log_level(), #{})},
|
||||
{fix_integer, hoconsc:mk(typerefl:integer(100), #{})}
|
||||
]
|
||||
}}
|
||||
post => #{
|
||||
responses => #{
|
||||
200 => [
|
||||
{no_neg_integer, hoconsc:mk(non_neg_integer(), #{})},
|
||||
{url, hoconsc:mk(emqx_connector_http:url(), #{})},
|
||||
{server, hoconsc:mk(emqx_schema:ip_port(), #{})},
|
||||
{connect_timeout, hoconsc:mk(emqx_connector_http:connect_timeout(), #{})},
|
||||
{pool_type, hoconsc:mk(emqx_connector_http:pool_type(), #{})},
|
||||
{timeout, hoconsc:mk(timeout(), #{})},
|
||||
{bytesize, hoconsc:mk(emqx_schema:bytesize(), #{})},
|
||||
{wordsize, hoconsc:mk(emqx_schema:wordsize(), #{})},
|
||||
{maps, hoconsc:mk(map(), #{})},
|
||||
{comma_separated_list, hoconsc:mk(emqx_schema:comma_separated_list(), #{})},
|
||||
{comma_separated_atoms, hoconsc:mk(emqx_schema:comma_separated_atoms(), #{})},
|
||||
{log_level, hoconsc:mk(emqx_conf_schema:log_level(), #{})},
|
||||
{fix_integer, hoconsc:mk(typerefl:integer(100), #{})}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
schema("/fields/sub") ->
|
||||
to_schema(hoconsc:ref(sub_fields)).
|
||||
|
@ -411,18 +682,20 @@ fields(nest_ref) ->
|
|||
{env, mk(hoconsc:enum([test, dev, prod]), #{})},
|
||||
{another_ref, mk(hoconsc:ref(good_ref), #{desc => "nest ref"})}
|
||||
];
|
||||
|
||||
fields(bad_ref) -> %% don't support maps
|
||||
%% don't support maps
|
||||
fields(bad_ref) ->
|
||||
#{
|
||||
username => mk(string(), #{}),
|
||||
is_admin => mk(boolean(), #{})
|
||||
};
|
||||
fields(sub_fields) ->
|
||||
#{fields => [
|
||||
{enable, fun enable/1},
|
||||
{init_file, fun init_file/1}
|
||||
],
|
||||
desc => <<"test sub fields">>}.
|
||||
#{
|
||||
fields => [
|
||||
{enable, fun enable/1},
|
||||
{init_file, fun init_file/1}
|
||||
],
|
||||
desc => <<"test sub fields">>
|
||||
}.
|
||||
|
||||
enable(type) -> boolean();
|
||||
enable(desc) -> <<"Whether to enable tls psk support">>;
|
||||
|
|
|
@ -284,7 +284,8 @@ get_full_config() ->
|
|||
maps:without(
|
||||
?EXCLUDES,
|
||||
emqx:get_raw_config([])
|
||||
)
|
||||
),
|
||||
#{obfuscate_sensitive_values => true}
|
||||
).
|
||||
|
||||
get_config_with_default(Path) ->
|
||||
|
|
|
@ -4,7 +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.27.2"}}}
|
||||
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.27.3"}}}
|
||||
]}.
|
||||
|
||||
{edoc_opts, [{preprocess, true}]}.
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -68,7 +68,7 @@ defmodule EMQXUmbrella.MixProject do
|
|||
# in conflict by emqtt and hocon
|
||||
{:getopt, "1.0.2", override: true},
|
||||
{:snabbkaffe, github: "kafka4beam/snabbkaffe", tag: "0.18.0", override: true},
|
||||
{:hocon, github: "emqx/hocon", tag: "0.27.2", override: true},
|
||||
{:hocon, github: "emqx/hocon", tag: "0.27.3", override: true},
|
||||
{:emqx_http_lib, github: "emqx/emqx_http_lib", tag: "0.5.1", override: true},
|
||||
{:esasl, github: "emqx/esasl", tag: "0.2.0"},
|
||||
{:jose, github: "potatosalad/erlang-jose", tag: "1.11.2"},
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
, {system_monitor, {git, "https://github.com/ieQu1/system_monitor", {tag, "3.0.3"}}}
|
||||
, {getopt, "1.0.2"}
|
||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.18.0"}}}
|
||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.27.2"}}}
|
||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.27.3"}}}
|
||||
, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.5.1"}}}
|
||||
, {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.2.0"}}}
|
||||
, {jose, {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.2"}}}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
-mode(compile).
|
||||
|
||||
main(_) ->
|
||||
{ok, BaseConf} = file:read_file("apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf"),
|
||||
{ok, BaseConf} = file:read_file("apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf"),
|
||||
|
||||
Cfgs = get_all_cfgs("apps/"),
|
||||
Conf = [merge(BaseConf, Cfgs),
|
||||
|
|
Loading…
Reference in New Issue