feat: gen hot conf schema.json.
This commit is contained in:
parent
574bbafc9b
commit
334d315838
|
@ -17,6 +17,7 @@
|
|||
|
||||
-compile({no_auto_import, [get/1, get/2]}).
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
-include_lib("hocon/include/hoconsc.hrl").
|
||||
|
||||
-export([add_handler/2, remove_handler/1]).
|
||||
-export([get/1, get/2, get_raw/2, get_all/1]).
|
||||
|
@ -124,14 +125,13 @@ reset(Node, KeyPath, Opts) ->
|
|||
%% @doc Called from build script.
|
||||
-spec dump_schema(file:name_all()) -> ok.
|
||||
dump_schema(Dir) ->
|
||||
SchemaJsonFile = filename:join([Dir, "schema.json"]),
|
||||
JsonMap = hocon_schema_json:gen(emqx_conf_schema),
|
||||
IoData = jsx:encode(JsonMap, [space, {indent, 4}]),
|
||||
io:format(user, "===< Generating: ~s~n", [SchemaJsonFile]),
|
||||
ok = file:write_file(SchemaJsonFile, IoData),
|
||||
SchemaMarkdownFile = filename:join([Dir, "config.md"]),
|
||||
io:format(user, "===< Generating: ~s~n", [SchemaMarkdownFile ]),
|
||||
ok = gen_doc(SchemaMarkdownFile).
|
||||
ok = gen_doc(SchemaMarkdownFile),
|
||||
SchemaJsonFile = filename:join([Dir, "schema.json"]),
|
||||
io:format(user, "===< Generating: ~s~n", [SchemaJsonFile]),
|
||||
ok = gen_hot_conf_schema(SchemaJsonFile),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
|
@ -158,3 +158,159 @@ check_cluster_rpc_result(Result) ->
|
|||
{error, Error} -> %% all MFA return not ok or {ok, term()}.
|
||||
Error
|
||||
end.
|
||||
|
||||
%% Only gen hot_conf schema, not all configuration fields.
|
||||
gen_hot_conf_schema(File) ->
|
||||
{ApiSpec0, Components0} = emqx_dashboard_swagger:spec(emqx_mgmt_api_configs,
|
||||
#{schema_to_spec_func => fun hocon_schema_to_spec/2}),
|
||||
ApiSpec = lists:foldl(fun({Path, Spec, _, _}, Acc) ->
|
||||
NewSpec = maps:fold(fun(Method, #{responses := Responses}, SubAcc) ->
|
||||
case Responses of
|
||||
#{<<"200">> :=
|
||||
#{<<"content">> := #{<<"application/json">> := #{<<"schema">> := Schema}}}} ->
|
||||
SubAcc#{Method => Schema};
|
||||
_ -> SubAcc
|
||||
end
|
||||
end, #{}, Spec),
|
||||
Acc#{list_to_atom(Path) => NewSpec} end, #{}, ApiSpec0),
|
||||
Components = lists:foldl(fun(M, Acc) -> maps:merge(M, Acc) end, #{}, Components0),
|
||||
IoData = jsx:encode(#{
|
||||
info => #{title => <<"EMQX Hot Conf Schema">>, version => <<"0.1.0">>},
|
||||
paths => ApiSpec,
|
||||
components => #{schemas => Components}
|
||||
}, [space, {indent, 4}]),
|
||||
file:write_file(File, IoData).
|
||||
|
||||
-define(INIT_SCHEMA, #{fields => #{}, translations => #{},
|
||||
validations => [], namespace => undefined}).
|
||||
|
||||
-define(TO_REF(_N_, _F_), iolist_to_binary([to_bin(_N_), ".", to_bin(_F_)])).
|
||||
-define(TO_COMPONENTS_SCHEMA(_M_, _F_), iolist_to_binary([<<"#/components/schemas/">>,
|
||||
?TO_REF(emqx_dashboard_swagger:namespace(_M_), _F_)])).
|
||||
-define(TO_COMPONENTS_PARAM(_M_, _F_), iolist_to_binary([<<"#/components/parameters/">>,
|
||||
?TO_REF(emqx_dashboard_swagger:namespace(_M_), _F_)])).
|
||||
|
||||
hocon_schema_to_spec(?R_REF(Module, StructName), _LocalModule) ->
|
||||
{#{<<"$ref">> => ?TO_COMPONENTS_SCHEMA(Module, StructName)},
|
||||
[{Module, StructName}]};
|
||||
hocon_schema_to_spec(?REF(StructName), LocalModule) ->
|
||||
{#{<<"$ref">> => ?TO_COMPONENTS_SCHEMA(LocalModule, StructName)},
|
||||
[{LocalModule, StructName}]};
|
||||
hocon_schema_to_spec(Type, LocalModule) when ?IS_TYPEREFL(Type) ->
|
||||
{typename_to_spec(typerefl:name(Type), LocalModule), []};
|
||||
hocon_schema_to_spec(?ARRAY(Item), LocalModule) ->
|
||||
{Schema, Refs} = hocon_schema_to_spec(Item, LocalModule),
|
||||
{#{type => array, items => Schema}, Refs};
|
||||
hocon_schema_to_spec(?LAZY(Item), LocalModule) ->
|
||||
hocon_schema_to_spec(Item, LocalModule);
|
||||
hocon_schema_to_spec(?ENUM(Items), _LocalModule) ->
|
||||
{#{type => enum, symbols => Items}, []};
|
||||
hocon_schema_to_spec(?MAP(Name, Type), LocalModule) ->
|
||||
{Schema, SubRefs} = hocon_schema_to_spec(Type, LocalModule),
|
||||
{#{<<"type">> => object,
|
||||
<<"properties">> => #{<<"$", (to_bin(Name))/binary>> => Schema}},
|
||||
SubRefs};
|
||||
hocon_schema_to_spec(?UNION(Types), LocalModule) ->
|
||||
{OneOf, Refs} = lists:foldl(fun(Type, {Acc, RefsAcc}) ->
|
||||
{Schema, SubRefs} = hocon_schema_to_spec(Type, LocalModule),
|
||||
{[Schema | Acc], SubRefs ++ RefsAcc}
|
||||
end, {[], []}, Types),
|
||||
{#{<<"oneOf">> => OneOf}, Refs};
|
||||
hocon_schema_to_spec(Atom, _LocalModule) when is_atom(Atom) ->
|
||||
{#{type => enum, symbols => [Atom]}, []}.
|
||||
|
||||
typename_to_spec("user_id_type()", _Mod) -> #{type => enum, symbols => [clientid, username]};
|
||||
typename_to_spec("term()", _Mod) -> #{type => string};
|
||||
typename_to_spec("boolean()", _Mod) -> #{type => boolean};
|
||||
typename_to_spec("binary()", _Mod) -> #{type => string};
|
||||
typename_to_spec("float()", _Mod) -> #{type => number};
|
||||
typename_to_spec("integer()", _Mod) -> #{type => integer};
|
||||
typename_to_spec("non_neg_integer()", _Mod) -> #{type => integer, minimum => 1};
|
||||
typename_to_spec("number()", _Mod) -> #{type => number};
|
||||
typename_to_spec("string()", _Mod) -> #{type => string};
|
||||
typename_to_spec("atom()", _Mod) -> #{type => string};
|
||||
|
||||
typename_to_spec("duration()", _Mod) -> #{type => duration};
|
||||
typename_to_spec("duration_s()", _Mod) -> #{type => duration};
|
||||
typename_to_spec("duration_ms()", _Mod) -> #{type => duration};
|
||||
typename_to_spec("percent()", _Mod) -> #{type => percent};
|
||||
typename_to_spec("file()", _Mod) -> #{type => file};
|
||||
typename_to_spec("ip_port()", _Mod) -> #{type => ip_port};
|
||||
typename_to_spec("url()", _Mod) -> #{type => url};
|
||||
typename_to_spec("bytesize()", _Mod) -> #{type => byteSize};
|
||||
typename_to_spec("wordsize()", _Mod) -> #{type => byteSize};
|
||||
typename_to_spec("qos()", _Mod) -> #{type => enum, symbols => [0, 1, 2]};
|
||||
typename_to_spec("comma_separated_list()", _Mod) -> #{type => comma_separated_string};
|
||||
typename_to_spec("comma_separated_atoms()", _Mod) -> #{type => comma_separated_string};
|
||||
typename_to_spec("pool_type()", _Mod) -> #{type => enum, symbols => [random, hash]};
|
||||
typename_to_spec("log_level()", _Mod) ->
|
||||
#{type => enum, symbols => [debug, info, notice, warning, error, critical, alert, emergency, all]};
|
||||
typename_to_spec("rate()", _Mod) -> #{type => string};
|
||||
typename_to_spec("capacity()", _Mod) -> #{type => string};
|
||||
typename_to_spec("burst_rate()", _Mod) -> #{type => string};
|
||||
typename_to_spec("failure_strategy()", _Mod) -> #{type => enum, symbols => [force, drop, throw]};
|
||||
typename_to_spec("initial()", _Mod) -> #{type => string};
|
||||
typename_to_spec(Name, Mod) ->
|
||||
Spec = range(Name),
|
||||
Spec1 = remote_module_type(Spec, Name, Mod),
|
||||
Spec2 = typerefl_array(Spec1, Name, Mod),
|
||||
Spec3 = integer(Spec2, Name),
|
||||
default_type(Spec3).
|
||||
|
||||
default_type(nomatch) -> #{type => string};
|
||||
default_type(Type) -> Type.
|
||||
|
||||
range(Name) ->
|
||||
case string:split(Name, "..") of
|
||||
[MinStr, MaxStr] -> %% 1..10 1..inf -inf..10
|
||||
Schema = #{type => integer},
|
||||
Schema1 = add_integer_prop(Schema, minimum, MinStr),
|
||||
add_integer_prop(Schema1, maximum, MaxStr);
|
||||
_ -> nomatch
|
||||
end.
|
||||
|
||||
%% Module:Type
|
||||
remote_module_type(nomatch, Name, Mod) ->
|
||||
case string:split(Name, ":") of
|
||||
[_Module, Type] -> typename_to_spec(Type, Mod);
|
||||
_ -> nomatch
|
||||
end;
|
||||
remote_module_type(Spec, _Name, _Mod) -> Spec.
|
||||
|
||||
%% [string()] or [integer()] or [xxx].
|
||||
typerefl_array(nomatch, Name, Mod) ->
|
||||
case string:trim(Name, leading, "[") of
|
||||
Name -> nomatch;
|
||||
Name1 ->
|
||||
case string:trim(Name1, trailing, "]") of
|
||||
Name1 -> notmatch;
|
||||
Name2 ->
|
||||
Schema = typename_to_spec(Name2, Mod),
|
||||
#{type => array, items => Schema}
|
||||
end
|
||||
end;
|
||||
typerefl_array(Spec, _Name, _Mod) -> Spec.
|
||||
|
||||
%% integer(1)
|
||||
integer(nomatch, Name) ->
|
||||
case string:to_integer(Name) of
|
||||
{Int, []} -> #{type => enum, symbols => [Int], default => Int};
|
||||
_ -> nomatch
|
||||
end;
|
||||
integer(Spec, _Name) -> Spec.
|
||||
|
||||
add_integer_prop(Schema, Key, Value) ->
|
||||
case string:to_integer(Value) of
|
||||
{error, no_integer} -> Schema;
|
||||
{Int, []}when Key =:= minimum -> Schema#{Key => Int};
|
||||
{Int, []} -> Schema#{Key => Int}
|
||||
end.
|
||||
|
||||
to_bin(List) when is_list(List) ->
|
||||
case io_lib:printable_list(List) of
|
||||
true -> unicode:characters_to_binary(List);
|
||||
false -> List
|
||||
end;
|
||||
to_bin(Boolean) when is_boolean(Boolean) -> Boolean;
|
||||
to_bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8);
|
||||
to_bin(X) -> X.
|
||||
|
|
|
@ -21,21 +21,21 @@
|
|||
|
||||
%% API
|
||||
-export([spec/1, spec/2]).
|
||||
-export([namespace/0, fields/1]).
|
||||
-export([namespace/0, namespace/1, fields/1]).
|
||||
-export([schema_with_example/2, schema_with_examples/2]).
|
||||
-export([error_codes/1, error_codes/2]).
|
||||
|
||||
-export([filter_check_request/2, filter_check_request_and_translate_body/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([ parse_spec_ref/2
|
||||
, components/1
|
||||
-export([ parse_spec_ref/3
|
||||
, components/2
|
||||
]).
|
||||
-endif.
|
||||
|
||||
-define(METHODS, [get, post, put, head, delete, patch, options, trace]).
|
||||
|
||||
-define(DEFAULT_FIELDS, [example, allowReserved, style, format,
|
||||
-define(DEFAULT_FIELDS, [example, allowReserved, style, format, readOnly,
|
||||
explode, maxLength, allowEmptyValue, deprecated, minimum, maximum]).
|
||||
|
||||
-define(INIT_SCHEMA, #{fields => #{}, translations => #{},
|
||||
|
@ -79,12 +79,12 @@ spec(Module, Options) ->
|
|||
Paths = apply(Module, paths, []),
|
||||
{ApiSpec, AllRefs} =
|
||||
lists:foldl(fun(Path, {AllAcc, AllRefsAcc}) ->
|
||||
{OperationId, Specs, Refs} = parse_spec_ref(Module, Path),
|
||||
{OperationId, Specs, Refs} = parse_spec_ref(Module, Path, Options),
|
||||
CheckSchema = support_check_schema(Options),
|
||||
{[{filename:join("/", Path), Specs, OperationId, CheckSchema} | AllAcc],
|
||||
Refs ++ AllRefsAcc}
|
||||
end, {[], []}, Paths),
|
||||
{ApiSpec, components(lists:usort(AllRefs))}.
|
||||
{ApiSpec, components(lists:usort(AllRefs), Options)}.
|
||||
|
||||
-spec(namespace() -> hocon_schema:name()).
|
||||
namespace() -> "public".
|
||||
|
@ -162,7 +162,7 @@ support_check_schema(#{check_schema := Filter}) when is_function(Filter, 2) ->
|
|||
support_check_schema(_) ->
|
||||
#{filter => undefined}.
|
||||
|
||||
parse_spec_ref(Module, Path) ->
|
||||
parse_spec_ref(Module, Path, Options) ->
|
||||
Schema =
|
||||
try
|
||||
erlang:apply(Module, schema, [Path])
|
||||
|
@ -172,7 +172,7 @@ parse_spec_ref(Module, Path) ->
|
|||
{Specs, Refs} = maps:fold(fun(Method, Meta, {Acc, RefsAcc}) ->
|
||||
(not lists:member(Method, ?METHODS))
|
||||
andalso throw({error, #{module => Module, path => Path, method => Method}}),
|
||||
{Spec, SubRefs} = meta_to_spec(Meta, Module),
|
||||
{Spec, SubRefs} = meta_to_spec(Meta, Module, Options),
|
||||
{Acc#{Method => Spec}, SubRefs ++ RefsAcc}
|
||||
end, {#{}, []},
|
||||
maps:without(['operationId'], Schema)),
|
||||
|
@ -239,10 +239,10 @@ check_request_body(#{body := Body}, Spec, _Module, _CheckFun, false)when is_map(
|
|||
Body.
|
||||
|
||||
%% tags, description, summary, security, deprecated
|
||||
meta_to_spec(Meta, Module) ->
|
||||
meta_to_spec(Meta, Module, Options) ->
|
||||
{Params, Refs1} = parameters(maps:get(parameters, Meta, []), Module),
|
||||
{RequestBody, Refs2} = request_body(maps:get('requestBody', Meta, []), Module),
|
||||
{Responses, Refs3} = responses(maps:get(responses, Meta, #{}), Module),
|
||||
{Responses, Refs3} = responses(maps:get(responses, Meta, #{}), Module, Options),
|
||||
{
|
||||
to_spec(Meta, Params, RequestBody, Responses),
|
||||
lists:usort(Refs1 ++ Refs2 ++ Refs3)
|
||||
|
@ -317,28 +317,29 @@ request_body(Schema, Module) ->
|
|||
HoconSchema = hocon_schema:field_schema(Schema, type),
|
||||
SchemaExamples = hocon_schema:field_schema(Schema, examples),
|
||||
{hocon_schema_to_spec(HoconSchema, Module), SchemaExamples};
|
||||
false -> {parse_object(Schema, Module), undefined}
|
||||
false -> {parse_object(Schema, Module, #{}), undefined}
|
||||
end,
|
||||
{#{<<"content">> => content(Props, Examples)},
|
||||
Refs}.
|
||||
|
||||
responses(Responses, Module) ->
|
||||
{Spec, Refs, _} = maps:fold(fun response/3, {#{}, [], Module}, Responses),
|
||||
responses(Responses, Module, Options) ->
|
||||
{Spec, Refs, _, _} = maps:fold(fun response/3, {#{}, [], Module, Options}, Responses),
|
||||
{Spec, Refs}.
|
||||
|
||||
response(Status, Bin, {Acc, RefsAcc, Module}) when is_binary(Bin) ->
|
||||
{Acc#{integer_to_binary(Status) => #{description => Bin}}, RefsAcc, Module};
|
||||
response(Status, Bin, {Acc, RefsAcc, Module, Options}) when is_binary(Bin) ->
|
||||
{Acc#{integer_to_binary(Status) => #{description => Bin}}, RefsAcc, Module, Options};
|
||||
%% Support swagger raw object(file download).
|
||||
%% TODO: multi type response(i.e. Support both 'application/json' and 'plain/text')
|
||||
response(Status, #{content := _} = Content, {Acc, RefsAcc, Module}) ->
|
||||
{Acc#{integer_to_binary(Status) => Content}, RefsAcc, Module};
|
||||
response(Status, ?REF(StructName), {Acc, RefsAcc, Module}) ->
|
||||
response(Status, ?R_REF(Module, StructName), {Acc, RefsAcc, Module});
|
||||
response(Status, ?R_REF(_Mod, _Name) = RRef, {Acc, RefsAcc, Module}) ->
|
||||
{Spec, Refs} = hocon_schema_to_spec(RRef, Module),
|
||||
response(Status, #{content := _} = Content, {Acc, RefsAcc, Module, Options}) ->
|
||||
{Acc#{integer_to_binary(Status) => Content}, RefsAcc, Module, Options};
|
||||
response(Status, ?REF(StructName), {Acc, RefsAcc, Module, Options}) ->
|
||||
response(Status, ?R_REF(Module, StructName), {Acc, RefsAcc, Module, Options});
|
||||
response(Status, ?R_REF(_Mod, _Name) = RRef, {Acc, RefsAcc, Module, Options}) ->
|
||||
SchemaToSpec = schema_to_spec_func(Options),
|
||||
{Spec, Refs} = SchemaToSpec(RRef, Module),
|
||||
Content = content(Spec),
|
||||
{Acc#{integer_to_binary(Status) => #{<<"content">> => Content}}, Refs ++ RefsAcc, Module};
|
||||
response(Status, Schema, {Acc, RefsAcc, Module}) ->
|
||||
{Acc#{integer_to_binary(Status) => #{<<"content">> => Content}}, Refs ++ RefsAcc, Module, Options};
|
||||
response(Status, Schema, {Acc, RefsAcc, Module, Options}) ->
|
||||
case hoconsc:is_schema(Schema) of
|
||||
true ->
|
||||
Hocon = hocon_schema:field_schema(Schema, type),
|
||||
|
@ -348,33 +349,34 @@ response(Status, Schema, {Acc, RefsAcc, Module}) ->
|
|||
Content = content(Spec, Examples),
|
||||
{
|
||||
Acc#{integer_to_binary(Status) => Init#{<<"content">> => Content}},
|
||||
Refs ++ RefsAcc, Module
|
||||
Refs ++ RefsAcc, Module, Options
|
||||
};
|
||||
false ->
|
||||
{Props, Refs} = parse_object(Schema, Module),
|
||||
Content = #{<<"content">> => content(Props)},
|
||||
{Acc#{integer_to_binary(Status) => Content}, Refs ++ RefsAcc, Module}
|
||||
{Props, Refs} = parse_object(Schema, Module, Options),
|
||||
Init = trans_desc(#{}, Schema),
|
||||
Content = Init#{<<"content">> => content(Props)},
|
||||
{Acc#{integer_to_binary(Status) => Content}, Refs ++ RefsAcc, Module, Options}
|
||||
end.
|
||||
|
||||
components(Refs) ->
|
||||
components(Refs, Options) ->
|
||||
lists:sort(maps:fold(fun(K, V, Acc) -> [#{K => V} | Acc] end, [],
|
||||
components(Refs, #{}, []))).
|
||||
components(Options, Refs, #{}, []))).
|
||||
|
||||
components([], SpecAcc, []) -> SpecAcc;
|
||||
components([], SpecAcc, SubRefAcc) -> components(SubRefAcc, SpecAcc, []);
|
||||
components([{Module, Field} | Refs], SpecAcc, SubRefsAcc) ->
|
||||
components(_Options, [], SpecAcc, []) -> SpecAcc;
|
||||
components(Options, [], SpecAcc, SubRefAcc) -> components(Options, SubRefAcc, SpecAcc, []);
|
||||
components(Options, [{Module, Field} | Refs], SpecAcc, SubRefsAcc) ->
|
||||
Props = hocon_schema_fields(Module, Field),
|
||||
Namespace = namespace(Module),
|
||||
{Object, SubRefs} = parse_object(Props, Module),
|
||||
{Object, SubRefs} = parse_object(Props, Module, Options),
|
||||
NewSpecAcc = SpecAcc#{?TO_REF(Namespace, Field) => Object},
|
||||
components(Refs, NewSpecAcc, SubRefs ++ SubRefsAcc);
|
||||
components(Options, Refs, NewSpecAcc, SubRefs ++ SubRefsAcc);
|
||||
%% parameters in ref only have one value, not array
|
||||
components([{Module, Field, parameter} | Refs], SpecAcc, SubRefsAcc) ->
|
||||
components(Options, [{Module, Field, parameter} | Refs], SpecAcc, SubRefsAcc) ->
|
||||
Props = hocon_schema_fields(Module, Field),
|
||||
{[Param], SubRefs} = parameters(Props, Module),
|
||||
Namespace = namespace(Module),
|
||||
NewSpecAcc = SpecAcc#{?TO_REF(Namespace, Field) => Param},
|
||||
components(Refs, NewSpecAcc, SubRefs ++ SubRefsAcc).
|
||||
components(Options, Refs, NewSpecAcc, SubRefs ++ SubRefsAcc).
|
||||
|
||||
hocon_schema_fields(Module, StructName) ->
|
||||
case apply(Module, fields, [StructName]) of
|
||||
|
@ -546,7 +548,7 @@ to_bin(Boolean) when is_boolean(Boolean) -> Boolean;
|
|||
to_bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8);
|
||||
to_bin(X) -> X.
|
||||
|
||||
parse_object(PropList = [_ | _], Module) when is_list(PropList) ->
|
||||
parse_object(PropList = [_ | _], Module, Options) when is_list(PropList) ->
|
||||
{Props, Required, Refs} =
|
||||
lists:foldl(fun({Name, Hocon}, {Acc, RequiredAcc, RefsAcc}) ->
|
||||
NameBin = to_bin(Name),
|
||||
|
@ -555,7 +557,8 @@ parse_object(PropList = [_ | _], Module) when is_list(PropList) ->
|
|||
HoconType = hocon_schema:field_schema(Hocon, type),
|
||||
Init0 = init_prop([default | ?DEFAULT_FIELDS], #{}, Hocon),
|
||||
Init = trans_desc(Init0, Hocon),
|
||||
{Prop, Refs1} = hocon_schema_to_spec(HoconType, Module),
|
||||
SchemaToSpec = schema_to_spec_func(Options),
|
||||
{Prop, Refs1} = SchemaToSpec(HoconType, Module),
|
||||
NewRequiredAcc =
|
||||
case is_required(Hocon) of
|
||||
true -> [NameBin | RequiredAcc];
|
||||
|
@ -563,7 +566,7 @@ parse_object(PropList = [_ | _], Module) when is_list(PropList) ->
|
|||
end,
|
||||
{[{NameBin, maps:merge(Prop, Init)} | Acc], NewRequiredAcc, Refs1 ++ RefsAcc};
|
||||
false ->
|
||||
{SubObject, SubRefs} = parse_object(Hocon, Module),
|
||||
{SubObject, SubRefs} = parse_object(Hocon, Module, Options),
|
||||
{[{NameBin, SubObject} | Acc], RequiredAcc, SubRefs ++ RefsAcc}
|
||||
end
|
||||
end, {[], [], []}, PropList),
|
||||
|
@ -572,10 +575,10 @@ parse_object(PropList = [_ | _], Module) when is_list(PropList) ->
|
|||
[] -> {Object, Refs};
|
||||
_ -> {maps:put(required, Required, Object), Refs}
|
||||
end;
|
||||
parse_object(Other, Module) ->
|
||||
parse_object(Other, Module, Options) ->
|
||||
erlang:throw({error,
|
||||
#{msg => <<"Object only supports not empty proplists">>,
|
||||
args => Other, module => Module}}).
|
||||
args => Other, module => Module, options => Options}}).
|
||||
|
||||
is_required(Hocon) ->
|
||||
hocon_schema:field_schema(Hocon, required) =:= true orelse
|
||||
|
@ -592,3 +595,6 @@ content(ApiSpec, Examples) when is_map(Examples) ->
|
|||
to_ref(Mod, StructName, Acc, RefsAcc) ->
|
||||
Ref = #{<<"$ref">> => ?TO_COMPONENTS_PARAM(Mod, StructName)},
|
||||
{[Ref | Acc], [{Mod, StructName, parameter} | RefsAcc]}.
|
||||
|
||||
schema_to_spec_func(Options) ->
|
||||
maps:get(schema_to_spec_func, Options, fun hocon_schema_to_spec/2).
|
||||
|
|
|
@ -50,8 +50,8 @@ t_ref(_Config) ->
|
|||
LocalPath = "/test/in/ref/local",
|
||||
Path = "/test/in/ref",
|
||||
Expect = [#{<<"$ref">> => <<"#/components/parameters/emqx_swagger_parameter_SUITE.page">>}],
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, LocalPath),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, LocalPath, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Params = maps:get(parameters, maps:get(post, Spec)),
|
||||
?assertEqual(Expect, Params),
|
||||
|
@ -64,7 +64,7 @@ t_public_ref(_Config) ->
|
|||
#{<<"$ref">> => <<"#/components/parameters/public.page">>},
|
||||
#{<<"$ref">> => <<"#/components/parameters/public.limit">>}
|
||||
],
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Params = maps:get(parameters, maps:get(post, Spec)),
|
||||
?assertEqual(Expect, Params),
|
||||
|
@ -80,7 +80,7 @@ t_public_ref(_Config) ->
|
|||
#{<<"public.page">> => #{description => <<"Page number of the results to fetch.">>,
|
||||
example => 1,in => query,name => page,
|
||||
schema => #{default => 1,example => 100,type => integer}}}],
|
||||
?assertEqual(ExpectRefs, emqx_dashboard_swagger:components(Refs)),
|
||||
?assertEqual(ExpectRefs, emqx_dashboard_swagger:components(Refs,#{})),
|
||||
ok.
|
||||
|
||||
t_in_mix(_Config) ->
|
||||
|
@ -110,7 +110,7 @@ t_in_mix(_Config) ->
|
|||
|
||||
t_without_in(_Config) ->
|
||||
?assertThrow({error, <<"missing in:path/query field in parameters">>},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, "/test/without/in")),
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, "/test/without/in", #{})),
|
||||
ok.
|
||||
|
||||
t_require(_Config) ->
|
||||
|
@ -133,10 +133,10 @@ t_nullable(_Config) ->
|
|||
t_method(_Config) ->
|
||||
PathOk = "/method/ok",
|
||||
PathError = "/method/error",
|
||||
{test, Spec, []} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathOk),
|
||||
{test, Spec, []} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathOk, #{}),
|
||||
?assertEqual(lists:sort(?METHODS), lists:sort(maps:keys(Spec))),
|
||||
?assertThrow({error, #{module := ?MODULE, path := PathError, method := bar}},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathError)),
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, PathError, #{})),
|
||||
ok.
|
||||
|
||||
t_in_path_trans(_Config) ->
|
||||
|
@ -242,7 +242,7 @@ assert_all_filters_equal(Spec, Filter) ->
|
|||
Spec).
|
||||
|
||||
validate(Path, ExpectParams) ->
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Params = maps:get(parameters, maps:get(post, Spec)),
|
||||
?assertEqual(ExpectParams, Params),
|
||||
|
|
|
@ -151,7 +151,7 @@ t_nest_ref(_Config) ->
|
|||
t_none_ref(_Config) ->
|
||||
Path = "/ref/none",
|
||||
?assertThrow({error, #{mfa := {?MODULE, schema, [Path]}}},
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path)),
|
||||
emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{})),
|
||||
ok.
|
||||
|
||||
t_sub_fields(_Config) ->
|
||||
|
@ -472,11 +472,11 @@ t_object_trans_error(_Config) ->
|
|||
ok.
|
||||
|
||||
validate(Path, ExpectSpec, ExpectRefs) ->
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
?assertEqual(ExpectSpec, Spec),
|
||||
?assertEqual(ExpectRefs, Refs),
|
||||
{Spec, emqx_dashboard_swagger:components(Refs)}.
|
||||
{Spec, emqx_dashboard_swagger:components(Refs, #{})}.
|
||||
|
||||
|
||||
filter(ApiSpec, Path) ->
|
||||
|
|
|
@ -69,7 +69,7 @@ t_error(_Config) ->
|
|||
{<<"message">>, #{description => <<"Details description of the error.">>,
|
||||
example => <<"Error code to troubleshoot problems.">>, type => string}}]
|
||||
}}}},
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Response = maps:get(responses, maps:get(get, Spec)),
|
||||
?assertEqual(Error400, maps:get(<<"400">>, Response)),
|
||||
|
@ -197,7 +197,7 @@ t_complicated_type(_Config) ->
|
|||
{<<"fix_integer">>, #{default => 100, enum => [100], example => 100,type => integer}}
|
||||
],
|
||||
<<"type">> => object}}}},
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Response = maps:get(responses, maps:get(post, Spec)),
|
||||
?assertEqual(Object, maps:get(<<"200">>, Response)),
|
||||
|
@ -379,14 +379,14 @@ schema("/fields/sub") ->
|
|||
to_schema(hoconsc:ref(sub_fields)).
|
||||
|
||||
validate(Path, ExpectObject, ExpectRefs) ->
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
||||
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
|
||||
?assertEqual(test, OperationId),
|
||||
Response = maps:get(responses, maps:get(post, Spec)),
|
||||
?assertEqual(ExpectObject, maps:get(<<"200">>, Response)),
|
||||
?assertEqual(ExpectObject, maps:get(<<"201">>, Response)),
|
||||
?assertEqual(#{}, maps:without([<<"201">>, <<"200">>], Response)),
|
||||
?assertEqual(ExpectRefs, Refs),
|
||||
{Spec, emqx_dashboard_swagger:components(Refs)}.
|
||||
{Spec, emqx_dashboard_swagger:components(Refs, #{})}.
|
||||
|
||||
to_schema(Object) ->
|
||||
#{
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
-include_lib("hocon/include/hoconsc.hrl").
|
||||
-behaviour(minirest_api).
|
||||
|
||||
-export([api_spec/0]).
|
||||
-export([api_spec/0, namespace/0]).
|
||||
-export([paths/0, schema/1, fields/1]).
|
||||
|
||||
-export([config/3, config_reset/3, configs/3, get_full_config/0]).
|
||||
|
@ -29,15 +29,31 @@
|
|||
-define(PREFIX, "/configs/").
|
||||
-define(PREFIX_RESET, "/configs_reset/").
|
||||
-define(ERR_MSG(MSG), list_to_binary(io_lib:format("~p", [MSG]))).
|
||||
-define(EXCLUDES, [listeners, node, cluster, gateway, rule_engine]).
|
||||
-define(EXCLUDES, [
|
||||
exhook,
|
||||
gateway,
|
||||
plugins,
|
||||
bridges,
|
||||
"rule_engine",
|
||||
"authorization",
|
||||
"authentication",
|
||||
"rpc",
|
||||
"db",
|
||||
"connectors",
|
||||
"slow_subs",
|
||||
"psk_authentication"
|
||||
]).
|
||||
|
||||
api_spec() ->
|
||||
emqx_dashboard_swagger:spec(?MODULE).
|
||||
|
||||
namespace() -> "configuration".
|
||||
|
||||
paths() ->
|
||||
["/configs", "/configs_reset/:rootname"] ++
|
||||
lists:map(fun({Name, _Type}) -> ?PREFIX ++ to_list(Name) end, config_list(?EXCLUDES)).
|
||||
|
||||
|
||||
schema("/configs") ->
|
||||
#{
|
||||
'operationId' => configs,
|
||||
|
@ -189,7 +205,7 @@ conf_path_from_querystr(Req) ->
|
|||
|
||||
config_list(Exclude) ->
|
||||
Roots = emqx_conf_schema:roots(),
|
||||
lists:foldl(fun(Key, Acc) -> lists:delete(Key, Acc) end, Roots, Exclude).
|
||||
lists:foldl(fun(Key, Acc) -> lists:keydelete(Key, 1, Acc) end, Roots, Exclude).
|
||||
|
||||
to_list(L) when is_list(L) -> L;
|
||||
to_list(Atom) when is_atom(Atom) -> atom_to_list(Atom).
|
||||
|
|
Loading…
Reference in New Issue