feat(message_validation_api): implement `reorder` API
This commit is contained in:
parent
bcb7fe96d5
commit
74c03377f2
|
@ -17,6 +17,7 @@
|
|||
|
||||
list/0,
|
||||
move/2,
|
||||
reorder/1,
|
||||
lookup/1,
|
||||
insert/1,
|
||||
update/1,
|
||||
|
@ -87,6 +88,15 @@ move(Name, Position) ->
|
|||
#{override_to => cluster}
|
||||
).
|
||||
|
||||
-spec reorder([validation_name()]) ->
|
||||
{ok, _} | {error, _}.
|
||||
reorder(Order) ->
|
||||
emqx:update_config(
|
||||
?VALIDATIONS_CONF_PATH,
|
||||
{reorder, Order},
|
||||
#{override_to => cluster}
|
||||
).
|
||||
|
||||
-spec lookup(validation_name()) -> {ok, validation()} | {error, not_found}.
|
||||
lookup(Name) ->
|
||||
Validations = emqx:get_config(?VALIDATIONS_CONF_PATH, []),
|
||||
|
@ -165,7 +175,9 @@ pre_config_update(?VALIDATIONS_CONF_PATH, {update, Validation}, OldValidations)
|
|||
pre_config_update(?VALIDATIONS_CONF_PATH, {delete, Validation}, OldValidations) ->
|
||||
delete(OldValidations, Validation);
|
||||
pre_config_update(?VALIDATIONS_CONF_PATH, {move, Name, Position}, OldValidations) ->
|
||||
move(OldValidations, Name, Position).
|
||||
move(OldValidations, Name, Position);
|
||||
pre_config_update(?VALIDATIONS_CONF_PATH, {reorder, Order}, OldValidations) ->
|
||||
reorder(OldValidations, Order).
|
||||
|
||||
post_config_update(?VALIDATIONS_CONF_PATH, {append, #{<<"name">> := Name}}, New, _Old, _AppEnvs) ->
|
||||
{Pos, Validation} = fetch_with_index(New, Name),
|
||||
|
@ -181,6 +193,9 @@ post_config_update(?VALIDATIONS_CONF_PATH, {delete, Name}, _New, Old, _AppEnvs)
|
|||
ok = emqx_message_validation_registry:delete(Validation),
|
||||
ok;
|
||||
post_config_update(?VALIDATIONS_CONF_PATH, {move, _Name, _Position}, New, _Old, _AppEnvs) ->
|
||||
ok = emqx_message_validation_registry:reindex_positions(New),
|
||||
ok;
|
||||
post_config_update(?VALIDATIONS_CONF_PATH, {reorder, _Order}, New, _Old, _AppEnvs) ->
|
||||
ok = emqx_message_validation_registry:reindex_positions(New),
|
||||
ok.
|
||||
|
||||
|
@ -348,6 +363,52 @@ move(OldValidations, Name, {before, OtherName}) ->
|
|||
{OtherValidation, Front2, Rear2} = take(OtherName, Front1 ++ Rear1),
|
||||
{ok, Front2 ++ [Validation, OtherValidation] ++ Rear2}.
|
||||
|
||||
reorder(Validations, Order) ->
|
||||
Context = #{
|
||||
not_found => sets:new([{version, 2}]),
|
||||
duplicated => sets:new([{version, 2}]),
|
||||
res => [],
|
||||
seen => sets:new([{version, 2}])
|
||||
},
|
||||
reorder(Validations, Order, Context).
|
||||
|
||||
reorder(NotReordered, _Order = [], #{not_found := NotFound0, duplicated := Duplicated0, res := Res}) ->
|
||||
NotFound = sets:to_list(NotFound0),
|
||||
Duplicated = sets:to_list(Duplicated0),
|
||||
case {NotReordered, NotFound, Duplicated} of
|
||||
{[], [], []} ->
|
||||
{ok, lists:reverse(Res)};
|
||||
{_, _, _} ->
|
||||
Error = #{
|
||||
not_found => NotFound,
|
||||
duplicated => Duplicated,
|
||||
not_reordered => [N || #{<<"name">> := N} <- NotReordered]
|
||||
},
|
||||
{error, Error}
|
||||
end;
|
||||
reorder(RemainingValidations, [Name | Rest], Context0 = #{seen := Seen0}) ->
|
||||
case sets:is_element(Name, Seen0) of
|
||||
true ->
|
||||
Context = maps:update_with(
|
||||
duplicated, fun(S) -> sets:add_element(Name, S) end, Context0
|
||||
),
|
||||
reorder(RemainingValidations, Rest, Context);
|
||||
false ->
|
||||
case safe_take(Name, RemainingValidations) of
|
||||
error ->
|
||||
Context = maps:update_with(
|
||||
not_found, fun(S) -> sets:add_element(Name, S) end, Context0
|
||||
),
|
||||
reorder(RemainingValidations, Rest, Context);
|
||||
{ok, {Validation, Front, Rear}} ->
|
||||
Context1 = maps:update_with(
|
||||
seen, fun(S) -> sets:add_element(Name, S) end, Context0
|
||||
),
|
||||
Context = maps:update_with(res, fun(Vs) -> [Validation | Vs] end, Context1),
|
||||
reorder(Front ++ Rear, Rest, Context)
|
||||
end
|
||||
end.
|
||||
|
||||
fetch_with_index([{Pos, #{name := Name} = Validation} | _Rest], Name) ->
|
||||
{Pos, Validation};
|
||||
fetch_with_index([{_, _} | Rest], Name) ->
|
||||
|
@ -356,11 +417,19 @@ fetch_with_index(Validations, Name) ->
|
|||
fetch_with_index(lists:enumerate(Validations), Name).
|
||||
|
||||
take(Name, Validations) ->
|
||||
case safe_take(Name, Validations) of
|
||||
error ->
|
||||
throw({validation_not_found, Name});
|
||||
{ok, {Found, Front, Rear}} ->
|
||||
{Found, Front, Rear}
|
||||
end.
|
||||
|
||||
safe_take(Name, Validations) ->
|
||||
case lists:splitwith(fun(#{<<"name">> := N}) -> N =/= Name end, Validations) of
|
||||
{_Front, []} ->
|
||||
throw({validation_not_found, Name});
|
||||
error;
|
||||
{Front, [Found | Rear]} ->
|
||||
{Found, Front, Rear}
|
||||
{ok, {Found, Front, Rear}}
|
||||
end.
|
||||
|
||||
do_lookup(_Name, _Validations = []) ->
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
%% `minirest' handlers
|
||||
-export([
|
||||
'/message_validations'/2,
|
||||
'/message_validations/reorder'/2,
|
||||
'/message_validations/validation/:name'/2,
|
||||
'/message_validations/validation/:name/move'/2
|
||||
]).
|
||||
|
@ -44,6 +45,7 @@ api_spec() ->
|
|||
paths() ->
|
||||
[
|
||||
"/message_validations",
|
||||
"/message_validations/reorder",
|
||||
"/message_validations/validation/:name",
|
||||
"/message_validations/validation/:name/move"
|
||||
].
|
||||
|
@ -59,7 +61,7 @@ schema("/message_validations") ->
|
|||
#{
|
||||
200 =>
|
||||
emqx_dashboard_swagger:schema_with_examples(
|
||||
hoconsc:array(
|
||||
array(
|
||||
emqx_message_validation_schema:api_schema(list)
|
||||
),
|
||||
#{
|
||||
|
@ -107,6 +109,35 @@ schema("/message_validations") ->
|
|||
}
|
||||
}
|
||||
};
|
||||
schema("/message_validations/reorder") ->
|
||||
#{
|
||||
'operationId' => '/message_validations/reorder',
|
||||
post => #{
|
||||
tags => ?TAGS,
|
||||
summary => <<"Reorder all validations">>,
|
||||
description => ?DESC("reorder_validations"),
|
||||
'requestBody' =>
|
||||
emqx_dashboard_swagger:schema_with_examples(
|
||||
ref(reorder),
|
||||
example_input_reorder()
|
||||
),
|
||||
responses =>
|
||||
#{
|
||||
204 => <<"No Content">>,
|
||||
400 => error_schema(
|
||||
'BAD_REQUEST',
|
||||
<<"Bad request">>,
|
||||
[
|
||||
{not_found, mk(array(binary()), #{desc => "Validations not found"})},
|
||||
{not_reordered,
|
||||
mk(array(binary()), #{desc => "Validations not referenced in input"})},
|
||||
{duplicated,
|
||||
mk(array(binary()), #{desc => "Duplicated validations in input"})}
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
schema("/message_validations/validation/:name") ->
|
||||
#{
|
||||
'operationId' => '/message_validations/validation/:name',
|
||||
|
@ -119,7 +150,7 @@ schema("/message_validations/validation/:name") ->
|
|||
#{
|
||||
200 =>
|
||||
emqx_dashboard_swagger:schema_with_examples(
|
||||
hoconsc:array(
|
||||
array(
|
||||
emqx_message_validation_schema:api_schema(lookup)
|
||||
),
|
||||
#{
|
||||
|
@ -189,6 +220,10 @@ fields(before) ->
|
|||
[
|
||||
{position, mk(before, #{default => before, required => true, in => body})},
|
||||
{validation, mk(binary(), #{required => true, in => body})}
|
||||
];
|
||||
fields(reorder) ->
|
||||
[
|
||||
{order, mk(array(binary()), #{required => true, in => body})}
|
||||
].
|
||||
|
||||
%%-------------------------------------------------------------------------------------------------
|
||||
|
@ -255,12 +290,16 @@ fields(before) ->
|
|||
not_found(Name)
|
||||
).
|
||||
|
||||
'/message_validations/reorder'(post, #{body := #{<<"order">> := Order}}) ->
|
||||
do_reorder(Order).
|
||||
|
||||
%%-------------------------------------------------------------------------------------------------
|
||||
%% Internal fns
|
||||
%%-------------------------------------------------------------------------------------------------
|
||||
|
||||
ref(Struct) -> hoconsc:ref(?MODULE, Struct).
|
||||
mk(Type, Opts) -> hoconsc:mk(Type, Opts).
|
||||
array(Type) -> hoconsc:array(Type).
|
||||
|
||||
example_input_create() ->
|
||||
%% TODO
|
||||
|
@ -270,6 +309,10 @@ example_input_update() ->
|
|||
%% TODO
|
||||
#{}.
|
||||
|
||||
example_input_reorder() ->
|
||||
%% TODO
|
||||
#{}.
|
||||
|
||||
example_return_list() ->
|
||||
%% TODO
|
||||
[].
|
||||
|
@ -290,12 +333,15 @@ example_position() ->
|
|||
%% TODO
|
||||
#{}.
|
||||
|
||||
error_schema(Code, Message) when is_atom(Code) ->
|
||||
error_schema([Code], Message);
|
||||
error_schema(Codes, Message) when is_list(Message) ->
|
||||
error_schema(Codes, list_to_binary(Message));
|
||||
error_schema(Codes, Message) when is_list(Codes) andalso is_binary(Message) ->
|
||||
emqx_dashboard_swagger:error_codes(Codes, Message).
|
||||
error_schema(Code, Message) ->
|
||||
error_schema(Code, Message, _ExtraFields = []).
|
||||
|
||||
error_schema(Code, Message, ExtraFields) when is_atom(Code) ->
|
||||
error_schema([Code], Message, ExtraFields);
|
||||
error_schema(Codes, Message, ExtraFields) when is_list(Message) ->
|
||||
error_schema(Codes, list_to_binary(Message), ExtraFields);
|
||||
error_schema(Codes, Message, ExtraFields) when is_list(Codes) andalso is_binary(Message) ->
|
||||
ExtraFields ++ emqx_dashboard_swagger:error_codes(Codes, Message).
|
||||
|
||||
position_union_member_selector(all_union_members) ->
|
||||
position_refs();
|
||||
|
@ -359,6 +405,27 @@ do_move(ValidationName, Position) ->
|
|||
?BAD_REQUEST(Error)
|
||||
end.
|
||||
|
||||
do_reorder(Order) ->
|
||||
case emqx_message_validation:reorder(Order) of
|
||||
{ok, _} ->
|
||||
?NO_CONTENT;
|
||||
{error,
|
||||
{pre_config_update, _HandlerMod, #{
|
||||
not_found := NotFound,
|
||||
duplicated := Duplicated,
|
||||
not_reordered := NotReordered
|
||||
}}} ->
|
||||
Msg0 = ?ERROR_MSG('BAD_REQUEST', <<"Bad request">>),
|
||||
Msg = Msg0#{
|
||||
not_found => NotFound,
|
||||
duplicated => Duplicated,
|
||||
not_reordered => NotReordered
|
||||
},
|
||||
{400, Msg};
|
||||
{error, Error} ->
|
||||
?BAD_REQUEST(Error)
|
||||
end.
|
||||
|
||||
with_validation(Name, FoundFn, NotFoundFn) ->
|
||||
case emqx_message_validation:lookup(Name) of
|
||||
{ok, Validation} ->
|
||||
|
|
|
@ -182,7 +182,8 @@ move(Name, Pos) ->
|
|||
|
||||
reorder(Order) ->
|
||||
Path = emqx_mgmt_api_test_util:api_path([api_root(), "reorder"]),
|
||||
Res = request(post, Path, Order),
|
||||
Params = #{<<"order">> => Order},
|
||||
Res = request(post, Path, Params),
|
||||
ct:pal("reorder result:\n ~p", [Res]),
|
||||
simplify_result(Res).
|
||||
|
||||
|
@ -514,6 +515,103 @@ t_move(_Config) ->
|
|||
|
||||
ok.
|
||||
|
||||
%% test the "reorder" API
|
||||
t_reorder(_Config) ->
|
||||
%% no validations to reorder
|
||||
?assertMatch({204, _}, reorder([])),
|
||||
|
||||
%% unknown validation
|
||||
?assertMatch(
|
||||
{400, #{<<"not_found">> := [<<"nonexistent">>]}},
|
||||
reorder([<<"nonexistent">>])
|
||||
),
|
||||
|
||||
Topic = <<"t">>,
|
||||
|
||||
Name1 = <<"foo">>,
|
||||
Validation1 = validation(Name1, [sql_check()], #{<<"topics">> => Topic}),
|
||||
{201, _} = insert(Validation1),
|
||||
|
||||
%% unknown validation
|
||||
?assertMatch(
|
||||
{400, #{
|
||||
%% Note: minirest currently encodes empty lists as a "[]" string...
|
||||
<<"duplicated">> := "[]",
|
||||
<<"not_found">> := [<<"nonexistent">>],
|
||||
<<"not_reordered">> := [Name1]
|
||||
}},
|
||||
reorder([<<"nonexistent">>])
|
||||
),
|
||||
|
||||
%% repeated validations
|
||||
?assertMatch(
|
||||
{400, #{
|
||||
<<"not_found">> := "[]",
|
||||
<<"duplicated">> := [Name1],
|
||||
<<"not_reordered">> := "[]"
|
||||
}},
|
||||
reorder([Name1, Name1])
|
||||
),
|
||||
|
||||
%% mixed known, unknown and repeated validations
|
||||
?assertMatch(
|
||||
{400, #{
|
||||
<<"not_found">> := [<<"nonexistent">>],
|
||||
<<"duplicated">> := [Name1],
|
||||
%% Note: minirest currently encodes empty lists as a "[]" string...
|
||||
<<"not_reordered">> := "[]"
|
||||
}},
|
||||
reorder([Name1, <<"nonexistent">>, <<"nonexistent">>, Name1])
|
||||
),
|
||||
|
||||
?assertMatch({204, _}, reorder([Name1])),
|
||||
?assertMatch({200, [#{<<"name">> := Name1}]}, list()),
|
||||
?assertIndexOrder([Name1], Topic),
|
||||
|
||||
Name2 = <<"bar">>,
|
||||
Validation2 = validation(Name2, [sql_check()], #{<<"topics">> => Topic}),
|
||||
{201, _} = insert(Validation2),
|
||||
Name3 = <<"baz">>,
|
||||
Validation3 = validation(Name3, [sql_check()], #{<<"topics">> => Topic}),
|
||||
{201, _} = insert(Validation3),
|
||||
|
||||
?assertMatch(
|
||||
{200, [#{<<"name">> := Name1}, #{<<"name">> := Name2}, #{<<"name">> := Name3}]},
|
||||
list()
|
||||
),
|
||||
?assertIndexOrder([Name1, Name2, Name3], Topic),
|
||||
|
||||
%% Doesn't mention all validations
|
||||
?assertMatch(
|
||||
{400, #{
|
||||
%% Note: minirest currently encodes empty lists as a "[]" string...
|
||||
<<"not_found">> := "[]",
|
||||
<<"not_reordered">> := [_, _]
|
||||
}},
|
||||
reorder([Name1])
|
||||
),
|
||||
?assertMatch(
|
||||
{200, [#{<<"name">> := Name1}, #{<<"name">> := Name2}, #{<<"name">> := Name3}]},
|
||||
list()
|
||||
),
|
||||
?assertIndexOrder([Name1, Name2, Name3], Topic),
|
||||
|
||||
?assertMatch({204, _}, reorder([Name3, Name2, Name1])),
|
||||
?assertMatch(
|
||||
{200, [#{<<"name">> := Name3}, #{<<"name">> := Name2}, #{<<"name">> := Name1}]},
|
||||
list()
|
||||
),
|
||||
?assertIndexOrder([Name3, Name2, Name1], Topic),
|
||||
|
||||
?assertMatch({204, _}, reorder([Name1, Name3, Name2])),
|
||||
?assertMatch(
|
||||
{200, [#{<<"name">> := Name1}, #{<<"name">> := Name3}, #{<<"name">> := Name2}]},
|
||||
list()
|
||||
),
|
||||
?assertIndexOrder([Name1, Name3, Name2], Topic),
|
||||
|
||||
ok.
|
||||
|
||||
%% Check the `all_pass' strategy
|
||||
t_all_pass(_Config) ->
|
||||
Name1 = <<"foo">>,
|
||||
|
|
|
@ -18,6 +18,9 @@ emqx_message_validation_http_api {
|
|||
move_validation.desc:
|
||||
"""Change the order of a validation in the list of validations"""
|
||||
|
||||
reorder_validations.desc:
|
||||
"""Reorder of all validations"""
|
||||
|
||||
param_path_name.desc:
|
||||
"""Validation name"""
|
||||
|
||||
|
|
Loading…
Reference in New Issue