Merge pull request #6848 from JimMoen/refactor-api

refactor api swagger spec via hoconsc
This commit is contained in:
JimMoen 2022-01-26 09:49:23 +08:00 committed by GitHub
commit 05d6c40717
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 152 additions and 81 deletions

View File

@ -100,6 +100,7 @@ node_metrics_api() ->
parameters => parameters(), parameters => parameters(),
responses => #{ responses => #{
<<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']), <<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']),
%% TODO: Node Metrics Schema
<<"200">> => schema(metrics, <<"Get EMQ X Node Metrics">>)}}}, <<"200">> => schema(metrics, <<"Get EMQ X Node Metrics">>)}}},
{"/nodes/:node_name/metrics", Metadata, node_metrics}. {"/nodes/:node_name/metrics", Metadata, node_metrics}.
@ -110,6 +111,7 @@ node_stats_api() ->
parameters => parameters(), parameters => parameters(),
responses => #{ responses => #{
<<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']), <<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']),
%% TODO: Node Stats Schema
<<"200">> => schema(stat, <<"Get EMQ X Node Stats">>)}}}, <<"200">> => schema(stat, <<"Get EMQ X Node Stats">>)}}},
{"/nodes/:node_name/stats", Metadata, node_stats}. {"/nodes/:node_name/stats", Metadata, node_stats}.

View File

