diff --git a/apps/emqx/src/emqx_map_lib.erl b/apps/emqx/src/emqx_map_lib.erl index 0486c10da..529c22816 100644 --- a/apps/emqx/src/emqx_map_lib.erl +++ b/apps/emqx/src/emqx_map_lib.erl @@ -24,13 +24,16 @@ , safe_atom_key_map/1 , unsafe_atom_key_map/1 , jsonable_map/1 - , jsonable_value/1 - , deep_convert/2 + , jsonable_map/2 + , binary_string/1 + , deep_convert/3 ]). -export_type([config_key/0, config_key_path/0]). -type config_key() :: atom() | binary(). -type config_key_path() :: [config_key()]. +-type convert_fun() :: fun((K::any(), V::any(), Args::list()) -> + {K1::any(), V1::any()} | drop). %%----------------------------------------------------------------- -spec deep_get(config_key_path(), map()) -> term(). @@ -100,15 +103,17 @@ deep_merge(BaseMap, NewMap) -> end, #{}, BaseMap), maps:merge(MergedBase, maps:with(NewKeys, NewMap)). --spec deep_convert(map(), fun((K::any(), V::any()) -> {K1::any(), V1::any()})) -> map(). -deep_convert(Map, ConvFun) when is_map(Map) -> +-spec deep_convert(map(), convert_fun(), Args::list()) -> map(). +deep_convert(Map, ConvFun, Args) when is_map(Map) -> maps:fold(fun(K, V, Acc) -> - {K1, V1} = ConvFun(K, deep_convert(V, ConvFun)), - Acc#{K1 => V1} + case apply(ConvFun, [K, deep_convert(V, ConvFun, Args) | Args]) of + drop -> Acc; + {K1, V1} -> Acc#{K1 => V1} + end end, #{}, Map); -deep_convert(ListV, ConvFun) when is_list(ListV) -> - [deep_convert(V, ConvFun) || V <- ListV]; -deep_convert(Val, _) -> Val. +deep_convert(ListV, ConvFun, Args) when is_list(ListV) -> + [deep_convert(V, ConvFun, Args) || V <- ListV]; +deep_convert(Val, _, _Args) -> Val. -spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}. unsafe_atom_key_map(Map) -> @@ -120,17 +125,24 @@ safe_atom_key_map(Map) -> -spec jsonable_map(map() | list()) -> map() | list(). jsonable_map(Map) -> - deep_convert(Map, fun(K, V) -> - {jsonable_value(K), jsonable_value(V)} - end). + jsonable_map(Map, fun(K, V) -> {K, V} end). -jsonable_value([]) -> []; -jsonable_value(Val) when is_list(Val) -> +jsonable_map(Map, JsonableFun) -> + deep_convert(Map, fun binary_string_kv/3, [JsonableFun]). + +binary_string_kv(K, V, JsonableFun) -> + case JsonableFun(K, V) of + drop -> drop; + {K1, V1} -> {binary_string(K1), binary_string(V1)} + end. + +binary_string([]) -> []; +binary_string(Val) when is_list(Val) -> case io_lib:printable_unicode_list(Val) of true -> unicode:characters_to_binary(Val); - false -> Val + false -> [binary_string(V) || V <- Val] end; -jsonable_value(Val) -> +binary_string(Val) -> Val. %%--------------------------------------------------------------------------- @@ -138,4 +150,4 @@ covert_keys_to_atom(BinKeyMap, Conv) -> deep_convert(BinKeyMap, fun (K, V) when is_atom(K) -> {K, V}; (K, V) when is_binary(K) -> {Conv(K), V} - end). + end, []). diff --git a/apps/emqx_management/src/emqx_mgmt_api_configs.erl b/apps/emqx_management/src/emqx_mgmt_api_configs.erl index ba864fa89..a859f2002 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_configs.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_configs.erl @@ -188,7 +188,7 @@ gen_schema(_Conf) -> #{type => string}. with_default_value(Type, Value) -> - Type#{example => emqx_map_lib:jsonable_value(Value)}. + Type#{example => emqx_map_lib:binary_string(Value)}. path_join(Path) -> path_join(Path, "/"). diff --git a/apps/emqx_management/src/emqx_mgmt_api_listeners.erl b/apps/emqx_management/src/emqx_mgmt_api_listeners.erl index e13549a86..4bad6a26b 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_listeners.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_listeners.erl @@ -24,14 +24,9 @@ , list_listeners_by_id/2 , list_listeners_on_node/2 , get_listener_by_id_on_node/2 - , manage_listeners/2]). - --import(emqx_mgmt_util, [ schema/1 - , object_schema/2 - , object_array_schema/2 - , error_schema/2 - , properties/1 - ]). + , manage_listeners/2 + , jsonable_resp/2 + ]). -export([format/1]). @@ -53,17 +48,23 @@ api_spec() -> [] }. -properties() -> - properties([ - {node, string, <<"Node">>}, - {id, string, <<"Identifier">>}, - {acceptors, integer, <<"Number of Acceptor process">>}, - {max_conn, integer, <<"Maximum number of allowed connection">>}, - {type, string, <<"Listener type">>}, - {listen_on, string, <<"Listener port">>}, - {running, boolean, <<"Open or close">>}, - {auth, boolean, <<"Has auth">>} - ]). +-define(TYPES, [tcp, ssl, ws, wss, quic]). +req_schema() -> + Schema = [emqx_mgmt_api_configs:gen_schema( + emqx:get_raw_config([listeners, T, default], #{})) + || T <- ?TYPES], + #{oneOf => Schema}. + +resp_schema() -> + #{oneOf := Schema} = req_schema(), + AddMetadata = fun(Prop) -> + Prop#{running => #{type => boolean}, + id => #{type => string}, + node => #{type => string}} + end, + Schema1 = [S#{properties => AddMetadata(Prop)} + || S = #{properties := Prop} <- Schema], + #{oneOf => Schema1}. api_list_listeners() -> Metadata = #{ @@ -71,7 +72,7 @@ api_list_listeners() -> description => <<"List listeners from all nodes in the cluster">>, responses => #{ <<"200">> => - object_array_schema(properties(), <<"List listeners successfully">>)}}}, + emqx_mgmt_util:array_schema(resp_schema(), <<"List listeners successfully">>)}}}, {"/listeners", Metadata, list_listeners}. api_list_listeners_by_id() -> @@ -81,9 +82,9 @@ api_list_listeners_by_id() -> parameters => [param_path_id()], responses => #{ <<"404">> => - error_schema(?LISTENER_NOT_FOUND, ['BAD_LISTENER_ID']), + emqx_mgmt_util:error_schema(?LISTENER_NOT_FOUND, ['BAD_LISTENER_ID']), <<"200">> => - object_array_schema(properties(), <<"List listeners successfully">>)}}}, + emqx_mgmt_util:array_schema(resp_schema(), <<"List listeners successfully">>)}}}, {"/listeners/:id", Metadata, list_listeners_by_id}. api_list_listeners_on_node() -> @@ -92,7 +93,7 @@ api_list_listeners_on_node() -> description => <<"List listeners in one node">>, parameters => [param_path_node()], responses => #{ - <<"200">> => object_schema(properties(), <<"List listeners successfully">>)}}}, + <<"200">> => emqx_mgmt_util:object_schema(resp_schema(), <<"List listeners successfully">>)}}}, {"/nodes/:node/listeners", Metadata, list_listeners_on_node}. api_get_listener_by_id_on_node() -> @@ -102,10 +103,10 @@ api_get_listener_by_id_on_node() -> parameters => [param_path_node(), param_path_id()], responses => #{ <<"404">> => - error_schema(?NODE_LISTENER_NOT_FOUND, + emqx_mgmt_util:error_schema(?NODE_LISTENER_NOT_FOUND, ['BAD_NODE_NAME', 'BAD_LISTENER_ID']), <<"200">> => - object_schema(properties(), <<"Get listener successfully">>)}}}, + emqx_mgmt_util:object_schema(resp_schema(), <<"Get listener successfully">>)}}}, {"/nodes/:node/listeners/:id", Metadata, get_listener_by_id_on_node}. api_manage_listeners() -> @@ -116,9 +117,9 @@ api_manage_listeners() -> param_path_id(), param_path_operation()], responses => #{ - <<"500">> => error_schema(<<"Operation Failed">>, ['INTERNAL_ERROR']), - <<"200">> => schema(<<"Operation success">>)}}}, - {"/listeners/:id/:operation", Metadata, manage_listeners}. + <<"500">> => emqx_mgmt_util:error_schema(<<"Operation Failed">>, ['INTERNAL_ERROR']), + <<"200">> => emqx_mgmt_util:schema(<<"Operation success">>)}}}, + {"/listeners/:id/operation/:operation", Metadata, manage_listeners}. api_manage_listeners_on_node() -> Metadata = #{ @@ -129,9 +130,9 @@ api_manage_listeners_on_node() -> param_path_id(), param_path_operation()], responses => #{ - <<"500">> => error_schema(<<"Operation Failed">>, ['INTERNAL_ERROR']), - <<"200">> => schema(<<"Operation success">>)}}}, - {"/nodes/:node/listeners/:id/:operation", Metadata, manage_listeners}. + <<"500">> => emqx_mgmt_util:error_schema(<<"Operation Failed">>, ['INTERNAL_ERROR']), + <<"200">> => emqx_mgmt_util:schema(<<"Operation success">>)}}}, + {"/nodes/:node/listeners/:id/operation/:operation", Metadata, manage_listeners}. %%%============================================================================================== %% parameters @@ -247,16 +248,12 @@ format({error, Reason}) -> {error, Reason}; format({ID, Conf}) -> - {Type, _Name} = emqx_listeners:parse_listener_id(ID), - #{ + emqx_map_lib:jsonable_map(Conf#{ id => ID, node => maps:get(node, Conf), - acceptors => maps:get(acceptors, Conf), - max_conn => maps:get(max_connections, Conf), - type => Type, - listen_on => list_to_binary(esockd:to_string(maps:get(bind, Conf))), running => trans_running(Conf) - }. + }, fun ?MODULE:jsonable_resp/2). + trans_running(Conf) -> case maps:get(running, Conf) of {error, _} -> @@ -265,6 +262,15 @@ trans_running(Conf) -> Running end. +jsonable_resp(bind, Port) when is_integer(Port) -> + {bind, Port}; +jsonable_resp(bind, {Addr, Port}) when is_tuple(Addr); is_integer(Port)-> + {bind, inet:ntoa(Addr) ++ ":" ++ integer_to_list(Port)}; +jsonable_resp(user_lookup_fun, _) -> + drop; +jsonable_resp(K, V) -> + {K, V}. + atom(B) when is_binary(B) -> binary_to_atom(B, utf8); atom(S) when is_list(S) -> list_to_atom(S); atom(A) when is_atom(A) -> A.