diff --git a/apps/emqx_conf/src/emqx_conf.erl b/apps/emqx_conf/src/emqx_conf.erl index 140b008d1..4f845a166 100644 --- a/apps/emqx_conf/src/emqx_conf.erl +++ b/apps/emqx_conf/src/emqx_conf.erl @@ -28,7 +28,7 @@ -export([remove/2, remove/3]). -export([tombstone/2]). -export([reset/2, reset/3]). --export([dump_schema/2, reformat_schema_dump/1]). +-export([dump_schema/2, reformat_schema_dump/2]). -export([schema_module/0]). %% TODO: move to emqx_dashboard when we stop building api schema at build time @@ -186,7 +186,7 @@ gen_schema_json(Dir, SchemaModule, Lang) -> ok = gen_preformat_md_json_files(Dir, StructsJsonArray, Lang). gen_preformat_md_json_files(Dir, StructsJsonArray, Lang) -> - NestedStruct = reformat_schema_dump(StructsJsonArray), + NestedStruct = reformat_schema_dump(StructsJsonArray, Lang), %% write to files NestedJsonFile = filename:join([Dir, "schema-v2-" ++ Lang ++ ".json"]), io:format(user, "===< Generating: ~s~n", [NestedJsonFile]), @@ -196,15 +196,17 @@ gen_preformat_md_json_files(Dir, StructsJsonArray, Lang) -> ok. %% @doc This function is exported for scripts/schema-dump-reformat.escript -reformat_schema_dump(StructsJsonArray0) -> +reformat_schema_dump(StructsJsonArray0, Lang) -> %% prepare + DescResolver = make_desc_resolver(Lang), StructsJsonArray = deduplicate_by_full_name(StructsJsonArray0), #{fields := RootFields} = hd(StructsJsonArray), RootNames0 = lists:map(fun(#{name := RootName}) -> RootName end, RootFields), RootNames = lists:map(fun to_bin/1, RootNames0), %% reformat [Root | FlatStructs0] = lists:map( - fun(Struct) -> gen_flat_doc(RootNames, Struct) end, StructsJsonArray + fun(Struct) -> gen_flat_doc(RootNames, Struct, DescResolver) end, + StructsJsonArray ), FlatStructs = [Root#{text => <<"root">>, hash => <<"root">>} | FlatStructs0], gen_nested_doc(FlatStructs). @@ -302,7 +304,7 @@ expand_ref(#{hash := FullName}, FindFn, Path) -> %% generate flat docs for each struct. %% using references to link to other structs. -gen_flat_doc(RootNames, #{full_name := FullName, fields := Fields} = S) -> +gen_flat_doc(RootNames, #{full_name := FullName, fields := Fields} = S, DescResolver) -> ShortName = short_name(FullName), case is_missing_namespace(ShortName, to_bin(FullName), RootNames) of true -> @@ -314,18 +316,17 @@ gen_flat_doc(RootNames, #{full_name := FullName, fields := Fields} = S) -> text => short_name(FullName), hash => format_hash(FullName), doc => maps:get(desc, S, <<"">>), - fields => format_fields(Fields) + fields => format_fields(Fields, DescResolver) }. -format_fields([]) -> - []; -format_fields([Field | Fields]) -> - [format_field(Field) | format_fields(Fields)]. +format_fields(Fields, DescResolver) -> + [format_field(F, DescResolver) || F <- Fields]. -format_field(#{name := Name, aliases := Aliases, type := Type} = F) -> +format_field(#{name := Name, aliases := Aliases, type := Type} = F, DescResolver) -> L = [ {text, Name}, {type, format_type(Type)}, + {typedoc, format_type_desc(Type, DescResolver)}, {refs, format_refs(Type)}, {aliases, case Aliases of @@ -393,10 +394,26 @@ format_union_members([Member | Members], Acc) -> NewAcc = [format_type(Member) | Acc], format_union_members(Members, NewAcc). +format_type_desc(#{kind := primitive, name := Name}, DescResolver) -> + format_primitive_type_desc(Name, DescResolver); +format_type_desc(#{}, _DescResolver) -> + undefined. + format_primitive_type(TypeStr) -> - Spec = emqx_conf_schema_types:readable_docgen(?MODULE, TypeStr), + Spec = get_primitive_typespec(TypeStr), to_bin(maps:get(type, Spec)). +format_primitive_type_desc(TypeStr, DescResolver) -> + case get_primitive_typespec(TypeStr) of + #{desc := Desc} -> + DescResolver(Desc); + #{} -> + undefined + end. + +get_primitive_typespec(TypeStr) -> + emqx_conf_schema_types:readable_docgen(?MODULE, TypeStr). + %% All types should have a namespace to avlid name clashing. is_missing_namespace(ShortName, FullName, RootNames) -> case lists:member(ShortName, RootNames) of diff --git a/apps/emqx_conf/src/emqx_conf_schema_types.erl b/apps/emqx_conf/src/emqx_conf_schema_types.erl index 239624a81..9d00b9650 100644 --- a/apps/emqx_conf/src/emqx_conf_schema_types.erl +++ b/apps/emqx_conf/src/emqx_conf_schema_types.erl @@ -16,6 +16,8 @@ -module(emqx_conf_schema_types). +-include_lib("hocon/include/hocon_types.hrl"). + -export([readable/2]). -export([readable_swagger/2, readable_dashboard/2, readable_docgen/2]). @@ -165,37 +167,37 @@ readable("duration()") -> #{ swagger => #{type => string, example => <<"12m">>}, dashboard => #{type => duration}, - docgen => #{type => "String", example => <<"12m">>} + docgen => #{type => "Duration", example => <<"12m">>, desc => ?DESC(duration)} }; readable("duration_s()") -> #{ swagger => #{type => string, example => <<"1h">>}, dashboard => #{type => duration}, - docgen => #{type => "String", example => <<"1h">>} + docgen => #{type => "Duration(s)", example => <<"1h">>, desc => ?DESC(duration)} }; readable("duration_ms()") -> #{ swagger => #{type => string, example => <<"32s">>}, dashboard => #{type => duration}, - docgen => #{type => "String", example => <<"32s">>} + docgen => #{type => "Duration", example => <<"32s">>, desc => ?DESC(duration)} }; readable("timeout_duration()") -> #{ swagger => #{type => string, example => <<"12m">>}, dashboard => #{type => duration}, - docgen => #{type => "String", example => <<"12m">>} + docgen => #{type => "Duration", example => <<"12m">>, desc => ?DESC(duration)} }; readable("timeout_duration_s()") -> #{ swagger => #{type => string, example => <<"1h">>}, dashboard => #{type => duration}, - docgen => #{type => "String", example => <<"1h">>} + docgen => #{type => "Duration(s)", example => <<"1h">>, desc => ?DESC(duration)} }; readable("timeout_duration_ms()") -> #{ swagger => #{type => string, example => <<"32s">>}, dashboard => #{type => duration}, - docgen => #{type => "String", example => <<"32s">>} + docgen => #{type => "Duration", example => <<"32s">>, desc => ?DESC(duration)} }; readable("percent()") -> #{ @@ -219,13 +221,13 @@ readable("bytesize()") -> #{ swagger => #{type => string, example => <<"32MB">>}, dashboard => #{type => 'byteSize'}, - docgen => #{type => "String", example => <<"32MB">>} + docgen => #{type => "Bytesize", example => <<"32MB">>, desc => ?DESC(bytesize)} }; readable("wordsize()") -> #{ swagger => #{type => string, example => <<"1024KB">>}, dashboard => #{type => 'wordSize'}, - docgen => #{type => "String", example => <<"1024KB">>} + docgen => #{type => "Bytesize", example => <<"1024KB">>, desc => ?DESC(bytesize)} }; readable("map(" ++ Map) -> [$) | _MapArgs] = lists:reverse(Map), @@ -287,7 +289,11 @@ readable("secret()") -> #{ swagger => #{type => string, example => <<"R4ND0M/S∃CЯ∃T"/utf8>>}, dashboard => #{type => string}, - docgen => #{type => "String", example => <<"R4ND0M/S∃CЯ∃T"/utf8>>} + docgen => #{ + type => "Secret", + example => <<"R4ND0M/S∃CЯ∃T"/utf8>>, + desc => ?DESC(secret) + } }; readable(TypeStr0) -> case string:split(TypeStr0, ":") of diff --git a/rel/i18n/emqx_conf_schema_types.hocon b/rel/i18n/emqx_conf_schema_types.hocon new file mode 100644 index 000000000..4955d3173 --- /dev/null +++ b/rel/i18n/emqx_conf_schema_types.hocon @@ -0,0 +1,12 @@ +emqx_conf_schema_types { + + duration.desc: + """A string that represents a time duration, for example: 10s, 2.5m, 1h30m, 1W2D, or 2345ms, which is the smallest unit. When precision is specified, finer portions of the duration may be ignored: writing 1200ms for Duration(s) is equivalent to writing 1s. It doesn't matter if units are in upper or lower case.""" + + bytesize.desc: + """A string that represents a number of bytes, for example: 10B, 640kb, 4MB, 1GB. Units are interpreted as powers of 1024, and the unit part is case-insensitive.""" + + secret.desc: + """A string holding some sensitive information, such as a password. When secret starts with file://, the rest of the string is interpreted as a path to a file containing the secret itself: whole content of the file except any trailing whitespace characters is considered a secret value.""" + +} diff --git a/scripts/schema-dump-reformat.escript b/scripts/schema-dump-reformat.escript index 31cfdd7d9..a3e059b70 100755 --- a/scripts/schema-dump-reformat.escript +++ b/scripts/schema-dump-reformat.escript @@ -18,7 +18,8 @@ main(_) -> halt(1). reformat(Json) -> - emqx_conf:reformat_schema_dump(fix(Json)). + %% NOTE: Assuming schema would contain no types needing localized typedocs. + emqx_conf:reformat_schema_dump(fix(Json), _Lang = "en"). %% fix old type specs to make them compatible with new type specs fix(#{