Merge pull request #11030 from thalesmg/fix-listener-api-union-member-r51
fix(listener_mgmt_api): use union member selector fn for better error messages (r5.1)
This commit is contained in:
commit
3a7d4ea29d
|
@ -1104,14 +1104,9 @@ do_t_validations(_Config) ->
|
||||||
emqx_utils_json:decode(ResRaw1, [return_maps]),
|
emqx_utils_json:decode(ResRaw1, [return_maps]),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
#{
|
#{
|
||||||
<<"mismatches">> :=
|
<<"kind">> := <<"validation_error">>,
|
||||||
#{
|
<<"reason">> :=
|
||||||
<<"listeners:ssl_not_required_bind">> :=
|
<<"verify must be verify_peer when CRL check is enabled">>
|
||||||
#{
|
|
||||||
<<"reason">> :=
|
|
||||||
<<"verify must be verify_peer when CRL check is enabled">>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emqx_utils_json:decode(MsgRaw1, [return_maps])
|
emqx_utils_json:decode(MsgRaw1, [return_maps])
|
||||||
),
|
),
|
||||||
|
|
|
@ -912,14 +912,9 @@ do_t_validations(_Config) ->
|
||||||
emqx_utils_json:decode(ResRaw1, [return_maps]),
|
emqx_utils_json:decode(ResRaw1, [return_maps]),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
#{
|
#{
|
||||||
<<"mismatches">> :=
|
<<"kind">> := <<"validation_error">>,
|
||||||
#{
|
<<"reason">> :=
|
||||||
<<"listeners:ssl_not_required_bind">> :=
|
<<"The responder URL is required for OCSP stapling">>
|
||||||
#{
|
|
||||||
<<"reason">> :=
|
|
||||||
<<"The responder URL is required for OCSP stapling">>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emqx_utils_json:decode(MsgRaw1, [return_maps])
|
emqx_utils_json:decode(MsgRaw1, [return_maps])
|
||||||
),
|
),
|
||||||
|
@ -942,14 +937,9 @@ do_t_validations(_Config) ->
|
||||||
emqx_utils_json:decode(ResRaw2, [return_maps]),
|
emqx_utils_json:decode(ResRaw2, [return_maps]),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
#{
|
#{
|
||||||
<<"mismatches">> :=
|
<<"kind">> := <<"validation_error">>,
|
||||||
#{
|
<<"reason">> :=
|
||||||
<<"listeners:ssl_not_required_bind">> :=
|
<<"The issuer PEM path is required for OCSP stapling">>
|
||||||
#{
|
|
||||||
<<"reason">> :=
|
|
||||||
<<"The issuer PEM path is required for OCSP stapling">>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emqx_utils_json:decode(MsgRaw2, [return_maps])
|
emqx_utils_json:decode(MsgRaw2, [return_maps])
|
||||||
),
|
),
|
||||||
|
|
|
@ -132,7 +132,7 @@ t_query_mode(CtConfig) ->
|
||||||
begin
|
begin
|
||||||
publish_with_config_template_parameters(CtConfig1, #{"query_mode" => "sync"})
|
publish_with_config_template_parameters(CtConfig1, #{"query_mode" => "sync"})
|
||||||
end,
|
end,
|
||||||
fun(RunStageResult, Trace) ->
|
fun(Trace) ->
|
||||||
%% We should have a sync Snabbkaffe trace
|
%% We should have a sync Snabbkaffe trace
|
||||||
?assertMatch([_], ?of_kind(emqx_bridge_kafka_impl_producer_sync_query, Trace))
|
?assertMatch([_], ?of_kind(emqx_bridge_kafka_impl_producer_sync_query, Trace))
|
||||||
end
|
end
|
||||||
|
@ -141,7 +141,7 @@ t_query_mode(CtConfig) ->
|
||||||
begin
|
begin
|
||||||
publish_with_config_template_parameters(CtConfig1, #{"query_mode" => "async"})
|
publish_with_config_template_parameters(CtConfig1, #{"query_mode" => "async"})
|
||||||
end,
|
end,
|
||||||
fun(RunStageResult, Trace) ->
|
fun(Trace) ->
|
||||||
%% We should have a sync Snabbkaffe trace
|
%% We should have a sync Snabbkaffe trace
|
||||||
?assertMatch([_], ?of_kind(emqx_bridge_kafka_impl_producer_async_query, Trace))
|
?assertMatch([_], ?of_kind(emqx_bridge_kafka_impl_producer_async_query, Trace))
|
||||||
end
|
end
|
||||||
|
|
|
@ -277,10 +277,39 @@ fields(Type) ->
|
||||||
|
|
||||||
listener_schema(Opts) ->
|
listener_schema(Opts) ->
|
||||||
emqx_dashboard_swagger:schema_with_example(
|
emqx_dashboard_swagger:schema_with_example(
|
||||||
?UNION(lists:map(fun(#{ref := Ref}) -> Ref end, listeners_info(Opts))),
|
hoconsc:union(listener_union_member_selector(Opts)),
|
||||||
tcp_schema_example()
|
tcp_schema_example()
|
||||||
).
|
).
|
||||||
|
|
||||||
|
listener_union_member_selector(Opts) ->
|
||||||
|
ListenersInfo = listeners_info(Opts),
|
||||||
|
Index = maps:from_list([
|
||||||
|
{iolist_to_binary(ListenerType), Ref}
|
||||||
|
|| #{listener_type := ListenerType, ref := Ref} <- ListenersInfo
|
||||||
|
]),
|
||||||
|
fun
|
||||||
|
(all_union_members) ->
|
||||||
|
maps:values(Index);
|
||||||
|
({value, V}) ->
|
||||||
|
case V of
|
||||||
|
#{<<"type">> := T} ->
|
||||||
|
case maps:get(T, Index, undefined) of
|
||||||
|
undefined ->
|
||||||
|
throw(#{
|
||||||
|
field_name => type,
|
||||||
|
reason => <<"unknown listener type">>
|
||||||
|
});
|
||||||
|
Ref ->
|
||||||
|
[Ref]
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
throw(#{
|
||||||
|
field_name => type,
|
||||||
|
reason => <<"unknown listener type">>
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
create_listener_schema(Opts) ->
|
create_listener_schema(Opts) ->
|
||||||
Schemas = [
|
Schemas = [
|
||||||
?R_REF(Mod, {Type, with_name})
|
?R_REF(Mod, {Type, with_name})
|
||||||
|
@ -311,6 +340,7 @@ listeners_info(Opts) ->
|
||||||
TypeAtom = list_to_existing_atom(ListenerType),
|
TypeAtom = list_to_existing_atom(ListenerType),
|
||||||
#{
|
#{
|
||||||
ref => ?R_REF(Ref),
|
ref => ?R_REF(Ref),
|
||||||
|
listener_type => ListenerType,
|
||||||
schema => [
|
schema => [
|
||||||
{type, ?HOCON(?ENUM([TypeAtom]), #{desc => "Listener type", required => true})},
|
{type, ?HOCON(?ENUM([TypeAtom]), #{desc => "Listener type", required => true})},
|
||||||
{running, ?HOCON(boolean(), #{desc => "Listener status", required => false})},
|
{running, ?HOCON(boolean(), #{desc => "Listener status", required => false})},
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
-define(PORT(Base), (Base + ?LINE)).
|
-define(PORT(Base), (Base + ?LINE)).
|
||||||
-define(PORT, ?PORT(20000)).
|
-define(PORT, ?PORT(20000)).
|
||||||
|
@ -404,6 +405,62 @@ t_action_listeners(Config) when is_list(Config) ->
|
||||||
action_listener(ID, "start", true),
|
action_listener(ID, "start", true),
|
||||||
action_listener(ID, "restart", true).
|
action_listener(ID, "restart", true).
|
||||||
|
|
||||||
|
t_update_validation_error_message({init, Config}) ->
|
||||||
|
NewListenerId = <<"ssl:new", (integer_to_binary(?LINE))/binary>>,
|
||||||
|
NewPath = emqx_mgmt_api_test_util:api_path(["listeners", NewListenerId]),
|
||||||
|
ListenerId = "ssl:default",
|
||||||
|
OriginalPath = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
|
||||||
|
OriginalListener = request(get, OriginalPath, [], []),
|
||||||
|
[
|
||||||
|
{new_listener_id, NewListenerId},
|
||||||
|
{new_path, NewPath},
|
||||||
|
{original_listener, OriginalListener}
|
||||||
|
| Config
|
||||||
|
];
|
||||||
|
t_update_validation_error_message(Config) when is_list(Config) ->
|
||||||
|
NewListenerId = ?config(new_listener_id, Config),
|
||||||
|
NewPath = ?config(new_path, Config),
|
||||||
|
OriginalListener = ?config(original_listener, Config),
|
||||||
|
Port = integer_to_binary(?PORT),
|
||||||
|
NewListener = OriginalListener#{
|
||||||
|
<<"id">> := NewListenerId,
|
||||||
|
<<"bind">> => <<"0.0.0.0:", Port/binary>>
|
||||||
|
},
|
||||||
|
CreateResp = request(post, NewPath, [], NewListener),
|
||||||
|
?assertEqual(lists:sort(maps:keys(OriginalListener)), lists:sort(maps:keys(CreateResp))),
|
||||||
|
|
||||||
|
%% check that a validation error is user-friendly
|
||||||
|
WrongConf1a = emqx_utils_maps:deep_put(
|
||||||
|
[<<"ssl_options">>, <<"enable_crl_check">>],
|
||||||
|
CreateResp,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
WrongConf1 = emqx_utils_maps:deep_put(
|
||||||
|
[<<"ssl_options">>, <<"verify">>],
|
||||||
|
WrongConf1a,
|
||||||
|
<<"verify_none">>
|
||||||
|
),
|
||||||
|
Result1 = request(put, NewPath, [], WrongConf1, #{return_all => true}),
|
||||||
|
?assertMatch({error, {{_, 400, _}, _Headers, _Body}}, Result1),
|
||||||
|
{error, {{_, _Code, _}, _Headers, Body1}} = Result1,
|
||||||
|
#{<<"message">> := RawMsg1} = emqx_utils_json:decode(Body1, [return_maps]),
|
||||||
|
Msg1 = emqx_utils_json:decode(RawMsg1, [return_maps]),
|
||||||
|
%% No confusing union type errors.
|
||||||
|
?assertNotMatch(#{<<"mismatches">> := _}, Msg1),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"kind">> := <<"validation_error">>,
|
||||||
|
<<"reason">> := <<"verify must be verify_peer when CRL check is enabled">>,
|
||||||
|
<<"value">> := #{}
|
||||||
|
},
|
||||||
|
Msg1
|
||||||
|
),
|
||||||
|
ok;
|
||||||
|
t_update_validation_error_message({'end', Config}) ->
|
||||||
|
NewPath = ?config(new_path, Config),
|
||||||
|
?assertEqual([], delete(NewPath)),
|
||||||
|
ok.
|
||||||
|
|
||||||
action_listener(ID, Action, Running) ->
|
action_listener(ID, Action, Running) ->
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["listeners", ID, Action]),
|
Path = emqx_mgmt_api_test_util:api_path(["listeners", ID, Action]),
|
||||||
{ok, _} = emqx_mgmt_api_test_util:request_api(post, Path),
|
{ok, _} = emqx_mgmt_api_test_util:request_api(post, Path),
|
||||||
|
@ -413,8 +470,11 @@ action_listener(ID, Action, Running) ->
|
||||||
listener_stats(Listener, Running).
|
listener_stats(Listener, Running).
|
||||||
|
|
||||||
request(Method, Url, QueryParams, Body) ->
|
request(Method, Url, QueryParams, Body) ->
|
||||||
|
request(Method, Url, QueryParams, Body, _Opts = #{}).
|
||||||
|
|
||||||
|
request(Method, Url, QueryParams, Body, Opts) ->
|
||||||
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
||||||
case emqx_mgmt_api_test_util:request_api(Method, Url, QueryParams, AuthHeader, Body) of
|
case emqx_mgmt_api_test_util:request_api(Method, Url, QueryParams, AuthHeader, Body, Opts) of
|
||||||
{ok, Res} -> emqx_utils_json:decode(Res, [return_maps]);
|
{ok, Res} -> emqx_utils_json:decode(Res, [return_maps]);
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Improved error messages when a validation error occurs while using the Listeners HTTP API.
|
Loading…
Reference in New Issue