@ -17,82 +17,137 @@
-behaviour(minirest_api). -behaviour(minirest_api).
-export([api_spec/0]). -include_lib("typerefl/include/types.hrl").
-import( hoconsc
, [ mk/2
, ref/1
, ref/2
, array/1]).
-export([ api_spec/0
, paths/0
, schema/1
, fields/1
]).
-export([list/2]). -export([list/2]).
api_spec() -> api_spec() ->
{[stats_api()], stats_schema()}. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
stats_schema() -> paths() ->
Stats = #{ ["/stats"].
type => array,
items => #{
type => object,
properties => emqx_mgmt_util:properties([{'node', string} | properties()])
}
},
Stat = #{
type => object,
properties => emqx_mgmt_util:properties(properties())
},
StatsInfo =#{
oneOf => [ minirest:ref(stats)
, minirest:ref(stat)
]
},
[#{stats => Stats, stat => Stat, stats_info => StatsInfo}].
properties() -> schema("/stats") ->
[ #{ 'operationId' => list
{'channels.count', integer, <<"sessions.count">>}, , get =>
{'channels.max', integer, <<"session.max">>}, #{ description => <<"EMQ X stats">>
{'connections.count', integer, <<"Number of current connections">>}, , tags => [<<"stats">>]
{'connections.max', integer, <<"Historical maximum number of connections">>}, , parameters => [ref(aggregate)]
{'retained.count', integer, <<"Number of currently retained messages">>}, , responses =>
{'retained.max', integer, <<"Historical maximum number of retained messages">>}, #{ 200 => mk( hoconsc:union([ ref(?MODULE, base_data)
{'routes.count', integer, <<"Number of current routes">>}, , array(ref(?MODULE, aggergate_data))
{'routes.max', integer, <<"Historical maximum number of routes">>}, ])
{'sessions.count', integer, <<"Number of current sessions">>}, , #{ desc => <<"List stats ok">> })
{'sessions.max', integer, <<"Historical maximum number of sessions">>}, }
{'suboptions.count', integer, <<"subscriptions.count">>}, }
{'suboptions.max', integer, <<"subscriptions.max">>}, }.
{'subscribers.count', integer, <<"Number of current subscribers">>},
{'subscribers.max', integer, <<"Historical maximum number of subscribers">>}, fields(aggregate) ->
{'subscriptions.count', integer, <<"Number of current subscriptions, including shared subscriptions">>}, [ { aggregate
{'subscriptions.max', integer, <<"Historical maximum number of subscriptions">>}, , mk( boolean()
{'subscriptions.shared.count', integer, <<"Number of current shared subscriptions">>}, , #{ desc => <<"Calculation aggregate for all nodes">>
{'subscriptions.shared.max', integer, <<"Historical maximum number of shared subscriptions">>}, , in => query
{'topics.count', integer, <<"Number of current topics">>}, , nullable => true
{'topics.max', integer, <<"Historical maximum number of topics">>} , example => false})}
]. ];
fields(base_data) ->
[ { 'channels.count'
, mk( integer(), #{ desc => <<"sessions.count">>
, example => 0})}
, { 'channels.max'
, mk( integer(), #{ desc => <<"session.max">>
, example => 0})}
, { 'connections.count'
, mk( integer(), #{ desc => <<"Number of current connections">>
, example => 0})}
, { 'connections.max'
, mk( integer(), #{ desc => <<"Historical maximum number of connections">>
, example => 0})}
, { 'delayed.count'
, mk( integer(), #{ desc => <<"Number of delayed messages">>
, example => 0})}
, { 'delayed.max'
, mk( integer(), #{ desc => <<"Historical maximum number of delayed messages">>
, example => 0})}
, { 'live_connections.count'
, mk( integer(), #{ desc => <<"Number of current live connections">>
, example => 0})}
, { 'live_connections.max'
, mk( integer(), #{ desc => <<"Historical maximum number of live connections">>
, example => 0})}
, { 'retained.count'
, mk( integer(), #{ desc => <<"Number of currently retained messages">>
, example => 0})}
, { 'retained.max'
, mk( integer(), #{ desc => <<"Historical maximum number of retained messages">>
, example => 0})}
, { 'routes.count'
, mk( integer(), #{ desc => <<"Number of current routes">>
, example => 0})}
, { 'routes.max'
, mk( integer(), #{ desc => <<"Historical maximum number of routes">>
, example => 0})}
, { 'sessions.count'
, mk( integer(), #{ desc => <<"Number of current sessions">>
, example => 0})}
, { 'sessions.max'
, mk( integer(), #{ desc => <<"Historical maximum number of sessions">>
, example => 0})}
, { 'suboptions.count'
, mk( integer(), #{ desc => <<"subscriptions.count">>
, example => 0})}
, { 'suboptions.max'
, mk( integer(), #{ desc => <<"subscriptions.max">>
, example => 0})}
, { 'subscribers.count'
, mk( integer(), #{ desc => <<"Number of current subscribers">>
, example => 0})}
, { 'subscribers.max'
, mk( integer(), #{ desc => <<"Historical maximum number of subscribers">>
, example => 0})}
, { 'subscriptions.count'
, mk( integer(), #{ desc => <<"Number of current subscriptions, including shared subscriptions">>
, example => 0})}
, { 'subscriptions.max'
, mk( integer(), #{ desc => <<"Historical maximum number of subscriptions">>
, example => 0})}
, { 'subscriptions.shared.count'
, mk( integer(), #{ desc => <<"Number of current shared subscriptions">>
, example => 0})}
, { 'subscriptions.shared.max'
, mk( integer(), #{ desc => <<"Historical maximum number of shared subscriptions">>
, example => 0})}
, { 'topics.count'
, mk( integer(), #{ desc => <<"Number of current topics">>
, example => 0})}
, { 'topics.max'
, mk( integer(), #{ desc => <<"Historical maximum number of topics">>
, example => 0})}
];
fields(aggergate_data) ->
[ { node
, mk( string(), #{ desc => <<"Node name">>
, example => <<"emqx@127.0.0.1">>})}
] ++ fields(base_data).
stats_api() ->
Metadata = #{
get => #{
description => <<"EMQ X stats">>,
parameters => [#{
name => aggregate,
in => query,
schema => #{type => boolean}
}],
responses => #{
<<"200">> => #{
description => <<"List stats ok">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"stats_info">>)
}
}
}
}}},
{"/stats", Metadata, list}.
%%%============================================================================================== %%%==============================================================================================
%% api apply %% api apply
list(get, #{query_string := Qs}) -> list(get, #{query_string := Qs}) ->
case maps:get(<<"aggregate">>, Qs, undefined) of case maps:get(<<"aggregate">>, Qs, undefined) of
<<"true">> -> true ->
{200, emqx_mgmt:get_stats()}; {200, emqx_mgmt:get_stats()};
_ -> _ ->
Data = [maps:from_list(emqx_mgmt:get_stats(Node) ++ [{node, Node}]) || Data = [maps:from_list(emqx_mgmt:get_stats(Node) ++ [{node, Node}]) ||

View File

@ -17,22 +17,40 @@
%% API %% API
-behaviour(minirest_api). -behaviour(minirest_api).
-export([api_spec/0]). -export([ api_spec/0
, paths/0
, schema/1
]).
-export([running_status/2]). -export([running_status/2]).
api_spec() -> api_spec() ->
{[status_api()], []}. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
status_api() -> paths() ->
Path = "/status", ["/status"].
Metadata = #{
get => #{ schema("/status") ->
security => [], #{ 'operationId' => running_status
responses => #{<<"200">> => #{description => <<"running">>}} , get =>
} #{ description => <<"Node running status">>
}, , security => []
{Path, Metadata, running_status}. , responses =>
#{200 =>
#{ description => <<"Node is running">>
, content =>
#{ 'text/plain' =>
#{ schema => #{type => string}
, example => <<"Node emqx@127.0.0.1 is started\nemqx is running">>}
}
}
}
}
}.
%%--------------------------------------------------------------------
%% API Handler funcs
%%--------------------------------------------------------------------
running_status(get, _Params) -> running_status(get, _Params) ->
{InternalStatus, _ProvidedStatus} = init:get_status(), {InternalStatus, _ProvidedStatus} = init:get_status(),
@ -44,5 +62,3 @@ running_status(get, _Params) ->
Status = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), InternalStatus, AppStatus]), Status = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), InternalStatus, AppStatus]),
Body = list_to_binary(Status), Body = list_to_binary(Status),
{200, #{<<"content-type">> => <<"text/plain">>}, Body}. {200, #{<<"content-type">> => <<"text/plain">>}, Body}.

View File

@ -116,10 +116,8 @@ prometheus_data_schema() ->
#{ description => <<"Get Prometheus Data">> #{ description => <<"Get Prometheus Data">>
, content => , content =>
#{ 'application/json' => #{ 'application/json' =>
#{ schema => #{type => object} #{schema => #{type => object}}
, description => <<"Prometheus Data in json">>}
, 'text/plain' => , 'text/plain' =>
#{ schema => #{type => string} #{schema => #{type => string}}
, description => <<"Prometheus Data in text/plain">>}
} }
}. }.