emqx/apps/emqx_management/src/emqx_mgmt_util.erl
2022-04-19 14:02:39 +08:00

328 lines
9.6 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_mgmt_util).
-export([
strftime/1,
datetime/1,
kmg/1,
ntoa/1,
merge_maps/2,
batch_operation/3
]).
-export([
bad_request/0,
bad_request/1,
properties/1,
page_params/0,
schema/1,
schema/2,
object_schema/1,
object_schema/2,
array_schema/1,
array_schema/2,
object_array_schema/1,
object_array_schema/2,
page_schema/1,
page_object_schema/1,
error_schema/1,
error_schema/2,
batch_schema/1
]).
-export([urldecode/1]).
-define(KB, 1024).
-define(MB, (1024 * 1024)).
-define(GB, (1024 * 1024 * 1024)).
%%--------------------------------------------------------------------
%% Strftime
%%--------------------------------------------------------------------
strftime({MegaSecs, Secs, _MicroSecs}) ->
strftime(datetime(MegaSecs * 1000000 + Secs));
strftime(Secs) when is_integer(Secs) ->
strftime(datetime(Secs));
strftime({{Y, M, D}, {H, MM, S}}) ->
lists:flatten(
io_lib:format(
"~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S]
)
).
datetime(Timestamp) when is_integer(Timestamp) ->
Epoch = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
Universal = calendar:gregorian_seconds_to_datetime(Timestamp + Epoch),
calendar:universal_time_to_local_time(Universal).
kmg(Byte) when Byte > ?GB ->
kmg(Byte / ?GB, "G");
kmg(Byte) when Byte > ?MB ->
kmg(Byte / ?MB, "M");
kmg(Byte) when Byte > ?KB ->
kmg(Byte / ?KB, "K");
kmg(Byte) ->
Byte.
kmg(F, S) ->
iolist_to_binary(io_lib:format("~.2f~ts", [F, S])).
ntoa({0, 0, 0, 0, 0, 16#ffff, AB, CD}) ->
inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256});
ntoa(IP) ->
inet_parse:ntoa(IP).
merge_maps(Default, New) ->
maps:fold(
fun(K, V, Acc) ->
case maps:get(K, Acc, undefined) of
OldV when
is_map(OldV),
is_map(V)
->
Acc#{K => merge_maps(OldV, V)};
_ ->
Acc#{K => V}
end
end,
Default,
New
).
urldecode(S) ->
emqx_http_lib:uri_decode(S).
%%%==============================================================================================
%% schema util
schema(Ref) when is_atom(Ref) ->
json_content_schema(minirest:ref(atom_to_binary(Ref, utf8)));
schema(SchemaOrDesc) ->
json_content_schema(SchemaOrDesc).
schema(Ref, Desc) when is_atom(Ref) ->
json_content_schema(minirest:ref(atom_to_binary(Ref, utf8)), Desc);
schema(Schema, Desc) ->
json_content_schema(Schema, Desc).
object_schema(Properties) when is_map(Properties) ->
json_content_schema(#{type => object, properties => Properties}).
object_schema(Properties, Desc) when is_map(Properties) ->
json_content_schema(#{type => object, properties => Properties}, Desc).
array_schema(Ref) when is_atom(Ref) ->
json_content_schema(#{type => array, items => minirest:ref(atom_to_binary(Ref, utf8))}).
array_schema(Ref, Desc) when is_atom(Ref) ->
json_content_schema(#{type => array, items => minirest:ref(atom_to_binary(Ref, utf8))}, Desc);
array_schema(Schema, Desc) ->
json_content_schema(#{type => array, items => Schema}, Desc).
object_array_schema(Properties) when is_map(Properties) ->
json_content_schema(#{type => array, items => #{type => object, properties => Properties}}).
object_array_schema(Properties, Desc) ->
json_content_schema(
#{
type => array,
items => #{type => object, properties => Properties}
},
Desc
).
page_schema(Ref) when is_atom(Ref) ->
page_schema(minirest:ref(atom_to_binary(Ref, utf8)));
page_schema(Schema) ->
Schema1 = #{
type => object,
properties => #{
meta => #{
type => object,
properties => properties([
{page, integer},
{limit, integer},
{count, integer}
])
},
data => #{
type => array,
items => Schema
}
}
},
json_content_schema(Schema1).
page_object_schema(Properties) when is_map(Properties) ->
page_schema(#{type => object, properties => Properties}).
error_schema(Description) ->
error_schema(Description, ['RESOURCE_NOT_FOUND']).
error_schema(Description, Enum) ->
Schema = #{
type => object,
properties => properties([
{code, string, <<>>, Enum},
{message, string}
])
},
json_content_schema(Schema, Description).
batch_schema(DefName) when is_atom(DefName) ->
batch_schema(atom_to_binary(DefName, utf8));
batch_schema(DefName) when is_binary(DefName) ->
Schema = #{
type => object,
properties => #{
success => #{
type => integer,
description => <<"Success count">>
},
failed => #{
type => integer,
description => <<"Failed count">>
},
detail => #{
type => array,
description => <<"Failed object & reason">>,
items => #{
type => object,
properties =>
#{
data => minirest:ref(DefName),
reason => #{
type => <<"string">>
}
}
}
}
}
},
json_content_schema(Schema).
json_content_schema(Schema) when is_map(Schema) ->
#{content => #{'application/json' => #{schema => Schema}}};
json_content_schema(Desc) when is_binary(Desc) ->
#{description => Desc}.
json_content_schema(Schema, Desc) ->
#{
content => #{'application/json' => #{schema => Schema}},
description => Desc
}.
%%%==============================================================================================
batch_operation(Module, Function, ArgsList) ->
Failed = batch_operation(Module, Function, ArgsList, []),
Len = erlang:length(Failed),
Success = erlang:length(ArgsList) - Len,
Fun =
fun({Args, Reason}, Detail) ->
[#{data => Args, reason => io_lib:format("~p", [Reason])} | Detail]
end,
#{success => Success, failed => Len, detail => lists:foldl(Fun, [], Failed)}.
batch_operation(_Module, _Function, [], Failed) ->
lists:reverse(Failed);
batch_operation(Module, Function, [Args | ArgsList], Failed) ->
case erlang:apply(Module, Function, Args) of
ok ->
batch_operation(Module, Function, ArgsList, Failed);
{error, Reason} ->
batch_operation(Module, Function, ArgsList, [{Args, Reason} | Failed])
end.
properties(Props) ->
properties(Props, #{}).
properties([], Acc) ->
Acc;
properties([Key | Props], Acc) when is_atom(Key) ->
properties(Props, maps:put(Key, #{type => string}, Acc));
properties([{Key, Type} | Props], Acc) ->
properties(Props, maps:put(Key, #{type => Type}, Acc));
properties([{Key, object, Props1} | Props], Acc) ->
properties(
Props,
maps:put(
Key,
#{
type => object,
properties => properties(Props1)
},
Acc
)
);
properties([{Key, {array, object}, Props1} | Props], Acc) ->
properties(
Props,
maps:put(
Key,
#{
type => array,
items => #{
type => object,
properties => properties(Props1)
}
},
Acc
)
);
properties([{Key, {array, Type}, Desc} | Props], Acc) ->
properties(
Props,
maps:put(
Key,
#{
type => array,
items => #{type => Type},
description => Desc
},
Acc
)
);
properties([{Key, Type, Desc} | Props], Acc) ->
properties(Props, maps:put(Key, #{type => Type, description => Desc}, Acc));
properties([{Key, Type, Desc, Enum} | Props], Acc) ->
properties(
Props,
maps:put(
Key,
#{
type => Type,
description => Desc,
enum => Enum
},
Acc
)
).
page_params() ->
[
#{
name => page,
in => query,
description => <<"Page">>,
schema => #{type => integer, default => 1}
},
#{
name => limit,
in => query,
description => <<"Page size">>,
schema => #{type => integer, default => emqx_mgmt:max_row_limit()}
}
].
bad_request() ->
bad_request(<<"Bad Request">>).
bad_request(Desc) ->
object_schema(properties([{message, string}, {code, string}]), Desc).