diff --git a/.github/workflows/build_slim_packages.yaml b/.github/workflows/build_slim_packages.yaml
index 2c42c1a26..5614b774c 100644
--- a/.github/workflows/build_slim_packages.yaml
+++ b/.github/workflows/build_slim_packages.yaml
@@ -218,7 +218,7 @@ jobs:
- emqx
- emqx-enterprise
runs-on: ubuntu-20.04
- container: "ghcr.io/iequ1/emqx-schema-validate:0.2.3"
+ container: "ghcr.io/iequ1/emqx-schema-validate:0.3.0"
steps:
- uses: actions/download-artifact@v2
name: Download schema dump
diff --git a/apps/emqx/etc/emqx.conf b/apps/emqx/etc/emqx.conf
index a48a226a4..b2a3d77ef 100644
--- a/apps/emqx/etc/emqx.conf
+++ b/apps/emqx/etc/emqx.conf
@@ -515,7 +515,7 @@ authorization {
## Default: allow
no_match: allow
- ## The action when authorization check reject current operation
+ ## The action when the authorization check rejects an operation
##
## @doc authorization.deny_action
## ValueType: ignore | disconnect
@@ -1048,16 +1048,16 @@ broker {
## Default: true
route_batch_clean = true
- ## Performance toggle for subscribe/unsubscribe wildcard topic.
- ## Change this toggle only when there are many wildcard topics.
+ ## Performance tuning for subscribe/unsubscribe wildcard topic.
+ ## Change this parameter only when there are many wildcard topics.
##
## NOTE: when changing from/to 'global' lock, it requires all
## nodes in the cluster to be stopped before the change.
##
## @doc broker.perf.route_lock_type
## ValueType: key | tab | global
- ## - key: mnesia translational updates with per-key locks. recommended for single node setup.
- ## - tab: mnesia translational updates with table lock. recommended for multi-nodes setup.
+ ## - key: mnesia transactional updates with per-key locks. recommended for single node setup.
+ ## - tab: mnesia transactional updates with table lock. recommended for multi-nodes setup.
## - global: global lock protected updates. recommended for larger cluster.
## Default: key
perf.route_lock_type = key
diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl
index 3a37b8ddb..90cd9cbff 100644
--- a/apps/emqx/src/emqx_schema.erl
+++ b/apps/emqx/src/emqx_schema.erl
@@ -326,7 +326,10 @@ fields("authorization") ->
{"deny_action",
sc(
hoconsc:enum([ignore, disconnect]),
- #{default => ignore}
+ #{
+ default => ignore,
+ desc => "The action when the authorization check rejects an operation."
+ }
)},
{"cache",
sc(
@@ -920,7 +923,7 @@ fields("mqtt_quic_listener") ->
{"certfile",
sc(
string(),
- #{desc => "Path to the certificate."}
+ #{desc => "Path to the certificate file."}
)},
{"keyfile",
sc(
@@ -949,7 +952,11 @@ fields("ws_opts") ->
{"mqtt_piggyback",
sc(
hoconsc:enum([single, multiple]),
- #{default => multiple}
+ #{
+ default => multiple,
+ desc =>
+ "Whether a WebSocket message is allowed to contain multiple MQTT packets."
+ }
)},
{"compress",
sc(
@@ -1280,12 +1287,34 @@ fields("broker_perf") ->
{"route_lock_type",
sc(
hoconsc:enum([key, tab, global]),
- #{default => key}
+ #{
+ default => key,
+ desc =>
+ "Performance tuning for subscribing/unsubscribing a wildcard topic.
\n"
+ "Change this parameter only when there are many wildcard topics.
\n"
+ "NOTE: when changing from/to `global` lock, it requires all\n"
+ "nodes in the cluster to be stopped before the change.\n\n"
+ " - `key`: mnesia transactional updates with per-key locks. "
+ "Recommended for a single-node setup.\n"
+ " - `tab`: mnesia transactional updates with table lock. Recommended for a cluster setup.\n"
+ " - `global`: updates are protected with a global lock. Recommended for large clusters."
+ }
)},
{"trie_compaction",
sc(
boolean(),
- #{default => true}
+ #{
+ default => true,
+ desc =>
+ "Enable trie path compaction.
\n"
+ "Enabling it significantly improves wildcard topic subscribe\n"
+ "rate, if wildcard topics have unique prefixes like:\n"
+ "'sensor/{{id}}/+/', where ID is unique per subscriber.
\n"
+ "Topic match performance (when publishing) may degrade if messages\n"
+ "are mostly published to topics with large number of levels.
\n"
+ "NOTE: This is a cluster-wide configuration.\n"
+ "It requires all nodes to be stopped before changing it."
+ }
)}
];
fields("sys_topics") ->
@@ -1293,12 +1322,21 @@ fields("sys_topics") ->
{"sys_msg_interval",
sc(
hoconsc:union([disabled, duration()]),
- #{default => "1m"}
+ #{
+ default => "1m",
+ desc => "Time interval of publishing `$SYS` messages."
+ }
)},
{"sys_heartbeat_interval",
sc(
hoconsc:union([disabled, duration()]),
- #{default => "30s"}
+ #{
+ default => "30s",
+ desc =>
+ "Time interval for publishing following heartbeat messages:
"
+ " - `$SYS/brokers//uptime`\n"
+ " - `$SYS/brokers//datetime`"
+ }
)},
{"sys_event_messages",
sc(
@@ -2045,8 +2083,8 @@ server_ssl_opts_schema(Defaults, IsRanchListener) ->
default => D("dhfile"),
required => false,
desc =>
- "Path to a file containing PEM-encoded Diffie Hellman parameters\n"
- "to be used by the server if a cipher suite using Diffie Hellman\n"
+ "Path to a file containing PEM-encoded Diffie-Hellman parameters\n"
+ "to be used by the server if a cipher suite using Diffie-Hellman\n"
"key exchange is negotiated. If not specified, default parameters\n"
"are used.
\n"
"NOTE: The dhfile
option is not supported by TLS 1.3."
diff --git a/apps/emqx/src/emqx_zone_schema.erl b/apps/emqx/src/emqx_zone_schema.erl
index 2a101c0b9..27be87808 100644
--- a/apps/emqx/src/emqx_zone_schema.erl
+++ b/apps/emqx/src/emqx_zone_schema.erl
@@ -16,7 +16,7 @@
-module(emqx_zone_schema).
--export([namespace/0, roots/0, fields/1]).
+-export([namespace/0, roots/0, fields/1, desc/1]).
namespace() -> zone.
@@ -38,6 +38,9 @@ roots() ->
fields(Name) ->
[{N, no_default(Sc)} || {N, Sc} <- emqx_schema:fields(Name)].
+desc(Name) ->
+ emqx_schema:desc(Name).
+
%% no default values for zone settings
no_default(Sc) ->
fun
diff --git a/apps/emqx_authn/src/emqx_authn_password_hashing.erl b/apps/emqx_authn/src/emqx_authn_password_hashing.erl
index 7435fe9c9..959d9030a 100644
--- a/apps/emqx_authn/src/emqx_authn_password_hashing.erl
+++ b/apps/emqx_authn/src/emqx_authn_password_hashing.erl
@@ -47,6 +47,7 @@
-export([
roots/0,
fields/1,
+ desc/1,
namespace/0
]).
@@ -69,39 +70,72 @@ fields(bcrypt_rw) ->
fields(bcrypt) ++
[{salt_rounds, fun salt_rounds/1}];
fields(bcrypt) ->
- [{name, {enum, [bcrypt]}}];
+ [{name, sc(bcrypt, #{desc => "BCRYPT password hashing."})}];
fields(pbkdf2) ->
[
- {name, {enum, [pbkdf2]}},
- {mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}},
- {iterations, integer()},
+ {name, sc(pbkdf2, #{desc => "PBKDF2 password hashing."})},
+ {mac_fun,
+ sc(
+ hoconsc:enum([md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]),
+ #{desc => "Specifies mac_fun for PBKDF2 hashing algorithm."}
+ )},
+ {iterations,
+ sc(
+ integer(),
+ #{desc => "Iteration count for PBKDF2 hashing algorithm."}
+ )},
{dk_length, fun dk_length/1}
];
fields(other_algorithms) ->
[
- {name, {enum, [plain, md5, sha, sha256, sha512]}},
+ {name,
+ sc(
+ hoconsc:enum([plain, md5, sha, sha256, sha512]),
+ #{
+ desc =>
+ "Simple password hashing algorithm."
+ }
+ )},
{salt_position, fun salt_position/1}
].
+desc(bcrypt_rw) ->
+ "Settings for bcrypt password hashing algorithm (for DB backends with write capability).";
+desc(bcrypt) ->
+ "Settings for bcrypt password hashing algorithm.";
+desc(pbkdf2) ->
+ "Settings for PBKDF2 password hashing algorithm.";
+desc(other_algorithms) ->
+ "Settings for other password hashing algorithms.";
+desc(_) ->
+ undefined.
+
salt_position(type) -> {enum, [prefix, suffix]};
-salt_position(desc) -> "Specifies whether the password salt is stored as a prefix or the suffix.";
salt_position(default) -> prefix;
+salt_position(desc) -> "Salt position for PLAIN, MD5, SHA, SHA256 and SHA512 algorithms.";
salt_position(_) -> undefined.
salt_rounds(type) -> integer();
-salt_rounds(desc) -> "Cost factor for the bcrypt hash.";
salt_rounds(default) -> 10;
+salt_rounds(desc) -> "Salt rounds for BCRYPT password generation.";
salt_rounds(_) -> undefined.
-dk_length(type) -> integer();
-dk_length(desc) -> "Length of the derived key.";
-dk_length(required) -> false;
-dk_length(_) -> undefined.
+dk_length(type) ->
+ integer();
+dk_length(required) ->
+ false;
+dk_length(desc) ->
+ "Derived length for PBKDF2 hashing algorithm. If not specified, "
+ "calculated automatically based on `mac_fun`.";
+dk_length(_) ->
+ undefined.
type_rw(type) ->
hoconsc:union(rw_refs());
type_rw(default) ->
#{<<"name">> => sha256, <<"salt_position">> => prefix};
+type_rw(desc) ->
+ "Options for password hash creation and verification.";
type_rw(_) ->
undefined.
@@ -109,6 +143,8 @@ type_ro(type) ->
hoconsc:union(ro_refs());
type_ro(default) ->
#{<<"name">> => sha256, <<"salt_position">> => prefix};
+type_ro(desc) ->
+ "Options for password hash verification.";
type_ro(_) ->
undefined.
@@ -199,3 +235,5 @@ ro_refs() ->
hoconsc:ref(?MODULE, pbkdf2),
hoconsc:ref(?MODULE, other_algorithms)
].
+
+sc(Type, Meta) -> hoconsc:mk(Type, Meta).
diff --git a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl b/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl
index 535ff30a9..af90f96e5 100644
--- a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl
+++ b/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl
@@ -26,7 +26,8 @@
-export([
namespace/0,
roots/0,
- fields/1
+ fields/1,
+ desc/1
]).
-export([
@@ -105,11 +106,19 @@ fields(?CONF_NS) ->
{iteration_count, fun iteration_count/1}
] ++ emqx_authn_schema:common_fields().
+desc(?CONF_NS) ->
+ "Settings for Salted Challenge Response Authentication Mechanism\n"
+ "(SCRAM) authentication.";
+desc(_) ->
+ undefined.
+
algorithm(type) -> hoconsc:enum([sha256, sha512]);
+algorithm(desc) -> "Hashing algorithm.";
algorithm(default) -> sha256;
algorithm(_) -> undefined.
iteration_count(type) -> non_neg_integer();
+iteration_count(desc) -> "Iteration count.";
iteration_count(default) -> 4096;
iteration_count(_) -> undefined.
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
index 31fff4940..88e4d83a5 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
@@ -28,6 +28,7 @@
namespace/0,
roots/0,
fields/1,
+ desc/1,
validations/0
]).
@@ -56,21 +57,29 @@ roots() ->
fields(get) ->
[
- {method, #{type => get, default => post}},
+ {method, #{type => get, default => post, desc => "HTTP method."}},
{headers, fun headers_no_content_type/1}
] ++ common_fields();
fields(post) ->
[
- {method, #{type => post, default => post}},
+ {method, #{type => post, default => post, desc => "HTTP method."}},
{headers, fun headers/1}
] ++ common_fields().
+desc(get) ->
+ "Settings for HTTP-based authentication (GET).";
+desc(post) ->
+ "Settings for HTTP-based authentication (POST).";
+desc(_) ->
+ undefined.
+
common_fields() ->
[
{mechanism, emqx_authn_schema:mechanism('password_based')},
{backend, emqx_authn_schema:backend(http)},
{url, fun url/1},
- {body, map([{fuzzy, term(), binary()}])},
+ {body,
+ hoconsc:mk(map([{fuzzy, term(), binary()}]), #{desc => "Body of the HTTP request."})},
{request_timeout, fun request_timeout/1}
] ++ emqx_authn_schema:common_fields() ++
maps:to_list(
@@ -90,12 +99,15 @@ validations() ->
].
url(type) -> binary();
+url(desc) -> "URL of the auth server.";
url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
url(required) -> true;
url(_) -> undefined.
headers(type) ->
map();
+headers(desc) ->
+ "List of HTTP headers.";
headers(converter) ->
fun(Headers) ->
maps:merge(default_headers(), transform_header_name(Headers))
@@ -107,6 +119,8 @@ headers(_) ->
headers_no_content_type(type) ->
map();
+headers_no_content_type(desc) ->
+ "List of HTTP headers.";
headers_no_content_type(converter) ->
fun(Headers) ->
maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
@@ -117,6 +131,7 @@ headers_no_content_type(_) ->
undefined.
request_timeout(type) -> emqx_schema:duration_ms();
+request_timeout(desc) -> "HTTP request timeout";
request_timeout(default) -> <<"5s">>;
request_timeout(_) -> undefined.
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl
index b12a7b041..388d36fdd 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl
@@ -26,7 +26,8 @@
-export([
namespace/0,
roots/0,
- fields/1
+ fields/1,
+ desc/1
]).
-export([
@@ -54,20 +55,20 @@ roots() ->
fields('hmac-based') ->
[
- {use_jwks, {enum, [false]}},
- {algorithm, {enum, ['hmac-based']}},
+ {use_jwks, sc(hoconsc:enum([false]), #{desc => ""})},
+ {algorithm, sc(hoconsc:enum(['hmac-based']), #{desc => "Signing algorithm."})},
{secret, fun secret/1},
{secret_base64_encoded, fun secret_base64_encoded/1}
] ++ common_fields();
fields('public-key') ->
[
- {use_jwks, {enum, [false]}},
- {algorithm, {enum, ['public-key']}},
+ {use_jwks, sc(hoconsc:enum([false]), #{desc => ""})},
+ {algorithm, sc(hoconsc:enum(['public-key']), #{desc => "Signing algorithm."})},
{certificate, fun certificate/1}
] ++ common_fields();
fields('jwks') ->
[
- {use_jwks, {enum, [true]}},
+ {use_jwks, sc(hoconsc:enum([true]), #{desc => ""})},
{endpoint, fun endpoint/1},
{refresh_interval, fun refresh_interval/1},
{ssl, #{
@@ -75,12 +76,13 @@ fields('jwks') ->
hoconsc:ref(?MODULE, ssl_enable),
hoconsc:ref(?MODULE, ssl_disable)
]),
+ desc => "Enable/disable SSL.",
default => #{<<"enable">> => false}
}}
] ++ common_fields();
fields(ssl_enable) ->
[
- {enable, #{type => true}},
+ {enable, #{type => true, desc => ""}},
{cacertfile, fun cacertfile/1},
{certfile, fun certfile/1},
{keyfile, fun keyfile/1},
@@ -88,7 +90,20 @@ fields(ssl_enable) ->
{server_name_indication, fun server_name_indication/1}
];
fields(ssl_disable) ->
- [{enable, #{type => false}}].
+ [{enable, #{type => false, desc => ""}}].
+
+desc('hmac-based') ->
+ "Settings for HMAC-based token signing algorithm.";
+desc('public-key') ->
+ "Settings for public key-based token signing algorithm.";
+desc('jwks') ->
+ "Settings for a signing using JSON Web Key Set (JWKs).";
+desc(ssl_disable) ->
+ "";
+desc(ssl_enable) ->
+ "SSL configuration.";
+desc(_) ->
+ undefined.
common_fields() ->
[
@@ -97,41 +112,53 @@ common_fields() ->
] ++ emqx_authn_schema:common_fields().
secret(type) -> binary();
+secret(desc) -> "The key to verify the JWT Token using HMAC algorithm.";
secret(_) -> undefined.
secret_base64_encoded(type) -> boolean();
+secret_base64_encoded(desc) -> "Enable/disable base64 encoding of the secret.";
secret_base64_encoded(default) -> false;
secret_base64_encoded(_) -> undefined.
certificate(type) -> string();
+certificate(desc) -> "The certificate used for signing the token.";
certificate(_) -> undefined.
endpoint(type) -> string();
+endpoint(desc) -> "JWKs endpoint.";
endpoint(_) -> undefined.
refresh_interval(type) -> integer();
+refresh_interval(desc) -> "JWKs refresh interval";
refresh_interval(default) -> 300;
refresh_interval(validator) -> [fun(I) -> I > 0 end];
refresh_interval(_) -> undefined.
cacertfile(type) -> string();
+cacertfile(desc) -> "Path to the SSL CA certificate file.";
cacertfile(_) -> undefined.
certfile(type) -> string();
+certfile(desc) -> "Path to the SSL certificate file.";
certfile(_) -> undefined.
keyfile(type) -> string();
+keyfile(desc) -> "Path to the SSL secret key file.";
keyfile(_) -> undefined.
verify(type) -> hoconsc:enum([verify_peer, verify_none]);
+verify(desc) -> "Enable or disable SSL peer verification.";
verify(default) -> verify_none;
verify(_) -> undefined.
server_name_indication(type) -> string();
+server_name_indication(desc) -> "SSL SNI (Server Name Indication)";
server_name_indication(_) -> undefined.
verify_claims(type) ->
list();
+verify_claims(desc) ->
+ "The list of claims to verify.";
verify_claims(default) ->
#{};
verify_claims(validator) ->
@@ -413,3 +440,5 @@ to_binary(A) when is_atom(A) ->
atom_to_binary(A);
to_binary(B) when is_binary(B) ->
B.
+
+sc(Type, Meta) -> hoconsc:mk(Type, Meta).
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl
index b4aeb6759..9d6d1820a 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl
@@ -26,7 +26,8 @@
-export([
namespace/0,
roots/0,
- fields/1
+ fields/1,
+ desc/1
]).
-export([
@@ -108,7 +109,13 @@ fields(?CONF_NS) ->
{password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1}
] ++ emqx_authn_schema:common_fields().
+desc(?CONF_NS) ->
+ "Configuration for authentication using the built-in database.";
+desc(_) ->
+ undefined.
+
user_id_type(type) -> user_id_type();
+user_id_type(desc) -> "Authenticate by client ID or username.";
user_id_type(default) -> <<"username">>;
user_id_type(_) -> undefined.
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl
index f848e661c..11e670f4f 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl
@@ -26,7 +26,8 @@
-export([
namespace/0,
roots/0,
- fields/1
+ fields/1,
+ desc/1
]).
-export([
@@ -55,10 +56,17 @@ fields(?CONF_NS) ->
] ++ emqx_authn_schema:common_fields() ++
emqx_connector_mysql:fields(config).
+desc(?CONF_NS) ->
+ "Configuration for authentication using MySQL database.";
+desc(_) ->
+ undefined.
+
query(type) -> string();
+query(desc) -> "SQL query used to lookup client data.";
query(_) -> undefined.
query_timeout(type) -> emqx_schema:duration_ms();
+query_timeout(desc) -> "Timeout for the SQL query.";
query_timeout(default) -> "5s";
query_timeout(_) -> undefined.
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl
index d795a4b64..34239cb87 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl
@@ -27,7 +27,8 @@
-export([
namespace/0,
roots/0,
- fields/1
+ fields/1,
+ desc/1
]).
-export([
@@ -61,7 +62,13 @@ fields(?CONF_NS) ->
emqx_authn_schema:common_fields() ++
proplists:delete(named_queries, emqx_connector_pgsql:fields(config)).
+desc(?CONF_NS) ->
+ "Configuration for PostgreSQL authentication backend.";
+desc(_) ->
+ undefined.
+
query(type) -> string();
+query(desc) -> "`SQL` query for looking up authentication data.";
query(_) -> undefined.
%%------------------------------------------------------------------------------
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl
index 1a71fafe6..1d37db10a 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl
@@ -26,7 +26,8 @@
-export([
namespace/0,
roots/0,
- fields/1
+ fields/1,
+ desc/1
]).
-export([
@@ -59,6 +60,15 @@ fields(cluster) ->
fields(sentinel) ->
common_fields() ++ emqx_connector_redis:fields(sentinel).
+desc(standalone) ->
+ "Configuration for a standalone Redis instance.";
+desc(cluster) ->
+ "Configuration for a Redis cluster.";
+desc(sentinel) ->
+ "Configuration for a Redis Sentinel.";
+desc(_) ->
+ "".
+
common_fields() ->
[
{mechanism, emqx_authn_schema:mechanism('password_based')},
@@ -68,6 +78,7 @@ common_fields() ->
] ++ emqx_authn_schema:common_fields().
cmd(type) -> string();
+cmd(desc) -> "Redis query.";
cmd(_) -> undefined.
%%------------------------------------------------------------------------------
diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl
index 1e34a2620..0123ee951 100644
--- a/apps/emqx_authz/src/emqx_authz_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_schema.erl
@@ -31,7 +31,8 @@
namespace/0,
roots/0,
fields/1,
- validations/0
+ validations/0,
+ desc/1
]).
-export([
@@ -95,10 +96,11 @@ fields("authorization") ->
];
fields(file) ->
[
- {type, #{type => file}},
+ {type, #{type => file, desc => "Backend type."}},
{enable, #{
type => boolean(),
- default => true
+ default => true,
+ desc => "Enable this backend."
}},
{path, #{
type => string(),
@@ -116,20 +118,21 @@ fields(file) ->
];
fields(http_get) ->
[
- {method, #{type => get, default => post}},
+ {method, #{type => get, default => get, desc => "HTTP method."}},
{headers, fun headers_no_content_type/1}
] ++ http_common_fields();
fields(http_post) ->
[
- {method, #{type => post, default => post}},
+ {method, #{type => post, default => post, desc => "HTTP method."}},
{headers, fun headers/1}
] ++ http_common_fields();
fields(mnesia) ->
[
- {type, #{type => 'built_in_database'}},
+ {type, #{type => 'built_in_database', desc => "Backend type."}},
{enable, #{
type => boolean(),
- default => true
+ default => true,
+ desc => "Enable this backend."
}}
];
fields(mongo_single) ->
@@ -144,9 +147,10 @@ fields(mysql) ->
fields(postgresql) ->
[
{query, query()},
- {type, #{type => postgresql}},
+ {type, #{type => postgresql, desc => "Backend type."}},
{enable, #{
type => boolean(),
+ desc => "Enable this backend.",
default => true
}}
] ++ emqx_connector_pgsql:fields(config);
@@ -160,6 +164,35 @@ fields(redis_cluster) ->
connector_fields(redis, cluster) ++
[{cmd, query()}].
+desc("authorization") ->
+ "Configuration related to the client authorization.";
+desc(file) ->
+ "Authorization using a static file.";
+desc(http_get) ->
+ "Authorization using an external HTTP server (via GET requests).";
+desc(http_post) ->
+ "Authorization using an external HTTP server (via POST requests).";
+desc(mnesia) ->
+ "Authorization using a built-in database (mnesia).";
+desc(mongo_single) ->
+ "Authorization using a single MongoDB instance.";
+desc(mongo_rs) ->
+ "Authorization using a MongoDB replica set.";
+desc(mongo_sharded) ->
+ "Authorization using a sharded MongoDB cluster.";
+desc(mysql) ->
+ "Authorization using a MySQL database.";
+desc(postgresql) ->
+ "Authorization using a PostgreSQL database.";
+desc(redis_single) ->
+ "Authorization using a single Redis instance.";
+desc(redis_sentinel) ->
+ "Authorization using a Redis Sentinel.";
+desc(redis_cluster) ->
+ "Authorization using a Redis cluster.";
+desc(_) ->
+ undefined.
+
http_common_fields() ->
[
{url, fun url/1},
@@ -301,7 +334,7 @@ union_array(Item) when is_list(Item) ->
query() ->
#{
type => binary(),
- desc => "",
+ desc => "Database query used to retrieve authorization data.",
validator => fun(S) ->
case size(S) > 0 of
true -> ok;
diff --git a/apps/emqx_auto_subscribe/src/emqx_auto_subscribe_schema.erl b/apps/emqx_auto_subscribe/src/emqx_auto_subscribe_schema.erl
index 7c034a8af..245339ee1 100644
--- a/apps/emqx_auto_subscribe/src/emqx_auto_subscribe_schema.erl
+++ b/apps/emqx_auto_subscribe/src/emqx_auto_subscribe_schema.erl
@@ -22,7 +22,8 @@
-export([ namespace/0
, roots/0
- , fields/1]).
+ , fields/1
+ , desc/1]).
namespace() -> "auto_subscribe".
@@ -30,7 +31,8 @@ roots() ->
["auto_subscribe"].
fields("auto_subscribe") ->
- [ {topics, hoconsc:array(hoconsc:ref(?MODULE, "topic"))}
+ [ {topics, hoconsc:mk(hoconsc:array(hoconsc:ref(?MODULE, "topic")),
+ #{desc => "List of auto-subscribe topics."})}
];
fields("topic") ->
@@ -52,6 +54,13 @@ fields("topic") ->
desc => "Not local. MQTT 5.0 definition."})}
].
+desc("auto_subscribe") ->
+ "Configuration for `auto_subscribe` feature.";
+desc("topic") ->
+ "";
+desc(_) ->
+ undefined.
+
topic_example() ->
<<"/clientid/", ?PH_S_CLIENTID,
"/username/", ?PH_S_USERNAME,
diff --git a/apps/emqx_bridge/src/emqx_bridge_http_schema.erl b/apps/emqx_bridge/src/emqx_bridge_http_schema.erl
index f5ebf2426..30cd8cc96 100644
--- a/apps/emqx_bridge/src/emqx_bridge_http_schema.erl
+++ b/apps/emqx_bridge/src/emqx_bridge_http_schema.erl
@@ -4,7 +4,7 @@
-import(hoconsc, [mk/2, enum/1]).
--export([roots/0, fields/1, namespace/0]).
+-export([roots/0, fields/1, namespace/0, desc/1]).
%%======================================================================================
%% Hocon Schema Definitions
@@ -16,30 +16,30 @@ fields("config") ->
basic_config() ++
[ {url, mk(binary(),
#{ required => true
- , desc =>"""
+ , desc =>"
The URL of the HTTP Bridge.
Template with variables is allowed in the path, but variables cannot be used in the scheme, host,
or port part.
For example, http://localhost:9901/${topic}
is allowed, but
http://${host}:9901/message
or http://localhost:${port}/message
is not allowed.
-"""
+"
})}
, {local_topic, mk(binary(),
- #{ desc =>"""
+ #{ desc =>"
The MQTT topic filter to be forwarded to the HTTP server. All MQTT 'PUBLISH' messages with the topic
matching the local_topic will be forwarded.
NOTE: if this bridge is used as the output of a rule (EMQX rule engine), and also local_topic is
configured, then both the data got from the rule and the MQTT messages that match local_topic
will be forwarded.
-"""
+"
})}
, {method, mk(method(),
#{ default => post
- , desc =>"""
+ , desc =>"
The method of the HTTP request. All the available methods are: post, put, get, delete.
Template with variables is allowed.
-"""
+"
})}
, {headers, mk(map(),
#{ default => #{
@@ -48,24 +48,22 @@ Template with variables is allowed.
<<"connection">> => <<"keep-alive">>,
<<"content-type">> => <<"application/json">>,
<<"keep-alive">> => <<"timeout=5">>}
- , desc =>"""
+ , desc =>"
The headers of the HTTP request.
Template with variables is allowed.
-"""
+"
})
}
, {body, mk(binary(),
#{ default => <<"${payload}">>
- , desc =>"""
+ , desc =>"
The body of the HTTP request.
Template with variables is allowed.
-"""
+"
})}
, {request_timeout, mk(emqx_schema:duration_ms(),
#{ default => <<"15s">>
- , desc =>"""
-How long will the HTTP request timeout.
-"""
+ , desc => "HTTP request timeout."
})}
];
@@ -80,6 +78,13 @@ fields("put") ->
fields("get") ->
emqx_bridge_schema:metrics_status_fields() ++ fields("post").
+desc("config") ->
+ "Configuration for an HTTP bridge.";
+desc(Method) when Method =:= "get"; Method =:= "put"; Method =:= "post" ->
+ ["Configuration for HTTP bridge using `", string:to_upper(Method), "` method."];
+desc(_) ->
+ undefined.
+
basic_config() ->
[ {enable,
mk(boolean(),
diff --git a/apps/emqx_bridge/src/emqx_bridge_mqtt_schema.erl b/apps/emqx_bridge/src/emqx_bridge_mqtt_schema.erl
index 67a3806b2..987296781 100644
--- a/apps/emqx_bridge/src/emqx_bridge_mqtt_schema.erl
+++ b/apps/emqx_bridge/src/emqx_bridge_mqtt_schema.erl
@@ -4,7 +4,7 @@
-import(hoconsc, [mk/2]).
--export([roots/0, fields/1]).
+-export([roots/0, fields/1, desc/1]).
%%======================================================================================
%% Hocon Schema Definitions
@@ -41,6 +41,11 @@ fields("get_ingress") ->
fields("get_egress") ->
emqx_bridge_schema:metrics_status_fields() ++ fields("post_egress").
+desc(Rec) when Rec =:= "ingress"; Rec =:= "egress" ->
+ "Configuration for MQTT bridge.";
+desc(_) ->
+ undefined.
+
%%======================================================================================
type_field() ->
{type, mk(mqtt,
diff --git a/apps/emqx_bridge/src/emqx_bridge_schema.erl b/apps/emqx_bridge/src/emqx_bridge_schema.erl
index d53a0bf3d..2813ee397 100644
--- a/apps/emqx_bridge/src/emqx_bridge_schema.erl
+++ b/apps/emqx_bridge/src/emqx_bridge_schema.erl
@@ -4,7 +4,7 @@
-import(hoconsc, [mk/2, ref/2]).
--export([roots/0, fields/1, namespace/0]).
+-export([roots/0, fields/1, desc/1, namespace/0]).
-export([ get_response/0
, put_request/0
@@ -86,11 +86,12 @@ namespace() -> "bridge".
roots() -> [bridges].
fields(bridges) ->
- [{http, mk(hoconsc:map(name, ref(emqx_bridge_http_schema, "config")), #{})}]
- ++ [{T, mk(hoconsc:map(name, hoconsc:union([
- ref(schema_mod(T), "ingress"),
- ref(schema_mod(T), "egress")
- ])), #{})} || T <- ?CONN_TYPES];
+ [{http, mk(hoconsc:map(name, ref(emqx_bridge_http_schema, "config")),
+ #{desc => "HTTP bridges to an HTTP server."})}]
+ ++ [{T, mk(hoconsc:map(name, hoconsc:union([ ref(schema_mod(T), "ingress")
+ , ref(schema_mod(T), "egress")
+ ])),
+ #{desc => "MQTT bridges to/from another MQTT broker"})} || T <- ?CONN_TYPES];
fields("metrics") ->
[ {"matched", mk(integer(), #{desc => "Count of this bridge is queried"})}
@@ -112,11 +113,22 @@ fields("node_status") ->
, {"status", mk(status(), #{})}
].
+desc(bridges) ->
+ "Configuration for MQTT bridges.";
+desc("metrics") ->
+ "Bridge metrics.";
+desc("node_metrics") ->
+ "Node metrics.";
+desc("node_status") ->
+ "Node status.";
+desc(_) ->
+ undefined.
+
status() ->
hoconsc:enum([connected, disconnected, connecting]).
node_name() ->
- {"node", mk(binary(), #{desc => "The node name", example => "emqx@127.0.0.1"})}.
+ {"node", mk(binary(), #{desc => "The node name.", example => "emqx@127.0.0.1"})}.
schema_mod(Type) ->
list_to_atom(lists:concat(["emqx_bridge_", Type, "_schema"])).
diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl
index 0af6bb97c..ff5c2d1ad 100644
--- a/apps/emqx_conf/src/emqx_conf_schema.erl
+++ b/apps/emqx_conf/src/emqx_conf_schema.erl
@@ -134,6 +134,7 @@ fields("cluster") ->
#{ mapping => "ekka.proto_dist"
, default => inet_tcp
, 'readOnly' => true
+ , desc => "The Erlang distribution protocol for the cluster."
})}
, {"static",
sc(ref(cluster_static),
diff --git a/apps/emqx_connector/src/emqx_connector_http.erl b/apps/emqx_connector/src/emqx_connector_http.erl
index a63ed78bb..fa53d75cd 100644
--- a/apps/emqx_connector/src/emqx_connector_http.erl
+++ b/apps/emqx_connector/src/emqx_connector_http.erl
@@ -36,6 +36,7 @@
-export([ roots/0
, fields/1
+ , desc/1
, validations/0
, namespace/0
]).
@@ -66,66 +67,73 @@ fields(config) ->
{error, "There must be no query in the base_url"};
(_) -> ok
end
- , desc => """
-The base URL is the URL includes only the scheme, host and port.
+ , desc => "
+The base URL is the URL includes only the scheme, host and port.
When send an HTTP request, the real URL to be used is the concatenation of the base URL and the
-path parameter (passed by the emqx_resource:query/2,3 or provided by the request parameter).
-For example: http://localhost:9901/
-"""
+path parameter (passed by the emqx_resource:query/2,3 or provided by the request parameter).
+For example: `http://localhost:9901/`
+"
})}
, {connect_timeout,
sc(emqx_schema:duration_ms(),
#{ default => "15s"
- , desc => "The timeout when connecting to the HTTP server"
+ , desc => "The timeout when connecting to the HTTP server."
})}
, {max_retries,
sc(non_neg_integer(),
#{ default => 5
- , desc => "Max retry times if error on sending request"
+ , desc => "Max retry times if error on sending request."
})}
, {retry_interval,
sc(emqx_schema:duration(),
#{ default => "1s"
- , desc => "Interval before next retry if error on sending request"
+ , desc => "Interval between retries."
})}
, {pool_type,
sc(pool_type(),
#{ default => random
- , desc => "The type of the pool. Can be one of random, hash"
+ , desc => "The type of the pool. Can be one of `random`, `hash`."
})}
, {pool_size,
sc(non_neg_integer(),
#{ default => 8
- , desc => "The pool size"
+ , desc => "The pool size."
})}
, {enable_pipelining,
sc(boolean(),
#{ default => true
- , desc => "Enable the HTTP pipeline"
+ , desc => "Enable the HTTP pipeline."
})}
, {request, hoconsc:mk(
ref("request"),
#{ default => undefined
, required => false
- , desc => """
+ , desc => "
If the request is provided, the caller can send HTTP requests via
emqx_resource:query(ResourceId, {send_message, BridgeId, Message})
-"""
+"
})}
] ++ emqx_connector_schema_lib:ssl_fields();
fields("request") ->
- [ {method, hoconsc:mk(hoconsc:enum([post, put, get, delete]), #{required => false})}
- , {path, hoconsc:mk(binary(), #{required => false})}
- , {body, hoconsc:mk(binary(), #{required => false})}
- , {headers, hoconsc:mk(map(), #{required => false})}
+ [ {method, hoconsc:mk(hoconsc:enum([post, put, get, delete]), #{required => false, desc => "HTTP method."})}
+ , {path, hoconsc:mk(binary(), #{required => false, desc => "URL path."})}
+ , {body, hoconsc:mk(binary(), #{required => false, desc => "HTTP request body."})}
+ , {headers, hoconsc:mk(map(), #{required => false, desc => "List of HTTP headers."})}
, {request_timeout,
sc(emqx_schema:duration_ms(),
#{ required => false
- , desc => "The timeout when sending request to the HTTP server"
+ , desc => "HTTP request timeout."
})}
].
+desc(config) ->
+ "";
+desc("request") ->
+ "";
+desc(_) ->
+ undefined.
+
validations() ->
[ {check_ssl_opts, fun check_ssl_opts/1} ].
diff --git a/apps/emqx_connector/src/emqx_connector_mongo.erl b/apps/emqx_connector/src/emqx_connector_mongo.erl
index 90945e5b8..4d83c9127 100644
--- a/apps/emqx_connector/src/emqx_connector_mongo.erl
+++ b/apps/emqx_connector/src/emqx_connector_mongo.erl
@@ -32,7 +32,7 @@
%% ecpool callback
-export([connect/1]).
--export([roots/0, fields/1]).
+-export([roots/0, fields/1, desc/1]).
-export([mongo_query/5, check_worker_health/1]).
@@ -89,18 +89,33 @@ fields(topology) ->
, {min_heartbeat_frequency_ms, fun duration/1}
].
+desc(single) ->
+ "Settings for a single MongoDB instance.";
+desc(rs) ->
+ "Settings for replica set.";
+desc(sharded) ->
+ "Settings for sharded cluster.";
+desc(topology) ->
+ "Topology of MongoDB.";
+desc(_) ->
+ undefined.
+
mongo_fields() ->
[ {srv_record, fun srv_record/1}
, {pool_size, fun emqx_connector_schema_lib:pool_size/1}
, {username, fun emqx_connector_schema_lib:username/1}
, {password, fun emqx_connector_schema_lib:password/1}
- , {auth_source, #{type => binary(), required => false}}
+ , {auth_source, #{ type => binary()
+ , required => false
+ , desc => "Database name associated with the user's credentials."
+ }}
, {database, fun emqx_connector_schema_lib:database/1}
, {topology, #{type => hoconsc:ref(?MODULE, topology), required => false}}
] ++
emqx_connector_schema_lib:ssl_fields().
internal_pool_size(type) -> integer();
+internal_pool_size(desc) -> "Pool size on start.";
internal_pool_size(default) -> 1;
internal_pool_size(validator) -> [?MIN(1)];
internal_pool_size(_) -> undefined.
diff --git a/apps/emqx_connector/src/emqx_connector_pgsql.erl b/apps/emqx_connector/src/emqx_connector_pgsql.erl
index 77509b276..d48064f34 100644
--- a/apps/emqx_connector/src/emqx_connector_pgsql.erl
+++ b/apps/emqx_connector/src/emqx_connector_pgsql.erl
@@ -56,6 +56,7 @@ fields(config) ->
emqx_connector_schema_lib:ssl_fields().
named_queries(type) -> map();
+named_queries(desc) -> "Key-value list of prepared SQL statements.";
named_queries(required) -> false;
named_queries(_) -> undefined.
diff --git a/apps/emqx_connector/src/emqx_connector_redis.erl b/apps/emqx_connector/src/emqx_connector_redis.erl
index e8bcc86ac..841bab856 100644
--- a/apps/emqx_connector/src/emqx_connector_redis.erl
+++ b/apps/emqx_connector/src/emqx_connector_redis.erl
@@ -55,22 +55,29 @@ roots() ->
fields(single) ->
[ {server, fun server/1}
, {redis_type, #{type => hoconsc:enum([single]),
- default => single}}
+ default => single,
+ desc => "Redis type."
+ }}
] ++
redis_fields() ++
emqx_connector_schema_lib:ssl_fields();
fields(cluster) ->
[ {servers, fun servers/1}
, {redis_type, #{type => hoconsc:enum([cluster]),
- default => cluster}}
+ default => cluster,
+ desc => "Redis type."
+ }}
] ++
redis_fields() ++
emqx_connector_schema_lib:ssl_fields();
fields(sentinel) ->
[ {servers, fun servers/1}
, {redis_type, #{type => hoconsc:enum([sentinel]),
- default => sentinel}}
- , {sentinel, #{type => string()}}
+ default => sentinel,
+ desc => "Redis type."
+ }}
+ , {sentinel, #{type => string(), desc => "The cluster name in Redis sentinel mode."
+ }}
] ++
redis_fields() ++
emqx_connector_schema_lib:ssl_fields().
@@ -203,7 +210,9 @@ redis_fields() ->
[ {pool_size, fun emqx_connector_schema_lib:pool_size/1}
, {password, fun emqx_connector_schema_lib:password/1}
, {database, #{type => integer(),
- default => 0}}
+ default => 0,
+ desc => "Redis database ID."
+ }}
, {auto_reconnect, fun emqx_connector_schema_lib:auto_reconnect/1}
].
diff --git a/apps/emqx_connector/src/emqx_connector_schema.erl b/apps/emqx_connector/src/emqx_connector_schema.erl
index 7f1be401b..5cd7d7a2a 100644
--- a/apps/emqx_connector/src/emqx_connector_schema.erl
+++ b/apps/emqx_connector/src/emqx_connector_schema.erl
@@ -21,7 +21,7 @@
-import(hoconsc, [mk/2, ref/2]).
--export([roots/0, fields/1]).
+-export([roots/0, fields/1, desc/1]).
-export([ get_response/0
, put_request/0
@@ -62,5 +62,13 @@ fields("connectors") ->
})}
].
+desc(Record) when Record =:= connectors;
+ Record =:= "connectors" ->
+ "Configuration for EMQX connectors.
"
+ "A connector maintains the data related to the external resources,\n"
+ "such as MySQL database.";
+desc(_) ->
+ undefined.
+
schema_mod(Type) ->
list_to_atom(lists:concat(["emqx_connector_", Type])).
diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl
index a2d865443..355b86aaf 100644
--- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl
+++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl
@@ -190,6 +190,8 @@ the memory cache reaches 'seg_bytes'.
})}
].
+desc("connector") ->
+ "Generic configuration for the connector.";
desc("ingress") ->
ingress_desc();
desc("egress") ->
diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl
index bde970a53..f518b8091 100644
--- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl
+++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl
@@ -19,7 +19,9 @@
-export([ roots/0
, fields/1
- ,namespace/0]).
+ , namespace/0
+ , desc/1
+ ]).
namespace() -> <<"dashboard">>.
roots() -> ["dashboard"].
@@ -95,6 +97,15 @@ fields("https") ->
proplists:delete("fail_if_no_peer_cert",
emqx_schema:server_ssl_opts_schema(#{}, true)).
+desc("dashboard") ->
+ "Configuration for EMQX dashboard.";
+desc("http") ->
+ "Configuration for the dashboard listener (plaintext).";
+desc("https") ->
+ "Configuration for the dashboard listener (TLS).";
+desc(_) ->
+ undefined.
+
bind(type) -> hoconsc:union([non_neg_integer(), emqx_schema:ip_port()]);
bind(default) -> 18083;
bind(required) -> true;
diff --git a/apps/emqx_modules/src/emqx_modules_schema.erl b/apps/emqx_modules/src/emqx_modules_schema.erl
index 4fa623dd2..a74406488 100644
--- a/apps/emqx_modules/src/emqx_modules_schema.erl
+++ b/apps/emqx_modules/src/emqx_modules_schema.erl
@@ -33,16 +33,17 @@ roots() ->
[
"delayed",
"telemetry",
- array("rewrite"),
- array("topic_metrics")
+ array("rewrite", #{desc => "List of topic rewrite rules."}),
+ array("topic_metrics", #{desc => "List of topics whose metrics are reported."})
].
fields("telemetry") ->
- [{enable, hoconsc:mk(boolean(), #{default => false})}];
+ [{enable, hoconsc:mk(boolean(), #{default => false, desc => "Enable telemetry."})}];
fields("delayed") ->
[
- {enable, hoconsc:mk(boolean(), #{default => false})},
- {max_delayed_messages, sc(integer(), #{})}
+ {enable, hoconsc:mk(boolean(), #{default => false, desc => "Enable `delayed` module."})},
+ {max_delayed_messages,
+ sc(integer(), #{desc => "Maximum number of delayed messages (0 is no limit)."})}
];
fields("rewrite") ->
[
@@ -64,16 +65,16 @@ fields("rewrite") ->
{re, fun regular_expression/1}
];
fields("topic_metrics") ->
- [{topic, sc(binary(), #{})}].
+ [{topic, sc(binary(), #{desc => "Collect metrics for the topic."})}].
desc("telemetry") ->
"Settings for the telemetry module.";
desc("delayed") ->
"Settings for the delayed module.";
desc("rewrite") ->
- "Settings for the rewrite module.";
+ "Rewrite rule.";
desc("topic_metrics") ->
- "Settings for the topic metrics module.";
+ "";
desc(_) ->
undefined.
@@ -89,6 +90,6 @@ is_re(Bin) ->
{error, Reason} -> {error, {Bin, Reason}}
end.
-array(Name) -> {Name, hoconsc:array(hoconsc:ref(?MODULE, Name))}.
+array(Name, Meta) -> {Name, hoconsc:mk(hoconsc:array(hoconsc:ref(?MODULE, Name)), Meta)}.
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
diff --git a/apps/emqx_retainer/src/emqx_retainer_schema.erl b/apps/emqx_retainer/src/emqx_retainer_schema.erl
index 7868bc6fd..74732bf9b 100644
--- a/apps/emqx_retainer/src/emqx_retainer_schema.erl
+++ b/apps/emqx_retainer/src/emqx_retainer_schema.erl
@@ -2,7 +2,7 @@
-include_lib("typerefl/include/types.hrl").
--export([roots/0, fields/1, namespace/0]).
+-export([roots/0, fields/1, desc/1, namespace/0]).
-define(TYPE(Type), hoconsc:mk(Type)).
@@ -26,7 +26,8 @@ fields("retainer") ->
, {stop_publish_clear_msg, sc(boolean(),
"When the retained flag of the `PUBLISH` message is set and Payload is empty, "
"whether to continue to publish the message.
"
- "See: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718038",
+ "See: "
+ "http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718038",
false)}
, {backend, backend_config()}
];
@@ -52,13 +53,25 @@ fields(flow_control) ->
0)}
, {batch_deliver_limiter, sc(emqx_limiter_schema:bucket_name(),
"The rate limiter name for retained messages' delivery.
"
- "Limiter helps to avoid delivering too many messages to the client at once, which may cause the client "
- "to block or crash, or drop messages due to exceeding the size of the message queue.
"
- "The names of the available rate limiters are taken from the existing rate limiters under `limiter.batch`.
"
+ "Limiter helps to avoid delivering too many messages to the client at once, "
+ "which may cause the client "
+ "to block or crash, or drop messages due to exceeding the size of the message"
+ " queue.
"
+ "The names of the available rate limiters are taken from the existing rate "
+ "limiters under `limiter.batch`.
"
"If this field is empty, limiter is not used.",
undefined)}
].
+desc("retainer") ->
+ "Configuration related to handling `PUBLISH` packets with a `retain` flag set to 1.";
+desc(mnesia_config) ->
+ "Configuration of the internal database storing retained messages.";
+desc(flow_control) ->
+ "Retainer batching and rate limiting.";
+desc(_) ->
+ undefined.
+
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
@@ -74,4 +87,7 @@ is_pos_integer(V) ->
V >= 0.
backend_config() ->
- #{type => hoconsc:union([hoconsc:ref(?MODULE, mnesia_config)])}.
+ #{
+ type => hoconsc:union([hoconsc:ref(?MODULE, mnesia_config)]),
+ desc => "Settings for the database storing the retained messages."
+ }.
diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine_schema.erl b/apps/emqx_rule_engine/src/emqx_rule_engine_schema.erl
index e8cc786ab..c39269ff3 100644
--- a/apps/emqx_rule_engine/src/emqx_rule_engine_schema.erl
+++ b/apps/emqx_rule_engine/src/emqx_rule_engine_schema.erl
@@ -43,16 +43,16 @@ fields("rule_engine") ->
fields("rules") ->
[ rule_name()
, {"sql", sc(binary(),
- #{ desc => """
+ #{ desc => "
SQL query to transform the messages.
Example: SELECT * FROM \"test/topic\" WHERE payload.x = 1
-"""
+"
, example => "SELECT * FROM \"test/topic\" WHERE payload.x = 1"
, required => true
, validator => fun ?MODULE:validate_sql/1
})}
, {"outputs", sc(hoconsc:array(hoconsc:union(outputs())),
- #{ desc => """
+ #{ desc => "
A list of outputs of the rule.
An output can be a string that refers to the channel ID of an EMQX bridge, or an object
that refers to a function.
@@ -65,7 +65,7 @@ If one of the output crashed, all other outputs come after it will still be exec
original order.
If there's any error when running an output, there will be an error message, and the 'failure'
counter of the function output or the bridge channel will increase.
-"""
+"
, default => []
, example => [
<<"http:my_http_bridge">>,
@@ -96,62 +96,62 @@ fields("builtin_output_console") ->
fields("user_provided_function") ->
[ {function, sc(binary(),
- #{ desc => """
+ #{ desc => "
The user provided function. Should be in the format: '{module}:{function}'.
Where {module} is the Erlang callback module and {function} is the Erlang function.
To write your own function, checkout the function console
and
republish
in the source file:
apps/emqx_rule_engine/src/emqx_rule_outputs.erl
as an example.
-"""
+"
, example => "module:function"
})}
, {args, sc(map(),
- #{ desc => """
+ #{ desc => "
The args will be passed as the 3rd argument to module:function/3,
checkout the function console
and republish
in the source file:
apps/emqx_rule_engine/src/emqx_rule_outputs.erl
as an example.
-"""
+"
, default => #{}
})}
];
fields("republish_args") ->
[ {topic, sc(binary(),
- #{ desc =>"""
+ #{ desc =>"
The target topic of message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.
-"""
+"
, required => true
, example => <<"a/1">>
})}
, {qos, sc(qos(),
- #{ desc => """
+ #{ desc => "
The qos of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.
Defaults to ${qos}. If variable ${qos} is not found from the selected result of the rule,
0 is used.
-"""
+"
, default => <<"${qos}">>
, example => <<"${qos}">>
})}
, {retain, sc(hoconsc:union([binary(), boolean()]),
- #{ desc => """
+ #{ desc => "
The 'retain' flag of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.
Defaults to ${retain}. If variable ${retain} is not found from the selected result
of the rule, false is used.
-"""
+"
, default => <<"${retain}">>
, example => <<"${retain}">>
})}
, {payload, sc(binary(),
- #{ desc => """
+ #{ desc => "
The payload of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.
.
Defaults to ${payload}. If variable ${payload} is not found from the selected result
of the rule, then the string \"undefined\" is used.
-"""
+"
, default => <<"${payload}">>
, example => <<"${payload}">>
})}
diff --git a/apps/emqx_slow_subs/src/emqx_slow_subs_schema.erl b/apps/emqx_slow_subs/src/emqx_slow_subs_schema.erl
index 98d9a96af..1d54edd6b 100644
--- a/apps/emqx_slow_subs/src/emqx_slow_subs_schema.erl
+++ b/apps/emqx_slow_subs/src/emqx_slow_subs_schema.erl
@@ -2,32 +2,37 @@
-include_lib("typerefl/include/types.hrl").
--export([roots/0, fields/1, namespace/0]).
+-export([roots/0, fields/1, desc/1, namespace/0]).
namespace() -> "slow_subs".
roots() -> ["slow_subs"].
fields("slow_subs") ->
- [ {enable, sc(boolean(), false, "Enable this feature")}
+ [ {enable, sc(boolean(), false, "Enable this feature.")}
, {threshold,
sc(emqx_schema:duration_ms(),
"500ms",
- "The latency threshold for statistics, the minimum value is 100ms")}
+ "The latency threshold for statistics, the minimum value is 100ms.")}
, {expire_interval,
sc(emqx_schema:duration_ms(),
"300s",
- "The eviction time of the record, which in the statistics record table")}
+ "The eviction time of the record, which in the statistics record table.")}
, {top_k_num,
sc(integer(),
10,
- "The maximum number of records in the slow subscription statistics record table")}
+ "The maximum number of records in the slow subscription statistics record table.")}
, {stats_type,
sc(hoconsc:union([whole, internal, response]),
whole,
- "The method to calculate the latency")}
+ "The method to calculate the latency.")}
].
+desc("slow_subs") ->
+ "Configuration for `slow_subs` feature.";
+desc(_) ->
+ undefined.
+
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
diff --git a/apps/emqx_statsd/src/emqx_statsd_schema.erl b/apps/emqx_statsd/src/emqx_statsd_schema.erl
index 0d6eea3fc..7566e6255 100644
--- a/apps/emqx_statsd/src/emqx_statsd_schema.erl
+++ b/apps/emqx_statsd/src/emqx_statsd_schema.erl
@@ -24,7 +24,8 @@
-export([ namespace/0
, roots/0
- , fields/1]).
+ , fields/1
+ , desc/1]).
-typerefl_from_string({ip_port/0, emqx_statsd_schema, to_ip_port}).
@@ -43,6 +44,11 @@ fields("statsd") ->
, {flush_time_interval, fun flush_interval/1}
].
+desc("statsd") ->
+ "Configuration related to reporting metrics to statsd.";
+desc(_) ->
+ undefined.
+
server(type) -> emqx_schema:ip_port();
server(required) -> true;
server(default) -> "127.0.0.1:8125";
diff --git a/lib-ee/emqx_license/src/emqx_license_schema.erl b/lib-ee/emqx_license/src/emqx_license_schema.erl
index 8b280f55e..d6517ab88 100644
--- a/lib-ee/emqx_license/src/emqx_license_schema.erl
+++ b/lib-ee/emqx_license/src/emqx_license_schema.erl
@@ -12,7 +12,7 @@
-behaviour(hocon_schema).
--export([roots/0, fields/1, validations/0]).
+-export([roots/0, fields/1, validations/0, desc/1]).
roots() ->
[
@@ -40,7 +40,7 @@ fields(key_license) ->
type => string(),
%% so it's not logged
sensitive => true,
- desc => "Configure the license as a string"
+ desc => "License string"
}}
| common_fields()
];
@@ -53,6 +53,13 @@ fields(file_license) ->
| common_fields()
].
+desc(key_license) ->
+ "License provisioned as a string.";
+desc(file_license) ->
+ "License provisioned as a file.";
+desc(_) ->
+ undefined.
+
common_fields() ->
[
{connection_low_watermark, #{