From 5223c3ee61d43240da8ae66c7ff0ebea4723e79e Mon Sep 17 00:00:00 2001 From: Zhongwen Deng Date: Sun, 24 Apr 2022 09:23:16 +0800 Subject: [PATCH] feat: obfuscate sensitive values default_password --- apps/emqx/rebar.config | 2 +- apps/emqx/src/emqx_config.erl | 17 +- apps/emqx/src/emqx_listeners.erl | 2 +- apps/emqx_bridge/src/emqx_bridge_api.erl | 2 +- ...d_schema.conf => emqx_dashboard_i18n.conf} | 6 +- apps/emqx_dashboard/src/emqx_dashboard.erl | 16 +- .../src/emqx_dashboard_schema.erl | 49 +- .../src/emqx_dashboard_swagger.erl | 31 +- .../test/emqx_dashboard_SUITE.erl | 127 ++-- .../test/emqx_dashboard_api_test_helpers.erl | 54 +- .../test/emqx_dashboard_bad_api_SUITE.erl | 7 +- .../test/emqx_dashboard_error_code_SUITE.erl | 16 +- .../test/emqx_dashboard_monitor_SUITE.erl | 38 +- .../test/emqx_swagger_parameter_SUITE.erl | 351 +++++++--- .../test/emqx_swagger_remote_schema.erl | 17 +- .../test/emqx_swagger_requestBody_SUITE.erl | 557 ++++++++++----- .../test/emqx_swagger_response_SUITE.erl | 641 +++++++++++++----- .../src/emqx_mgmt_api_configs.erl | 3 +- apps/emqx_prometheus/rebar.config | 2 +- mix.exs | 2 +- rebar.config | 2 +- scripts/merge-i18n.escript | 2 +- 22 files changed, 1343 insertions(+), 601 deletions(-) rename apps/emqx_dashboard/i18n/{emqx_dashboard_schema.conf => emqx_dashboard_i18n.conf} (96%) diff --git a/apps/emqx/rebar.config b/apps/emqx/rebar.config index 9e2234b81..53aaeb0f1 100644 --- a/apps/emqx/rebar.config +++ b/apps/emqx/rebar.config @@ -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"}}} diff --git a/apps/emqx/src/emqx_config.erl b/apps/emqx/src/emqx_config.erl index 0ed0a2dcc..a59d6a670 100644 --- a/apps/emqx/src/emqx_config.erl +++ b/apps/emqx/src/emqx_config.erl @@ -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) ). diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 228b1ee96..464140dd4 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -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). diff --git a/apps/emqx_bridge/src/emqx_bridge_api.erl b/apps/emqx_bridge/src/emqx_bridge_api.erl index ff277120a..ee6d69d24 100644 --- a/apps/emqx_bridge/src/emqx_bridge_api.erl +++ b/apps/emqx_bridge/src/emqx_bridge_api.erl @@ -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) -> diff --git a/apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf b/apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf similarity index 96% rename from apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf rename to apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf index 345b678cb..82c7075af 100644 --- a/apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf +++ b/apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf @@ -19,7 +19,7 @@ but use the same port.""" desc { en: """How often to update metrics displayed in the dashboard.
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" diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index 0c4fae5fe..f5c926a16 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard.erl @@ -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], []). diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index 4fb7b2abc..404648b14 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -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). diff --git a/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl b/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl index fba0a8998..7fb2613ee 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl @@ -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."). diff --git a/apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl b/apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl index 89f6f2199..c2e26160e 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl @@ -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 = <>}] = + [#?ADMIN{username = <<"username">>, pwdhash = <>}] = emqx_dashboard_admin:lookup_user(<<"username">>), ?assertEqual(Hash, crypto:hash(sha256, <>/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. diff --git a/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl b/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl index 3ad44e3b8..3501494cb 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl @@ -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} diff --git a/apps/emqx_dashboard/test/emqx_dashboard_bad_api_SUITE.erl b/apps/emqx_dashboard/test/emqx_dashboard_bad_api_SUITE.erl index 878212c61..6182327f4 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_bad_api_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_bad_api_SUITE.erl @@ -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} diff --git a/apps/emqx_dashboard/test/emqx_dashboard_error_code_SUITE.erl b/apps/emqx_dashboard/test/emqx_dashboard_error_code_SUITE.erl index ff9926336..a3ae228f7 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_error_code_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_error_code_SUITE.erl @@ -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} diff --git a/apps/emqx_dashboard/test/emqx_dashboard_monitor_SUITE.erl b/apps/emqx_dashboard/test/emqx_dashboard_monitor_SUITE.erl index b7430e586..89da2bc23 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_monitor_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_monitor_SUITE.erl @@ -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) diff --git a/apps/emqx_dashboard/test/emqx_swagger_parameter_SUITE.erl b/apps/emqx_dashboard/test/emqx_swagger_parameter_SUITE.erl index 4abfa136a..d1dfe7988 100644 --- a/apps/emqx_dashboard/test/emqx_swagger_parameter_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_swagger_parameter_SUITE.erl @@ -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) -> #{ diff --git a/apps/emqx_dashboard/test/emqx_swagger_remote_schema.erl b/apps/emqx_dashboard/test/emqx_swagger_remote_schema.erl index d2a47e59e..2b08c3a04 100644 --- a/apps/emqx_dashboard/test/emqx_swagger_remote_schema.erl +++ b/apps/emqx_dashboard/test/emqx_swagger_remote_schema.erl @@ -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"; diff --git a/apps/emqx_dashboard/test/emqx_swagger_requestBody_SUITE.erl b/apps/emqx_dashboard/test/emqx_swagger_requestBody_SUITE.erl index bfbb5e95e..5a807eac6 100644 --- a/apps/emqx_dashboard/test/emqx_swagger_requestBody_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_swagger_requestBody_SUITE.erl @@ -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">>; diff --git a/apps/emqx_dashboard/test/emqx_swagger_response_SUITE.erl b/apps/emqx_dashboard/test/emqx_swagger_response_SUITE.erl index 72f99ea52..7c51df0fa 100644 --- a/apps/emqx_dashboard/test/emqx_swagger_response_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_swagger_response_SUITE.erl @@ -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">>; diff --git a/apps/emqx_management/src/emqx_mgmt_api_configs.erl b/apps/emqx_management/src/emqx_mgmt_api_configs.erl index 283140aa1..ea594b5a8 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_configs.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_configs.erl @@ -284,7 +284,8 @@ get_full_config() -> maps:without( ?EXCLUDES, emqx:get_raw_config([]) - ) + ), + #{obfuscate_sensitive_values => true} ). get_config_with_default(Path) -> diff --git a/apps/emqx_prometheus/rebar.config b/apps/emqx_prometheus/rebar.config index ba1b7eb14..a51d56b99 100644 --- a/apps/emqx_prometheus/rebar.config +++ b/apps/emqx_prometheus/rebar.config @@ -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}]}. diff --git a/mix.exs b/mix.exs index 9ab642515..c29cdcc69 100644 --- a/mix.exs +++ b/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"}, diff --git a/rebar.config b/rebar.config index b1c3dc314..d6e520064 100644 --- a/rebar.config +++ b/rebar.config @@ -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"}}} diff --git a/scripts/merge-i18n.escript b/scripts/merge-i18n.escript index 8a42cffe4..13a7df27b 100755 --- a/scripts/merge-i18n.escript +++ b/scripts/merge-i18n.escript @@ -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),