feat(message_validation_api): implement `reorder` API
This commit is contained in:
parent
bcb7fe96d5
commit
74c03377f2
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
list/0,
|
list/0,
|
||||||
move/2,
|
move/2,
|
||||||
|
reorder/1,
|
||||||
lookup/1,
|
lookup/1,
|
||||||
insert/1,
|
insert/1,
|
||||||
update/1,
|
update/1,
|
||||||
|
@ -87,6 +88,15 @@ move(Name, Position) ->
|
||||||
#{override_to => cluster}
|
#{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}.
|
-spec lookup(validation_name()) -> {ok, validation()} | {error, not_found}.
|
||||||
lookup(Name) ->
|
lookup(Name) ->
|
||||||
Validations = emqx:get_config(?VALIDATIONS_CONF_PATH, []),
|
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) ->
|
pre_config_update(?VALIDATIONS_CONF_PATH, {delete, Validation}, OldValidations) ->
|
||||||
delete(OldValidations, Validation);
|
delete(OldValidations, Validation);
|
||||||
pre_config_update(?VALIDATIONS_CONF_PATH, {move, Name, Position}, OldValidations) ->
|
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) ->
|
post_config_update(?VALIDATIONS_CONF_PATH, {append, #{<<"name">> := Name}}, New, _Old, _AppEnvs) ->
|
||||||
{Pos, Validation} = fetch_with_index(New, Name),
|
{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 = emqx_message_validation_registry:delete(Validation),
|
||||||
ok;
|
ok;
|
||||||
post_config_update(?VALIDATIONS_CONF_PATH, {move, _Name, _Position}, New, _Old, _AppEnvs) ->
|
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 = emqx_message_validation_registry:reindex_positions(New),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -348,6 +363,52 @@ move(OldValidations, Name, {before, OtherName}) ->
|
||||||
{OtherValidation, Front2, Rear2} = take(OtherName, Front1 ++ Rear1),
|
{OtherValidation, Front2, Rear2} = take(OtherName, Front1 ++ Rear1),
|
||||||
{ok, Front2 ++ [Validation, OtherValidation] ++ Rear2}.
|
{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) ->
|
fetch_with_index([{Pos, #{name := Name} = Validation} | _Rest], Name) ->
|
||||||
{Pos, Validation};
|
{Pos, Validation};
|
||||||
fetch_with_index([{_, _} | Rest], Name) ->
|
fetch_with_index([{_, _} | Rest], Name) ->
|
||||||
|
@ -356,11 +417,19 @@ fetch_with_index(Validations, Name) ->
|
||||||
fetch_with_index(lists:enumerate(Validations), Name).
|
fetch_with_index(lists:enumerate(Validations), Name).
|
||||||
|
|
||||||
take(Name, Validations) ->
|
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
|
case lists:splitwith(fun(#{<<"name">> := N}) -> N =/= Name end, Validations) of
|
||||||
{_Front, []} ->
|
{_Front, []} ->
|
||||||
throw({validation_not_found, Name});
|
error;
|
||||||
{Front, [Found | Rear]} ->
|
{Front, [Found | Rear]} ->
|
||||||
{Found, Front, Rear}
|
{ok, {Found, Front, Rear}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_lookup(_Name, _Validations = []) ->
|
do_lookup(_Name, _Validations = []) ->
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
%% `minirest' handlers
|
%% `minirest' handlers
|
||||||
-export([
|
-export([
|
||||||
'/message_validations'/2,
|
'/message_validations'/2,
|
||||||
|
'/message_validations/reorder'/2,
|
||||||
'/message_validations/validation/:name'/2,
|
'/message_validations/validation/:name'/2,
|
||||||
'/message_validations/validation/:name/move'/2
|
'/message_validations/validation/:name/move'/2
|
||||||
]).
|
]).
|
||||||
|
@ -44,6 +45,7 @@ api_spec() ->
|
||||||
paths() ->
|
paths() ->
|
||||||
[
|
[
|
||||||
"/message_validations",
|
"/message_validations",
|
||||||
|
"/message_validations/reorder",
|
||||||
"/message_validations/validation/:name",
|
"/message_validations/validation/:name",
|
||||||
"/message_validations/validation/:name/move"
|
"/message_validations/validation/:name/move"
|
||||||
].
|
].
|
||||||
|
@ -59,7 +61,7 @@ schema("/message_validations") ->
|
||||||
#{
|
#{
|
||||||
200 =>
|
200 =>
|
||||||
emqx_dashboard_swagger:schema_with_examples(
|
emqx_dashboard_swagger:schema_with_examples(
|
||||||
hoconsc:array(
|
array(
|
||||||
emqx_message_validation_schema:api_schema(list)
|
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") ->
|
schema("/message_validations/validation/:name") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => '/message_validations/validation/:name',
|
'operationId' => '/message_validations/validation/:name',
|
||||||
|
@ -119,7 +150,7 @@ schema("/message_validations/validation/:name") ->
|
||||||
#{
|
#{
|
||||||
200 =>
|
200 =>
|
||||||
emqx_dashboard_swagger:schema_with_examples(
|
emqx_dashboard_swagger:schema_with_examples(
|
||||||
hoconsc:array(
|
array(
|
||||||
emqx_message_validation_schema:api_schema(lookup)
|
emqx_message_validation_schema:api_schema(lookup)
|
||||||
),
|
),
|
||||||
#{
|
#{
|
||||||
|
@ -189,6 +220,10 @@ fields(before) ->
|
||||||
[
|
[
|
||||||
{position, mk(before, #{default => before, required => true, in => body})},
|
{position, mk(before, #{default => before, required => true, in => body})},
|
||||||
{validation, mk(binary(), #{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)
|
not_found(Name)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
'/message_validations/reorder'(post, #{body := #{<<"order">> := Order}}) ->
|
||||||
|
do_reorder(Order).
|
||||||
|
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
%% Internal fns
|
%% Internal fns
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
ref(Struct) -> hoconsc:ref(?MODULE, Struct).
|
ref(Struct) -> hoconsc:ref(?MODULE, Struct).
|
||||||
mk(Type, Opts) -> hoconsc:mk(Type, Opts).
|
mk(Type, Opts) -> hoconsc:mk(Type, Opts).
|
||||||
|
array(Type) -> hoconsc:array(Type).
|
||||||
|
|
||||||
example_input_create() ->
|
example_input_create() ->
|
||||||
%% TODO
|
%% TODO
|
||||||
|
@ -270,6 +309,10 @@ example_input_update() ->
|
||||||
%% TODO
|
%% TODO
|
||||||
#{}.
|
#{}.
|
||||||
|
|
||||||
|
example_input_reorder() ->
|
||||||
|
%% TODO
|
||||||
|
#{}.
|
||||||
|
|
||||||
example_return_list() ->
|
example_return_list() ->
|
||||||
%% TODO
|
%% TODO
|
||||||
[].
|
[].
|
||||||
|
@ -290,12 +333,15 @@ example_position() ->
|
||||||
%% TODO
|
%% TODO
|
||||||
#{}.
|
#{}.
|
||||||
|
|
||||||
error_schema(Code, Message) when is_atom(Code) ->
|
error_schema(Code, Message) ->
|
||||||
error_schema([Code], Message);
|
error_schema(Code, Message, _ExtraFields = []).
|
||||||
error_schema(Codes, Message) when is_list(Message) ->
|
|
||||||
error_schema(Codes, list_to_binary(Message));
|
error_schema(Code, Message, ExtraFields) when is_atom(Code) ->
|
||||||
error_schema(Codes, Message) when is_list(Codes) andalso is_binary(Message) ->
|
error_schema([Code], Message, ExtraFields);
|
||||||
emqx_dashboard_swagger:error_codes(Codes, Message).
|
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_union_member_selector(all_union_members) ->
|
||||||
position_refs();
|
position_refs();
|
||||||
|
@ -359,6 +405,27 @@ do_move(ValidationName, Position) ->
|
||||||
?BAD_REQUEST(Error)
|
?BAD_REQUEST(Error)
|
||||||
end.
|
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) ->
|
with_validation(Name, FoundFn, NotFoundFn) ->
|
||||||
case emqx_message_validation:lookup(Name) of
|
case emqx_message_validation:lookup(Name) of
|
||||||
{ok, Validation} ->
|
{ok, Validation} ->
|
||||||
|
|
|
@ -182,7 +182,8 @@ move(Name, Pos) ->
|
||||||
|
|
||||||
reorder(Order) ->
|
reorder(Order) ->
|
||||||
Path = emqx_mgmt_api_test_util:api_path([api_root(), "reorder"]),
|
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]),
|
ct:pal("reorder result:\n ~p", [Res]),
|
||||||
simplify_result(Res).
|
simplify_result(Res).
|
||||||
|
|
||||||
|
@ -514,6 +515,103 @@ t_move(_Config) ->
|
||||||
|
|
||||||
ok.
|
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
|
%% Check the `all_pass' strategy
|
||||||
t_all_pass(_Config) ->
|
t_all_pass(_Config) ->
|
||||||
Name1 = <<"foo">>,
|
Name1 = <<"foo">>,
|
||||||
|
|
|
@ -18,6 +18,9 @@ emqx_message_validation_http_api {
|
||||||
move_validation.desc:
|
move_validation.desc:
|
||||||
"""Change the order of a validation in the list of validations"""
|
"""Change the order of a validation in the list of validations"""
|
||||||
|
|
||||||
|
reorder_validations.desc:
|
||||||
|
"""Reorder of all validations"""
|
||||||
|
|
||||||
param_path_name.desc:
|
param_path_name.desc:
|
||||||
"""Validation name"""
|
"""Validation name"""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue