feat: generate example.conf from schemas
This commit is contained in:
parent
8519b161dc
commit
9ec804ae03
|
@ -105,7 +105,18 @@ fields(limiter_opts) ->
|
||||||
{bucket,
|
{bucket,
|
||||||
sc(
|
sc(
|
||||||
map("bucket_name", ref(bucket_opts)),
|
map("bucket_name", ref(bucket_opts)),
|
||||||
#{desc => ?DESC(bucket_cfg), default => #{<<"default">> => #{}}}
|
#{
|
||||||
|
desc => ?DESC(bucket_cfg),
|
||||||
|
default => #{<<"default">> => #{}},
|
||||||
|
examples => #{
|
||||||
|
<<"mybucket-name">> => #{
|
||||||
|
<<"rate">> => <<"infinity">>,
|
||||||
|
<<"capcity">> => <<"infinity">>,
|
||||||
|
<<"initial">> => <<"100">>,
|
||||||
|
<<"per_client">> => #{<<"rate">> => <<"infinity">>}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
fields(bucket_opts) ->
|
fields(bucket_opts) ->
|
||||||
|
|
|
@ -118,7 +118,7 @@ roots(high) ->
|
||||||
)},
|
)},
|
||||||
{"zones",
|
{"zones",
|
||||||
sc(
|
sc(
|
||||||
map("name", ref("zone")),
|
map("my_zone_name", ref("zone")),
|
||||||
#{desc => ?DESC(zones)}
|
#{desc => ?DESC(zones)}
|
||||||
)},
|
)},
|
||||||
{"mqtt",
|
{"mqtt",
|
||||||
|
@ -744,7 +744,7 @@ fields("listeners") ->
|
||||||
[
|
[
|
||||||
{"tcp",
|
{"tcp",
|
||||||
sc(
|
sc(
|
||||||
map(name, ref("mqtt_tcp_listener")),
|
map(default, ref("mqtt_tcp_listener")),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(fields_listeners_tcp),
|
desc => ?DESC(fields_listeners_tcp),
|
||||||
required => {false, recursively}
|
required => {false, recursively}
|
||||||
|
@ -752,7 +752,7 @@ fields("listeners") ->
|
||||||
)},
|
)},
|
||||||
{"ssl",
|
{"ssl",
|
||||||
sc(
|
sc(
|
||||||
map(name, ref("mqtt_ssl_listener")),
|
map(default, ref("mqtt_ssl_listener")),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(fields_listeners_ssl),
|
desc => ?DESC(fields_listeners_ssl),
|
||||||
required => {false, recursively}
|
required => {false, recursively}
|
||||||
|
@ -760,7 +760,7 @@ fields("listeners") ->
|
||||||
)},
|
)},
|
||||||
{"ws",
|
{"ws",
|
||||||
sc(
|
sc(
|
||||||
map(name, ref("mqtt_ws_listener")),
|
map(default, ref("mqtt_ws_listener")),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(fields_listeners_ws),
|
desc => ?DESC(fields_listeners_ws),
|
||||||
required => {false, recursively}
|
required => {false, recursively}
|
||||||
|
@ -768,7 +768,7 @@ fields("listeners") ->
|
||||||
)},
|
)},
|
||||||
{"wss",
|
{"wss",
|
||||||
sc(
|
sc(
|
||||||
map(name, ref("mqtt_wss_listener")),
|
map(default, ref("mqtt_wss_listener")),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(fields_listeners_wss),
|
desc => ?DESC(fields_listeners_wss),
|
||||||
required => {false, recursively}
|
required => {false, recursively}
|
||||||
|
@ -776,7 +776,7 @@ fields("listeners") ->
|
||||||
)},
|
)},
|
||||||
{"quic",
|
{"quic",
|
||||||
sc(
|
sc(
|
||||||
map(name, ref("mqtt_quic_listener")),
|
map(default, ref("mqtt_quic_listener")),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(fields_listeners_quic),
|
desc => ?DESC(fields_listeners_quic),
|
||||||
required => {false, recursively}
|
required => {false, recursively}
|
||||||
|
@ -1582,7 +1582,7 @@ base_listener() ->
|
||||||
)},
|
)},
|
||||||
{"limiter",
|
{"limiter",
|
||||||
sc(
|
sc(
|
||||||
map("ratelimit's type", emqx_limiter_schema:bucket_name()),
|
map("ratelimit_name", emqx_limiter_schema:bucket_name()),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(base_listener_limiter),
|
desc => ?DESC(base_listener_limiter),
|
||||||
default => #{}
|
default => #{}
|
||||||
|
@ -2183,7 +2183,7 @@ authentication(Type) ->
|
||||||
%% authentication schema is lazy to make it more 'plugable'
|
%% authentication schema is lazy to make it more 'plugable'
|
||||||
%% the type checks are done in emqx_auth application when it boots.
|
%% the type checks are done in emqx_auth application when it boots.
|
||||||
%% and in emqx_authentication_config module for runtime changes.
|
%% and in emqx_authentication_config module for runtime changes.
|
||||||
Default = hoconsc:lazy(hoconsc:union([typerefl:map(), hoconsc:array(typerefl:map())])),
|
Default = hoconsc:lazy(hoconsc:union([hoconsc:array(typerefl:map())])),
|
||||||
%% as the type is lazy, the runtime module injection
|
%% as the type is lazy, the runtime module injection
|
||||||
%% from EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY
|
%% from EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY
|
||||||
%% is for now only affecting document generation.
|
%% is for now only affecting document generation.
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
authorization {
|
authorization {
|
||||||
cache: {
|
|
||||||
enable: true
|
|
||||||
max_size: 32
|
|
||||||
ttl: "60s"
|
|
||||||
}
|
|
||||||
deny_action: ignore
|
deny_action: ignore
|
||||||
no_match: allow
|
no_match: allow
|
||||||
sources: [
|
sources: [
|
||||||
|
|
|
@ -144,7 +144,8 @@ dump_schema(Dir, SchemaModule, I18nFile) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Lang) ->
|
fun(Lang) ->
|
||||||
gen_config_md(Dir, I18nFile, SchemaModule, Lang),
|
gen_config_md(Dir, I18nFile, SchemaModule, Lang),
|
||||||
gen_hot_conf_schema_json(Dir, I18nFile, Lang)
|
gen_hot_conf_schema_json(Dir, I18nFile, Lang),
|
||||||
|
gen_example_conf(Dir, I18nFile, SchemaModule, Lang)
|
||||||
end,
|
end,
|
||||||
[en, zh]
|
[en, zh]
|
||||||
),
|
),
|
||||||
|
@ -173,6 +174,12 @@ gen_config_md(Dir, I18nFile, SchemaModule, Lang0) ->
|
||||||
io:format(user, "===< Generating: ~s~n", [SchemaMdFile]),
|
io:format(user, "===< Generating: ~s~n", [SchemaMdFile]),
|
||||||
ok = gen_doc(SchemaMdFile, SchemaModule, I18nFile, Lang).
|
ok = gen_doc(SchemaMdFile, SchemaModule, I18nFile, Lang).
|
||||||
|
|
||||||
|
gen_example_conf(Dir, I18nFile, SchemaModule, Lang0) ->
|
||||||
|
Lang = atom_to_list(Lang0),
|
||||||
|
SchemaMdFile = filename:join([Dir, "emqx-" ++ Lang ++ ".conf.example"]),
|
||||||
|
io:format(user, "===< Generating: ~s~n", [SchemaMdFile]),
|
||||||
|
ok = gen_example(SchemaMdFile, SchemaModule, I18nFile, Lang).
|
||||||
|
|
||||||
%% @doc return the root schema module.
|
%% @doc return the root schema module.
|
||||||
-spec schema_module() -> module().
|
-spec schema_module() -> module().
|
||||||
schema_module() ->
|
schema_module() ->
|
||||||
|
@ -195,6 +202,11 @@ gen_doc(File, SchemaModule, I18nFile, Lang) ->
|
||||||
Doc = hocon_schema_md:gen(SchemaModule, Opts),
|
Doc = hocon_schema_md:gen(SchemaModule, Opts),
|
||||||
file:write_file(File, Doc).
|
file:write_file(File, Doc).
|
||||||
|
|
||||||
|
gen_example(File, SchemaModule, I18nFile, Lang) ->
|
||||||
|
Opts = #{title => <<"Title">>, body => <<"Body">>, desc_file => I18nFile, lang => Lang},
|
||||||
|
Example = hocon_schema_example:gen(SchemaModule, Opts),
|
||||||
|
file:write_file(File, Example).
|
||||||
|
|
||||||
check_cluster_rpc_result(Result) ->
|
check_cluster_rpc_result(Result) ->
|
||||||
case Result of
|
case Result of
|
||||||
{ok, _TnxId, Res} ->
|
{ok, _TnxId, Res} ->
|
||||||
|
|
|
@ -76,22 +76,22 @@ roots() ->
|
||||||
[
|
[
|
||||||
{"node",
|
{"node",
|
||||||
sc(
|
sc(
|
||||||
ref("node"),
|
?R_REF("node"),
|
||||||
#{translate_to => ["emqx"]}
|
#{translate_to => ["emqx"]}
|
||||||
)},
|
)},
|
||||||
{"cluster",
|
{"cluster",
|
||||||
sc(
|
sc(
|
||||||
ref("cluster"),
|
?R_REF("cluster"),
|
||||||
#{translate_to => ["ekka"]}
|
#{translate_to => ["ekka"]}
|
||||||
)},
|
)},
|
||||||
{"log",
|
{"log",
|
||||||
sc(
|
sc(
|
||||||
ref("log"),
|
?R_REF("log"),
|
||||||
#{translate_to => ["kernel"]}
|
#{translate_to => ["kernel"]}
|
||||||
)},
|
)},
|
||||||
{"rpc",
|
{"rpc",
|
||||||
sc(
|
sc(
|
||||||
ref("rpc"),
|
?R_REF("rpc"),
|
||||||
#{translate_to => ["gen_rpc"]}
|
#{translate_to => ["gen_rpc"]}
|
||||||
)}
|
)}
|
||||||
] ++
|
] ++
|
||||||
|
@ -166,27 +166,27 @@ fields("cluster") ->
|
||||||
)},
|
)},
|
||||||
{"static",
|
{"static",
|
||||||
sc(
|
sc(
|
||||||
ref(cluster_static),
|
?R_REF(cluster_static),
|
||||||
#{}
|
#{}
|
||||||
)},
|
)},
|
||||||
{"mcast",
|
{"mcast",
|
||||||
sc(
|
sc(
|
||||||
ref(cluster_mcast),
|
?R_REF(cluster_mcast),
|
||||||
#{}
|
#{}
|
||||||
)},
|
)},
|
||||||
{"dns",
|
{"dns",
|
||||||
sc(
|
sc(
|
||||||
ref(cluster_dns),
|
?R_REF(cluster_dns),
|
||||||
#{}
|
#{}
|
||||||
)},
|
)},
|
||||||
{"etcd",
|
{"etcd",
|
||||||
sc(
|
sc(
|
||||||
ref(cluster_etcd),
|
?R_REF(cluster_etcd),
|
||||||
#{}
|
#{}
|
||||||
)},
|
)},
|
||||||
{"k8s",
|
{"k8s",
|
||||||
sc(
|
sc(
|
||||||
ref(cluster_k8s),
|
?R_REF(cluster_k8s),
|
||||||
#{}
|
#{}
|
||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
|
@ -328,7 +328,7 @@ fields(cluster_etcd) ->
|
||||||
)},
|
)},
|
||||||
{"ssl",
|
{"ssl",
|
||||||
sc(
|
sc(
|
||||||
hoconsc:ref(emqx_schema, "ssl_client_opts"),
|
?R_REF(emqx_schema, "ssl_client_opts"),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(cluster_etcd_ssl),
|
desc => ?DESC(cluster_etcd_ssl),
|
||||||
'readOnly' => true
|
'readOnly' => true
|
||||||
|
@ -512,7 +512,7 @@ fields("node") ->
|
||||||
)},
|
)},
|
||||||
{"cluster_call",
|
{"cluster_call",
|
||||||
sc(
|
sc(
|
||||||
ref("cluster_call"),
|
?R_REF("cluster_call"),
|
||||||
#{'readOnly' => true}
|
#{'readOnly' => true}
|
||||||
)},
|
)},
|
||||||
{"db_backend",
|
{"db_backend",
|
||||||
|
@ -783,10 +783,10 @@ fields("rpc") ->
|
||||||
];
|
];
|
||||||
fields("log") ->
|
fields("log") ->
|
||||||
[
|
[
|
||||||
{"console_handler", ref("console_handler")},
|
{"console_handler", ?R_REF("console_handler")},
|
||||||
{"file_handlers",
|
{"file_handlers",
|
||||||
sc(
|
sc(
|
||||||
map(name, ref("log_file_handler")),
|
map(name, ?R_REF("log_file_handler")),
|
||||||
#{desc => ?DESC("log_file_handlers")}
|
#{desc => ?DESC("log_file_handlers")}
|
||||||
)},
|
)},
|
||||||
{"error_logger",
|
{"error_logger",
|
||||||
|
@ -814,7 +814,7 @@ fields("log_file_handler") ->
|
||||||
)},
|
)},
|
||||||
{"rotation",
|
{"rotation",
|
||||||
sc(
|
sc(
|
||||||
ref("log_rotation"),
|
?R_REF("log_rotation"),
|
||||||
#{}
|
#{}
|
||||||
)},
|
)},
|
||||||
{"max_size",
|
{"max_size",
|
||||||
|
@ -1137,8 +1137,8 @@ log_handler_common_confs(Enable) ->
|
||||||
desc => ?DESC("common_handler_flush_qlen")
|
desc => ?DESC("common_handler_flush_qlen")
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
{"overload_kill", sc(ref("log_overload_kill"), #{})},
|
{"overload_kill", sc(?R_REF("log_overload_kill"), #{})},
|
||||||
{"burst_limit", sc(ref("log_burst_limit"), #{})},
|
{"burst_limit", sc(?R_REF("log_burst_limit"), #{})},
|
||||||
{"supervisor_reports",
|
{"supervisor_reports",
|
||||||
sc(
|
sc(
|
||||||
hoconsc:enum([error, progress]),
|
hoconsc:enum([error, progress]),
|
||||||
|
@ -1251,8 +1251,6 @@ sc(Type, Meta) -> hoconsc:mk(Type, Meta).
|
||||||
|
|
||||||
map(Name, Type) -> hoconsc:map(Name, Type).
|
map(Name, Type) -> hoconsc:map(Name, Type).
|
||||||
|
|
||||||
ref(Field) -> hoconsc:ref(?MODULE, Field).
|
|
||||||
|
|
||||||
options(static, Conf) ->
|
options(static, Conf) ->
|
||||||
[{seeds, conf_get("cluster.static.seeds", Conf, [])}];
|
[{seeds, conf_get("cluster.static.seeds", Conf, [])}];
|
||||||
options(mcast, Conf) ->
|
options(mcast, Conf) ->
|
||||||
|
@ -1321,7 +1319,7 @@ emqx_schema_high_prio_roots() ->
|
||||||
Authz =
|
Authz =
|
||||||
{"authorization",
|
{"authorization",
|
||||||
sc(
|
sc(
|
||||||
hoconsc:ref(?MODULE, "authorization"),
|
?R_REF("authorization"),
|
||||||
#{desc => ?DESC(authorization)}
|
#{desc => ?DESC(authorization)}
|
||||||
)},
|
)},
|
||||||
lists:keyreplace("authorization", 1, Roots, Authz).
|
lists:keyreplace("authorization", 1, Roots, Authz).
|
||||||
|
|
|
@ -0,0 +1,539 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2021-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(hocon_schema_example).
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
|
||||||
|
-export([gen/2]).
|
||||||
|
|
||||||
|
-define(COMMENT, "# ").
|
||||||
|
-define(COMMENT2, "## ").
|
||||||
|
-define(INDENT, " ").
|
||||||
|
-define(NL, io_lib:nl()).
|
||||||
|
-define(DOC, "@doc ").
|
||||||
|
-define(TYPE, "@type ").
|
||||||
|
-define(PATH, "@path ").
|
||||||
|
-define(LINK, "@link ").
|
||||||
|
-define(DEFAULT, "@default ").
|
||||||
|
-define(BIND, ": ").
|
||||||
|
|
||||||
|
gen(Schema, undefined) ->
|
||||||
|
gen(Schema, "# HOCON Example");
|
||||||
|
gen(Schema, Title) when is_list(Title) orelse is_binary(Title) ->
|
||||||
|
gen(Schema, #{title => Title, body => <<>>});
|
||||||
|
gen(Schema, #{title := Title, body := Body} = Opts) ->
|
||||||
|
File = maps:get(desc_file, Opts, undefined),
|
||||||
|
Lang = maps:get(lang, Opts, "en"),
|
||||||
|
[Roots | Fields0] = hocon_schema_json:gen(Schema, #{desc_file => File, lang => Lang}),
|
||||||
|
Fields = lists:foldl(fun(F = #{full_name := Name}, Acc) -> Acc#{Name => F} end, #{}, Fields0),
|
||||||
|
#{fields := RootKeys} = Roots,
|
||||||
|
FmtOpts = #{tid => new_link_cache(), indent => "", comment => false},
|
||||||
|
try
|
||||||
|
Structs = lists:map(
|
||||||
|
fun(Root) ->
|
||||||
|
[
|
||||||
|
fmt_desc(Root, ""),
|
||||||
|
fmt_field(Root, Fields, "", FmtOpts)
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
RootKeys
|
||||||
|
),
|
||||||
|
[
|
||||||
|
?COMMENT2,
|
||||||
|
Title,
|
||||||
|
?NL,
|
||||||
|
?NL,
|
||||||
|
?COMMENT2,
|
||||||
|
Body,
|
||||||
|
?NL,
|
||||||
|
?NL,
|
||||||
|
Structs
|
||||||
|
]
|
||||||
|
after
|
||||||
|
delete_link_cache(FmtOpts)
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt_field(#{type := #{kind := struct, name := SubName}, name := Name} = Field, All, Path0, Opts) ->
|
||||||
|
case maps:find(SubName, All) of
|
||||||
|
{ok, #{fields := SubFields}} ->
|
||||||
|
#{indent := Indent, comment := Comment} = Opts,
|
||||||
|
Opts1 = Opts#{indent => Indent ++ ?INDENT},
|
||||||
|
{PathName, ValName} = resolve_name(Name),
|
||||||
|
Path = [str(PathName) | Path0],
|
||||||
|
SubStructs =
|
||||||
|
case maps:get(examples, Field, #{}) of
|
||||||
|
#{} = Example ->
|
||||||
|
fmt_field_with_example(Path, SubFields, Example, All, Opts1);
|
||||||
|
{union, UnionExamples} ->
|
||||||
|
Examples1 = filter_union_example(UnionExamples, SubFields),
|
||||||
|
fmt_field_with_example(Path, SubFields, Examples1, All, Opts1);
|
||||||
|
{array, ArrayExamples} ->
|
||||||
|
lists:flatmap(
|
||||||
|
fun(SubExample) ->
|
||||||
|
fmt_field_with_example(Path, SubFields, SubExample, All, Opts1)
|
||||||
|
end,
|
||||||
|
ArrayExamples
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
[
|
||||||
|
Indent,
|
||||||
|
comment(Comment),
|
||||||
|
ValName,
|
||||||
|
" {",
|
||||||
|
?NL,
|
||||||
|
lists:join(?NL, SubStructs),
|
||||||
|
Indent,
|
||||||
|
comment(Comment),
|
||||||
|
" }",
|
||||||
|
?NL
|
||||||
|
];
|
||||||
|
Unknown ->
|
||||||
|
throw({error, {Path0, SubName, Unknown}})
|
||||||
|
end;
|
||||||
|
fmt_field(#{type := #{kind := primitive, name := TypeName}} = Field, _All, Path, Opts) ->
|
||||||
|
Name = str(maps:get(name, Field)),
|
||||||
|
Fix = fmt_fix_header(Field, TypeName, [Name | Path], Opts),
|
||||||
|
[Fix, fmt_examples(Name, Field, Opts)];
|
||||||
|
fmt_field(#{type := #{kind := singleton, name := SingleTon}} = Field, _All, Path, Opts) ->
|
||||||
|
Name = str(maps:get(name, Field)),
|
||||||
|
#{indent := Indent, comment := Comment} = Opts,
|
||||||
|
Fix = fmt_fix_header(Field, "singleton", [Name | Path], Opts),
|
||||||
|
[Fix, fmt(Indent, Comment, Name, SingleTon)];
|
||||||
|
fmt_field(#{type := #{kind := enum, symbols := Symbols}} = Field, _All, Path, Opts) ->
|
||||||
|
TypeName = ["enum: ", lists:join(" | ", Symbols)],
|
||||||
|
Name = str(maps:get(name, Field)),
|
||||||
|
Fix = fmt_fix_header(Field, TypeName, [str(Name) | Path], Opts),
|
||||||
|
[Fix, fmt_examples(Name, Field, Opts)];
|
||||||
|
fmt_field(#{type := #{kind := union, members := Members0} = Type} = Field, All, Path0, Opts) ->
|
||||||
|
Name = str(maps:get(name, Field)),
|
||||||
|
Names = lists:map(fun(#{name := N}) -> N end, Members0),
|
||||||
|
Path = [Name | Path0],
|
||||||
|
TypeStr = ["union() ", lists:join(" | ", Names)],
|
||||||
|
Fix = fmt_fix_header(Field, TypeStr, Path, Opts),
|
||||||
|
Link = fmt_union_link(Type, Path, Opts),
|
||||||
|
Fix1 = [Fix, Link],
|
||||||
|
case Link =:= "" andalso need_comment_example(union, Opts, Type, Path) of
|
||||||
|
true ->
|
||||||
|
#{indent := Indent} = Opts,
|
||||||
|
Indent1 = Indent ++ ?INDENT,
|
||||||
|
Opts1 = Opts#{indent => Indent1},
|
||||||
|
case fmt_sub_fields(Opts1, Field, All, Path0) of
|
||||||
|
[] -> fallback_to_example(Field, Fix1, Indent1, Name, Opts1, Indent, "");
|
||||||
|
ValFields -> [Fix1, ValFields, ?NL]
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
[Fix1, ?NL]
|
||||||
|
end;
|
||||||
|
fmt_field(#{type := #{kind := map, name := MapName} = Type} = Field, All, Path0, Opts) ->
|
||||||
|
Name = str(maps:get(name, Field)),
|
||||||
|
#{indent := Indent} = Opts,
|
||||||
|
Path = [Name | Path0],
|
||||||
|
Path1 = ["$" ++ str(MapName) | Path],
|
||||||
|
Fix = fmt_fix_header(Field, "map_struct()", Path, Opts),
|
||||||
|
Link = fmt_map_link(Path1, Type, All, Opts),
|
||||||
|
Fix1 = [Fix, Link],
|
||||||
|
case Link =:= "" andalso need_comment_example(map, Opts, Path1) of
|
||||||
|
true ->
|
||||||
|
Indent1 = Indent ++ ?INDENT,
|
||||||
|
Opts1 = Opts#{indent => Indent1},
|
||||||
|
ValFields = fmt_sub_fields(Opts1, Field, All, Path),
|
||||||
|
[Fix1, Indent1, ?COMMENT, Name, ?BIND, ?NL, ValFields, ?NL];
|
||||||
|
false ->
|
||||||
|
[Fix1, ?NL]
|
||||||
|
end;
|
||||||
|
fmt_field(#{type := #{kind := array} = Type} = Field, All, Path0, Opts) ->
|
||||||
|
#{indent := Indent, comment := Comment} = Opts,
|
||||||
|
Name = str(maps:get(name, Field)),
|
||||||
|
Path = [Name | Path0],
|
||||||
|
Fix = fmt_fix_header(Field, "array()", Path, Opts),
|
||||||
|
Link = fmt_array_link(Type, Path, Opts),
|
||||||
|
Fix1 = [Fix, Link],
|
||||||
|
case Link =:= "" andalso need_comment_example(array, Opts, Type, Path) of
|
||||||
|
true ->
|
||||||
|
Indent1 = Indent ++ ?INDENT,
|
||||||
|
Opts1 = Opts#{indent => Indent1},
|
||||||
|
case fmt_sub_fields(Opts1, Field, All, Path) of
|
||||||
|
[] ->
|
||||||
|
fallback_to_example(Field, Fix1, Indent1, Name, Opts1, Indent, "[]");
|
||||||
|
ValFields ->
|
||||||
|
[
|
||||||
|
Fix1,
|
||||||
|
Indent1,
|
||||||
|
comment(Comment),
|
||||||
|
Name,
|
||||||
|
?BIND,
|
||||||
|
"[",
|
||||||
|
?NL,
|
||||||
|
ValFields,
|
||||||
|
?NL,
|
||||||
|
Indent1,
|
||||||
|
comment(Comment),
|
||||||
|
"]",
|
||||||
|
?NL
|
||||||
|
]
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
[Fix1, ?NL]
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt(Indent, Comment, Name, Value) ->
|
||||||
|
[Indent, comment(Comment), Name, ?BIND, Value, ?NL].
|
||||||
|
|
||||||
|
fallback_to_example(Field, Fix1, Indent1, Name, Opts, Indent, Default) ->
|
||||||
|
case Field of
|
||||||
|
#{examples := Examples} ->
|
||||||
|
[
|
||||||
|
Fix1,
|
||||||
|
Indent1,
|
||||||
|
?COMMENT,
|
||||||
|
Name,
|
||||||
|
?BIND,
|
||||||
|
fmt_example(Examples, Opts#{comment => true}),
|
||||||
|
?NL
|
||||||
|
];
|
||||||
|
_ ->
|
||||||
|
Default2 =
|
||||||
|
case get_default(Field, Opts) of
|
||||||
|
undefined -> Default;
|
||||||
|
Default1 -> Default1
|
||||||
|
end,
|
||||||
|
[Fix1, Indent, ?COMMENT, Name, ?BIND, Default2, ?NL]
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt_field_with_example(Path, SubFields, Examples, All, Opts1) ->
|
||||||
|
lists:map(
|
||||||
|
fun(F) ->
|
||||||
|
#{name := N} = F,
|
||||||
|
case maps:find(N, Examples) of
|
||||||
|
{ok, SubExample} ->
|
||||||
|
fmt_field(F#{examples => SubExample}, All, Path, Opts1);
|
||||||
|
error ->
|
||||||
|
fmt_field(F, All, Path, Opts1)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
SubFields
|
||||||
|
).
|
||||||
|
|
||||||
|
fmt_sub_fields(Opts, Field, All, Path) ->
|
||||||
|
Opts1 = Opts#{comment => true},
|
||||||
|
SubFields = get_sub_fields(Field),
|
||||||
|
[fmt_field(F, All, Path, Opts1) || F <- SubFields].
|
||||||
|
|
||||||
|
get_sub_fields(#{type := #{kind := array, elements := ElemT}, name := Name} = Field) ->
|
||||||
|
case is_simple_type(ElemT) of
|
||||||
|
true ->
|
||||||
|
[];
|
||||||
|
false ->
|
||||||
|
Examples =
|
||||||
|
case get_examples(Name, Field) of
|
||||||
|
undefined -> [];
|
||||||
|
Example0 -> Example0
|
||||||
|
end,
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
name => {"$INDEX", str(Name) ++ ".$INDEX"},
|
||||||
|
type => ElemT,
|
||||||
|
examples => {array, Examples}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end;
|
||||||
|
get_sub_fields(#{type := #{kind := union, members := Members}, name := Name} = Field) ->
|
||||||
|
case is_simple_type(Members) of
|
||||||
|
true ->
|
||||||
|
[];
|
||||||
|
false ->
|
||||||
|
Example =
|
||||||
|
case get_examples(Name, Field) of
|
||||||
|
undefined ->
|
||||||
|
[];
|
||||||
|
[Example0] when is_map(Example0) ->
|
||||||
|
[Value || #{value := Value} <- maps:values(Example0)];
|
||||||
|
%% TODO array
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end,
|
||||||
|
lists:map(
|
||||||
|
fun(M) -> #{name => Name, type => M, examples => {union, Example}} end, Members
|
||||||
|
)
|
||||||
|
end;
|
||||||
|
get_sub_fields(#{type := #{kind := map, values := ValT, name := MapName0}} = Field) ->
|
||||||
|
MapName = "$" ++ str(MapName0),
|
||||||
|
case get_examples(MapName0, Field) of
|
||||||
|
undefined ->
|
||||||
|
[#{name => MapName, type => ValT}];
|
||||||
|
[] ->
|
||||||
|
[#{name => MapName, type => ValT}];
|
||||||
|
Examples ->
|
||||||
|
lists:map(
|
||||||
|
fun(Example) ->
|
||||||
|
[{SubName, SubValue}] = maps:to_list(Example),
|
||||||
|
#{name => {MapName, SubName}, type => ValT, examples => SubValue}
|
||||||
|
end,
|
||||||
|
Examples
|
||||||
|
)
|
||||||
|
end.
|
||||||
|
|
||||||
|
filter_union_example(Examples0, SubFields) ->
|
||||||
|
TargetKeys = lists:sort([binary_to_atom(Name) || #{name := Name} <- SubFields]),
|
||||||
|
Examples =
|
||||||
|
lists:filtermap(
|
||||||
|
fun(Example) ->
|
||||||
|
case lists:all(fun(K) -> lists:member(K, TargetKeys) end, maps:keys(Example)) of
|
||||||
|
true -> {true, ensure_bin_key(Example)};
|
||||||
|
false -> false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Examples0
|
||||||
|
),
|
||||||
|
case Examples of
|
||||||
|
[Example] -> Example;
|
||||||
|
[] -> #{};
|
||||||
|
Other -> throw({error, {find_union_example_failed, Examples, SubFields, Other}})
|
||||||
|
end.
|
||||||
|
|
||||||
|
ensure_bin_key(Map) ->
|
||||||
|
maps:fold(
|
||||||
|
fun
|
||||||
|
(K0, V0 = #{}, Acc) -> Acc#{bin(K0) => ensure_bin_key(V0)};
|
||||||
|
(K0, V, Acc) -> Acc#{bin(K0) => V}
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
Map
|
||||||
|
).
|
||||||
|
|
||||||
|
fmt_desc(#{desc := Desc0}, Indent) ->
|
||||||
|
Target = iolist_to_binary([?NL, Indent, ?COMMENT2]),
|
||||||
|
Desc = string:trim(Desc0, both),
|
||||||
|
replace_nl(Indent, true, Desc, Target);
|
||||||
|
fmt_desc(_, _) ->
|
||||||
|
<<"">>.
|
||||||
|
|
||||||
|
fmt_type(Type, Indent) ->
|
||||||
|
[Indent, ?COMMENT2, ?TYPE, Type, ?NL].
|
||||||
|
|
||||||
|
fmt_path(Path, Indent) -> [Indent, ?COMMENT2, ?PATH, hocon_schema:path(Path), ?NL].
|
||||||
|
|
||||||
|
fmt_fix_header(Field, Type, Path, #{indent := Indent}) ->
|
||||||
|
[
|
||||||
|
fmt_desc(Field, Indent),
|
||||||
|
fmt_path(Path, Indent),
|
||||||
|
fmt_type(Type, Indent),
|
||||||
|
fmt_default(Field, Indent)
|
||||||
|
].
|
||||||
|
|
||||||
|
fmt_map_link(Path0, Type, All, Opts) ->
|
||||||
|
case Type of
|
||||||
|
#{values := #{name := ValueName}} ->
|
||||||
|
fmt_map_link2(Path0, ValueName, All, Opts);
|
||||||
|
#{values := #{members := Members}} ->
|
||||||
|
lists:map(fun(M) -> fmt_map_link(Path0, M, All, Opts) end, Members);
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt_map_link2(Path0, ValueName, All, Opts) ->
|
||||||
|
Paths =
|
||||||
|
case maps:find(ValueName, All) of
|
||||||
|
{ok, #{paths := SubPaths}} -> SubPaths;
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
PathStr = hocon_schema:path(Path0),
|
||||||
|
Path = bin(PathStr),
|
||||||
|
#{indent := Indent} = Opts,
|
||||||
|
case find_link(Opts, {map, PathStr}) of
|
||||||
|
{ok, Link} ->
|
||||||
|
[Indent, ?COMMENT2, ?LINK, Link, ?NL];
|
||||||
|
{error, not_found} ->
|
||||||
|
case lists:member(Path, Paths) of
|
||||||
|
true ->
|
||||||
|
insert_link(Opts, [{{map, binary_to_list(P)}, Path} || P <- Paths, P =/= Path]);
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
""
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt_union_link(Type = #{members := Members}, Path, Opts = #{indent := Indent}) ->
|
||||||
|
case find_link(Opts, {union, Type}) of
|
||||||
|
{ok, Link} ->
|
||||||
|
link(Link, Indent);
|
||||||
|
{error, not_found} ->
|
||||||
|
case is_simple_type(Members) of
|
||||||
|
true -> ok;
|
||||||
|
false -> insert_link(Opts, {{union, Type}, Path})
|
||||||
|
end,
|
||||||
|
""
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt_array_link(Type = #{elements := ElemT}, Path, Opts = #{indent := Indent}) ->
|
||||||
|
case find_link(Opts, {array, Type}) of
|
||||||
|
{ok, Link} ->
|
||||||
|
link(Link, Indent);
|
||||||
|
{error, not_found} ->
|
||||||
|
case is_simple_type(ElemT) of
|
||||||
|
true -> ok;
|
||||||
|
false -> insert_link(Opts, {{array, Type}, Path})
|
||||||
|
end,
|
||||||
|
""
|
||||||
|
end.
|
||||||
|
|
||||||
|
link(Link, Indent) ->
|
||||||
|
[Indent, ?COMMENT2, ?LINK, hocon_schema:path(Link), ?NL].
|
||||||
|
|
||||||
|
is_simple_type(Types) when is_list(Types) ->
|
||||||
|
lists:all(
|
||||||
|
fun(#{kind := Kind}) ->
|
||||||
|
Kind =:= primitive orelse Kind =:= singleton
|
||||||
|
end,
|
||||||
|
Types
|
||||||
|
);
|
||||||
|
is_simple_type(Type) ->
|
||||||
|
is_simple_type([Type]).
|
||||||
|
|
||||||
|
need_comment_example(map, Opts, Path) ->
|
||||||
|
case find_link(Opts, {map, hocon_schema:path(Path)}) of
|
||||||
|
{ok, _} -> false;
|
||||||
|
{error, not_found} -> true
|
||||||
|
end.
|
||||||
|
|
||||||
|
need_comment_example(Type, Opts, Key, Link) when Type =:= union; Type =:= array ->
|
||||||
|
case find_link(Opts, {union, Key}) of
|
||||||
|
{ok, Link} -> true;
|
||||||
|
{error, not_found} -> true;
|
||||||
|
{ok, _} -> false
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_examples(_MapName, #{examples := Examples}) ->
|
||||||
|
ensure_list(Examples);
|
||||||
|
get_examples(MapName, #{default := #{hocon := Hocon}}) ->
|
||||||
|
case hocon:binary(Hocon) of
|
||||||
|
{ok, Default} -> [#{MapName => Default}];
|
||||||
|
{error, _} -> [#{MapName => Hocon}]
|
||||||
|
end;
|
||||||
|
get_examples(_, _) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
fmt_examples(Name, #{examples := {union, Examples}}, Opts) ->
|
||||||
|
fmt_examples(Name, #{examples => Examples}, Opts);
|
||||||
|
fmt_examples(Name, #{examples := Examples}, Opts) ->
|
||||||
|
#{indent := Indent, comment := Comment} = Opts,
|
||||||
|
lists:map(
|
||||||
|
fun(E) ->
|
||||||
|
[Indent, comment(Comment), Name, ?BIND, fmt_example(E, Opts), ?NL]
|
||||||
|
end,
|
||||||
|
ensure_list(Examples)
|
||||||
|
);
|
||||||
|
fmt_examples(Name, Field, Opts = #{indent := Indent, comment := Comment}) ->
|
||||||
|
case get_default(Field, Opts) of
|
||||||
|
undefined -> [Indent, ?COMMENT, Name, ?BIND, ?NL];
|
||||||
|
Default -> fmt(Indent, Comment, Name, Default)
|
||||||
|
end.
|
||||||
|
|
||||||
|
ensure_list(L) when is_list(L) -> L;
|
||||||
|
ensure_list(T) -> [T].
|
||||||
|
|
||||||
|
fmt_example(Value, #{indent := Indent0, comment := Comment}) ->
|
||||||
|
case hocon_pp:do(Value, #{newline => "", embedded => true}) of
|
||||||
|
[OneLine] ->
|
||||||
|
[try_to_remove_quote(OneLine)];
|
||||||
|
Lines ->
|
||||||
|
Indent = Indent0 ++ ?INDENT,
|
||||||
|
Target = iolist_to_binary([?NL, Indent, comment(Comment)]),
|
||||||
|
[
|
||||||
|
?NL,
|
||||||
|
Indent,
|
||||||
|
comment(Comment),
|
||||||
|
binary:replace(bin(Lines), [<<"\n">>], Target, [global]),
|
||||||
|
?NL
|
||||||
|
]
|
||||||
|
end.
|
||||||
|
|
||||||
|
fmt_default(Field, Indent) ->
|
||||||
|
case get_default(Field, #{indent => Indent, comment => true}) of
|
||||||
|
undefined -> "";
|
||||||
|
Default -> [Indent, ?COMMENT2, ?DEFAULT, Default, ?NL]
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_default(#{default := Default}, Opts) when is_map(Opts) ->
|
||||||
|
#{indent := Indent, comment := Comment} = Opts,
|
||||||
|
get_default(Default, Indent, Comment);
|
||||||
|
get_default(_, _Opts) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
-define(RE, <<"^[A-Za-z0-9\"]+$">>).
|
||||||
|
|
||||||
|
get_default(#{oneliner := true, hocon := Content}, _Indent, _Comment) ->
|
||||||
|
try_to_remove_quote(Content);
|
||||||
|
get_default(#{oneliner := false, hocon := Content}, Indent0, Comment) ->
|
||||||
|
Target = iolist_to_binary([?NL, Indent0, comment2(Comment), ?INDENT]),
|
||||||
|
replace_nl(Indent0, Comment, Content, Target);
|
||||||
|
get_default(Bin, _Indent, _Comment) ->
|
||||||
|
Bin.
|
||||||
|
|
||||||
|
replace_nl(Indent0, Comment, Content, Target) ->
|
||||||
|
[
|
||||||
|
?NL,
|
||||||
|
Indent0,
|
||||||
|
comment2(Comment),
|
||||||
|
?INDENT,
|
||||||
|
binary:replace(Content, [<<"\n">>], Target, [global]),
|
||||||
|
?NL
|
||||||
|
].
|
||||||
|
|
||||||
|
try_to_remove_quote(Content) ->
|
||||||
|
case re:run(Content, ?RE) of
|
||||||
|
nomatch ->
|
||||||
|
Content;
|
||||||
|
_ ->
|
||||||
|
case string:trim(Content, both, [$"]) of
|
||||||
|
<<"">> -> Content;
|
||||||
|
Other -> Other
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
bin(S) when is_list(S) -> unicode:characters_to_binary(S, utf8);
|
||||||
|
bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom);
|
||||||
|
bin(Int) when is_integer(Int) -> integer_to_binary(Int);
|
||||||
|
bin(Bin) -> Bin.
|
||||||
|
|
||||||
|
str(A) when is_atom(A) -> atom_to_list(A);
|
||||||
|
str(S) when is_list(S) -> S;
|
||||||
|
str(B) when is_binary(B) -> binary_to_list(B);
|
||||||
|
str({KeyName, _ValName}) -> str(KeyName).
|
||||||
|
|
||||||
|
comment(true) -> ?COMMENT;
|
||||||
|
comment(false) -> "".
|
||||||
|
|
||||||
|
comment2(true) -> ?COMMENT2;
|
||||||
|
comment2(false) -> "".
|
||||||
|
|
||||||
|
new_link_cache() ->
|
||||||
|
ets:new(?MODULE, [private, set, {keypos, 1}]).
|
||||||
|
|
||||||
|
delete_link_cache(#{tid := Tid}) ->
|
||||||
|
ets:delete(Tid).
|
||||||
|
|
||||||
|
find_link(#{tid := Tid}, Key) ->
|
||||||
|
case ets:lookup(Tid, Key) of
|
||||||
|
[{_, Value}] -> {ok, Value};
|
||||||
|
[] -> {error, not_found}
|
||||||
|
end.
|
||||||
|
|
||||||
|
insert_link(#{tid := Tid}, Item) ->
|
||||||
|
ets:insert(Tid, Item).
|
||||||
|
|
||||||
|
resolve_name({N1, N2}) -> {N1, N2};
|
||||||
|
resolve_name(N) -> {N, N}.
|
|
@ -64,7 +64,7 @@ fields("connectors") ->
|
||||||
mk(
|
mk(
|
||||||
hoconsc:map(
|
hoconsc:map(
|
||||||
name,
|
name,
|
||||||
hoconsc:union([ref(emqx_connector_mqtt_schema, "connector")])
|
ref(emqx_connector_mqtt_schema, "connector")
|
||||||
),
|
),
|
||||||
#{desc => ?DESC("mqtt")}
|
#{desc => ?DESC("mqtt")}
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -171,6 +171,7 @@ bind(Port) ->
|
||||||
#{
|
#{
|
||||||
default => Port,
|
default => Port,
|
||||||
required => true,
|
required => true,
|
||||||
|
extra => #{example => [Port, "0.0.0.0:" ++ integer_to_list(Port)]},
|
||||||
desc => ?DESC(bind)
|
desc => ?DESC(bind)
|
||||||
}
|
}
|
||||||
)}.
|
)}.
|
||||||
|
|
|
@ -567,7 +567,8 @@ authentication_schema() ->
|
||||||
emqx_authn_schema:authenticator_type(),
|
emqx_authn_schema:authenticator_type(),
|
||||||
#{
|
#{
|
||||||
required => {false, recursively},
|
required => {false, recursively},
|
||||||
desc => ?DESC(gateway_common_authentication)
|
desc => ?DESC(gateway_common_authentication),
|
||||||
|
examples => emqx_authn_api:authenticator_examples()
|
||||||
}
|
}
|
||||||
).
|
).
|
||||||
|
|
||||||
|
@ -606,7 +607,7 @@ gateway_common_options() ->
|
||||||
].
|
].
|
||||||
|
|
||||||
mountpoint() ->
|
mountpoint() ->
|
||||||
mountpoint(<<>>).
|
mountpoint(<<"">>).
|
||||||
mountpoint(Default) ->
|
mountpoint(Default) ->
|
||||||
sc(
|
sc(
|
||||||
binary(),
|
binary(),
|
||||||
|
|
|
@ -109,10 +109,7 @@ sc(Type, DescId, Default) ->
|
||||||
hoconsc:mk(Type, #{default => Default, desc => ?DESC(DescId)}).
|
hoconsc:mk(Type, #{default => Default, desc => ?DESC(DescId)}).
|
||||||
|
|
||||||
backend_config() ->
|
backend_config() ->
|
||||||
hoconsc:mk(
|
hoconsc:mk(hoconsc:ref(?MODULE, mnesia_config), #{desc => ?DESC(backend)}).
|
||||||
hoconsc:union([hoconsc:ref(?MODULE, mnesia_config)]),
|
|
||||||
#{desc => ?DESC(backend)}
|
|
||||||
).
|
|
||||||
|
|
||||||
retainer_indices(type) ->
|
retainer_indices(type) ->
|
||||||
list(list(integer()));
|
list(list(integer()));
|
||||||
|
|
|
@ -186,7 +186,7 @@ rule_name() ->
|
||||||
binary(),
|
binary(),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC("rules_name"),
|
desc => ?DESC("rules_name"),
|
||||||
default => "",
|
default => <<"">>,
|
||||||
required => false,
|
required => false,
|
||||||
example => "foo"
|
example => "foo"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ fields("slow_subs") ->
|
||||||
)},
|
)},
|
||||||
{stats_type,
|
{stats_type,
|
||||||
sc(
|
sc(
|
||||||
hoconsc:union([whole, internal, response]),
|
hoconsc:enum([whole, internal, response]),
|
||||||
whole,
|
whole,
|
||||||
stats_type
|
stats_type
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue