272 lines
9.5 KiB
Erlang
272 lines
9.5 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020-2021 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([generate_response/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).
|
|
|
|
%%%==============================================================================================
|
|
%% Response util
|
|
|
|
generate_response(QueryResult) ->
|
|
case QueryResult of
|
|
{error, page_limit_invalid} ->
|
|
{400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}};
|
|
Response ->
|
|
{200, Response}
|
|
end.
|