Merge pull request #7683 from EMQ-YangM/rule-engine-doc

feat(rule-engine): add i18n support
This commit is contained in:
Yang Miao 2022-04-20 15:39:49 +08:00 committed by GitHub
commit 1dc9ddeac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1177 additions and 198 deletions

View File

@ -72,9 +72,9 @@ A host entry has the following form: `Host[:Port]`.<br/>
The MongoDB default port 27017 is used if `[:Port]` is not specified. The MongoDB default port 27017 is used if `[:Port]` is not specified.
""" """
zh: """ zh: """
集群要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>` 集群要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
对于每个节点,应为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/> 每个节点的配置为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/>
主机条目具有以下形式:`Host[:Port]`。<br/> 主机具有以下形式:`Host[:Port]`。<br/>
如果未指定 `[:Port]`,则使用 MongoDB 默认端口 27017。 如果未指定 `[:Port]`,则使用 MongoDB 默认端口 27017。
""" """
} }

View File

@ -72,9 +72,10 @@ A host entry has the following form: `Host[:Port]`.<br/>
The MongoDB default port 27017 is used if `[:Port]` is not specified. The MongoDB default port 27017 is used if `[:Port]` is not specified.
""" """
zh: """ zh: """
集群要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
对于每个节点,应为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/> 集群将要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].<br/>`
主机条目具有以下形式:`Host[:Port]`。<br/> 每个节点的配置为:将要连接的 IPv4 或 IPv6 地址或主机名。<br/>
主机名具有以下形式:`Host[:Port]`。<br/>
如果未指定 `[:Port]`,则使用 Redis 默认端口 6379。 如果未指定 `[:Port]`,则使用 Redis 默认端口 6379。
""" """
} }

View File

@ -0,0 +1,685 @@
emqx_rule_api_schema {
event_event_type {
desc {
en: "Event Type"
zh: "事件类型"
}
label: {
en: "Event Type"
zh: "事件类型"
}
}
event_id {
desc {
en: "Message ID"
zh: "消息 ID"
}
label: {
en: "Message ID"
zh: "消息 ID"
}
}
event_clientid {
desc {
en: "The Client ID"
zh: "客户端 ID"
}
label: {
en: "Client ID"
zh: "客户端 ID"
}
}
event_username {
desc {
en: "The User Name"
zh: ""
}
label: {
en: "Username"
zh: "用户名"
}
}
event_payload {
desc {
en: "The Message Payload"
zh: "消息负载"
}
label: {
en: "Message Payload"
zh: "消息负载"
}
}
event_peerhost {
desc {
en: "The IP Address of the Peer Client"
zh: "对等客户端的 IP 地址"
}
label: {
en: "Peer IP Address"
zh: "对等客户端的 IP"
}
}
event_topic {
desc {
en: "Message Topic"
zh: "消息主题"
}
label: {
en: "Message Topic"
zh: "消息主题"
}
}
event_publish_received_at {
desc {
en: "The Time that this Message is Received"
zh: "消息被接受的时间"
}
label: {
en: "Message Received Time"
zh: "消息被接受的时间"
}
}
event_qos {
desc {
en: "The Message QoS"
zh: "消息的 QoS"
}
label: {
en: "Message QoS"
zh: "消息 QoS"
}
}
event_from_clientid {
desc {
en: "The Client ID"
zh: "事件来源客户端的 ID"
}
label: {
en: "Client ID"
zh: "客户端 ID"
}
}
event_from_username {
desc {
en: "The User Name"
zh: "事件来源客户端的用户名"
}
label: {
en: "Username"
zh: "用户名"
}
}
event_mountpoint {
desc {
en: "The Mountpoint"
zh: "挂载点"
}
label: {
en: "Mountpoint"
zh: "挂载点"
}
}
event_peername {
desc {
en: "The IP Address and Port of the Peer Client"
zh: "对等客户端的 IP 地址和端口"
}
label: {
en: "IP Address And Port"
zh: "IP 地址和端口"
}
}
event_sockname {
desc {
en: "The IP Address and Port of the Local Listener"
zh: "本地监听的 IP 地址和端口"
}
label: {
en: "IP Address And Port"
zh: "IP 地址和端口"
}
}
event_proto_name {
desc {
en: "Protocol Name"
zh: "协议名称"
}
label: {
en: "Protocol Name"
zh: "协议名称"
}
}
event_proto_ver {
desc {
en: "Protocol Version"
zh: "协议版本"
}
label: {
en: "Protocol Version"
zh: "协议版本"
}
}
event_keepalive {
desc {
en: "KeepAlive"
zh: "保持连接"
}
label: {
en: "KeepAlive"
zh: "保持连接"
}
}
event_clean_start {
desc {
en: "Clean Start"
zh: "清除会话"
}
label: {
en: "Clean Start"
zh: "清除会话"
}
}
event_expiry_interval {
desc {
en: "Expiry Interval"
zh: "到期间隔"
}
label: {
en: "Expiry Interval"
zh: "到期间隔"
}
}
event_is_bridge {
desc {
en: "Is Bridge"
zh: "是否桥接"
}
label: {
en: "Is Bridge"
zh: "是否桥接"
}
}
event_connected_at {
desc {
en: "The Time that this Client is Connected"
zh: "客户端连接完成时的时刻"
}
label: {
en: "Connected Time"
zh: "连接完成时的时刻"
}
}
event_action {
desc {
en: "Publish or Subscribe"
zh: "订阅或发布"
}
label: {
en: "Publish or Subscribe"
zh: "订阅或发布"
}
}
event_authz_source {
desc {
en: "Cache, Plugs or Default"
zh: "缓存,插件或者默认值"
}
label: {
en: "Auth Source"
zh: "认证源"
}
}
event_result {
desc {
en: "Allow or Deny"
zh: "允许或禁止"
}
label: {
en: "Auth Result"
zh: "认证结果"
}
}
event_server {
desc {
en: "The IP address (or hostname) and port of the MQTT broker, in IP:Port format"
zh: "MQTT broker的 IP 地址(或主机名)和端口,采用 IP:Port 格式"
}
label: {
en: "Server IP And Port"
zh: "服务器 IP 地址和端口"
}
}
event_dup {
desc {
en: "The DUP flag of the MQTT message"
zh: "MQTT 消息的 DUP 标志"
}
label: {
en: "DUP Flag"
zh: "DUP 标志"
}
}
event_retain {
desc {
en: "If is a retain message"
zh: "是否是保留消息"
}
label: {
en: "Retain Message"
zh: "保留消息"
}
}
event_ctx_dropped {
desc {
en: "The Reason for Dropping"
zh: "消息被丢弃的原因"
}
label: {
en: "Dropped Reason"
zh: "丢弃原因"
}
}
event_ctx_disconnected_reason {
desc {
en: "The Reason for Disconnect"
zh: "断开连接的原因"
}
label: {
en: "Disconnect Reason"
zh: "断开连接原因"
}
}
event_ctx_disconnected_da {
desc {
en: "The Time that this Client is Disconnected"
zh: "客户端断开连接的时刻"
}
label: {
en: "Disconnected Time"
zh: "客户端断开连接时刻"
}
}
event_ctx_connack_reason_code {
desc {
en: "The reason code"
zh: "错误码"
}
label: {
en: "Reason Code"
zh: "错误码"
}
}
rule_id {
desc {
en: "The ID of the rule"
zh: "规则的 ID "
}
label: {
en: "Rule ID"
zh: "规则 ID "
}
}
node_node {
desc {
en: "The node name"
zh: "节点名字"
}
label: {
en: "Node Name"
zh: "节点名字"
}
}
metrics_sql_matched {
desc {
en: "How much times the FROM clause of the SQL is matched."
zh: "SQL 的 FROM 子句匹配的次数。"
}
label: {
en: "Matched"
zh: "命中数"
}
}
metrics_sql_matched_rate {
desc {
en: "The rate of matched, times/second"
zh: "命中速率,次/秒"
}
label: {
en: "命中速率"
zh: "Matched Rate"
}
}
metrics_sql_matched_rate_max {
desc {
en: "The max rate of matched, times/second"
zh: "最大命中速率,次/秒"
}
label: {
en: "Max Matched Rate"
zh: "最大命中速率"
}
}
metrics_sql_matched_rate_last5m {
desc {
en: "The average rate of matched in last 5 minutes, times/second"
zh: "5分钟平均命中速率次/秒"
}
label: {
en: "Average Matched Rate"
zh: "平均命中速率"
}
}
metrics_sql_passed {
desc {
en: "How much times the SQL is passed"
zh: "SQL 通过的次数"
}
label: {
en: "SQL Passed"
zh: "SQL 通过"
}
}
metrics_sql_failed {
desc {
en: "How much times the SQL is failed"
zh: "SQL 失败的次数"
}
label: {
en: "SQL Failed"
zh: "SQL 失败"
}
}
metrics_sql_failed_exception {
desc {
en: "How much times the SQL is failed due to exceptions. This may because of a crash when calling a SQL function, or trying to do arithmetic operation on undefined variables"
zh: "SQL 由于执行异常而失败的次数。 这可能是因为调用 SQL 函数时崩溃,或者试图对未定义的变量进行算术运算"
}
label: {
en: "SQL Exception"
zh: "SQL 执行异常"
}
}
metrics_sql_failed_unknown {
desc {
en: "How much times the SQL is failed due to an unknown error."
zh: "由于未知错误导致 SQL 失败的次数。"
}
label: {
en: "SQL Unknown Error"
zh: "SQL 未知错误"
}
}
metrics_outputs_total {
desc {
en: "How much times the outputs are called by the rule. This value may several times of 'sql.matched', depending on the number of the outputs of the rule."
zh: "规则调用输出的次数。 该值可能是“sql.matched”的几倍具体取决于规则输出的数量。"
}
label: {
en: "Output Total"
zh: "调用输出次数"
}
}
metrics_outputs_success {
desc {
en: "How much times the rule success to call the outputs."
zh: "规则成功调用输出的次数。"
}
label: {
en: "Success Output"
zh: "成功调用输出次数"
}
}
metrics_outputs_failed {
desc {
en: "How much times the rule failed to call the outputs."
zh: "规则调用输出失败的次数。"
}
label: {
en: "Failed Output"
zh: "调用输出失败次数"
}
}
metrics_outputs_failed_out_of_service {
desc {
en: "How much times the rule failed to call outputs due to the output is out of service. For example, a bridge is disabled or stopped."
zh: "由于输出停止服务而导致规则调用输出失败的次数。 例如,桥接被禁用或停止。"
}
label: {
en: "Fail Output"
zh: "调用输出失败次数"
}
}
metrics_outputs_failed_unknown {
desc {
en: "How much times the rule failed to call outputs due to to an unknown error."
zh: "由于未知错误,规则调用输出失败的次数。"
}
label: {
en: "Fail Output"
zh: "调用输出失败次数"
}
}
test_context {
desc {
en: "The context of the event for testing"
zh: "测试事件的上下文"
}
label: {
en: "Event Conetxt"
zh: "事件上下文"
}
}
test_sql {
desc {
en: "The SQL of the rule for testing"
zh: "测试的 SQL"
}
label: {
en: "Test SQL"
zh: "测试 SQL"
}
}
rs_event {
desc {
en: "The event topics"
zh: "事件主题"
}
label: {
en: "Event Topics"
zh: "事件主题"
}
}
rs_title {
desc {
en: "The title"
zh: "标题"
}
label: {
en: "Title"
zh: "标题"
}
}
rs_description {
desc {
en: "The description"
zh: "描述"
}
label: {
en: "Description"
zh: "描述"
}
}
rs_columns {
desc {
en: "The columns"
zh: "列"
}
label: {
en: "Column"
zh: "列"
}
}
rs_test_columns {
desc {
en: "The test columns"
zh: "测试列"
}
label: {
en: "Test Columns"
zh: "测试列"
}
}
rs_sql_example {
desc {
en: "The sql_example"
zh: "SQL 例子"
}
label: {
en: "SQL Example"
zh: "SQL 例子"
}
}
ri_metrics {
desc {
en: "The metrics of the rule"
zh: "规则的计数器"
}
label: {
en: "Rule Metrics"
zh: "规则计数器"
}
}
ri_node_metrics {
desc {
en: "The metrics of the rule for each node"
zh: "每个节点的规则计数器"
}
label: {
en: "Each Node Rule Metrics"
zh: "每个节点规则计数器"
}
}
ri_from {
desc {
en: "The topics of the rule"
zh: "规则指定的主题"
}
label: {
en: "Topics of Rule"
zh: "规则指定的主题"
}
}
ri_created_at {
desc {
en: "The created time of the rule"
zh: "规则创建时间"
}
label: {
en: "Rule Create Time"
zh: "规则创建时间"
}
}
root_rule_creation {
desc {
en: "Schema for creating rules"
zh: "用于创建规则的 Schema"
}
label: {
en: "Create Schema"
zh: "用于创建规则的 Schema"
}
}
root_rule_info {
desc {
en: "Schema for rule info"
zh: "用于规则信息的 Schema"
}
label: {
en: "Info Schema"
zh: "用于规则信息的 Schema"
}
}
root_rule_events {
desc {
en: "Schema for rule events"
zh: "用于事件的 Schema"
}
label: {
en: "Rule Events Schema"
zh: "用于规则事件的 Schema"
}
}
root_rule_test {
desc {
en: "Schema for testing rules"
zh: "用于规则测试的 Schema"
}
label: {
en: "Rule Test Schema"
zh: "用于规则测试的 Schema"
}
}
}

View File

@ -0,0 +1,100 @@
emqx_rule_engine_api {
api1 {
desc {
en: "List all rules"
zh: "列出所有规则"
}
label: {
en: "List All Rules"
zh: "列出所有规则"
}
}
api2 {
desc {
en: "Create a new rule using given Id"
zh: "通过指定 ID 创建规则"
}
label: {
en: "Create Rule By ID"
zh: "通过指定 ID 创建规则"
}
}
api3 {
desc {
en: "List all events can be used in rules"
zh: "列出所有能被规则使用的事件"
}
label: {
en: "List All Events Can Be Used In Rule"
zh: "列出所有能被规则使用的事件"
}
}
api4 {
desc {
en: "Get a rule by given Id"
zh: "通过 ID 查询规则"
}
label: {
en: "Get Rule"
zh: "查询规则"
}
}
api5 {
desc {
en: "Update a rule by given Id to all nodes in the cluster"
zh: "通过 ID 更新集群里所有节点上的规则"
}
label: {
en: "Update Cluster Rule"
zh: "更新集群规则"
}
}
api6 {
desc {
en: "Delete a rule by given Id from all nodes in the cluster"
zh: "通过 ID 删除集群里所有节点上的规则"
}
label: {
en: "Delete Cluster Rule"
zh: "删除集群规则"
}
}
api7 {
desc {
en: "Reset a rule metrics"
zh: "重置规则计数"
}
label: {
en: "Reset Rule Metrics"
zh: "重置规则计数"
}
}
api8 {
desc {
en: "Test a rule"
zh: "测试一个规则"
}
label: {
en: "Test Rule"
zh: "测试规则"
}
}
desc9 {
desc {
en: "List of rules"
zh: "列出所有规则"
}
label: {
en: "List Rules"
zh: "列出所有规则"
}
}
}

View File

@ -0,0 +1,242 @@
emqx_rule_engine_schema {
rules_name {
desc {
en: "The name of the rule"
zh: "规则名字"
}
label: {
en: "Rule Name"
zh: "规则名字"
}
}
rules_sql {
desc {
en: """
SQL query to transform the messages.<br>
Example: <code>SELECT * FROM "test/topic" WHERE payload.x = 1</code><br>
"""
zh: """
用于处理消息的 SQL 。<br>
示例:<code>SELECT * FROM "test/topic" WHERE payload.x = 1</code><br>
"""
}
label: {
en: "Rule SQL"
zh: "规则 SQL"
}
}
rules_outputs {
desc {
en: """
A list of outputs of the rule.<br>
An output can be a string that refers to the channel ID of an EMQX bridge, or an object
that refers to a function.<br>
There a some built-in functions like "republish" and "console", and we also support user
provided functions in the format: "{module}:{function}".<br>
The outputs in the list are executed sequentially.
This means that if one of the output is executing slowly, all the following outputs will not
be executed until it returns.<br>
If one of the output crashed, all other outputs come after it will still be executed, in the
original order.<br>
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.
"""
zh: """
规则的动作列表。<br>
动作可以是指向 EMQX bridge 的引用,也可以是一个指向函数的对象。<br>
我们支持一些内置函数如“republish”和“console”我们还支持用户提供的函数它的格式为“{module}:{function}”。<br>
列表中的动作按顺序执行。这意味着如果其中一个动作执行缓慢,则以下所有动作都不会被执行直到它返回。<br>
如果其中一个动作崩溃,在它之后的所有动作仍然会被按照原始顺序执行。<br>
如果运行动作时出现任何错误,则会出现错误消息,并且相应的计数器会增加。
"""
}
label: {
en: "Rule Action List"
zh: "动作列表"
}
}
rules_enable {
desc {
en: "Enable or disable the rule"
zh: "启用或禁用规则引擎"
}
label: {
en: "Enable Or Disable Rule"
zh: "启用或禁用规则引擎"
}
}
rules_description {
desc {
en: "The description of the rule"
zh: "规则的描述"
}
label: {
en: "Rule Description"
zh: "规则描述"
}
}
republish_function {
desc {
en: """Republish the message as a new MQTT message"""
zh: """将消息重新发布为新的 MQTT 消息"""
}
label: {
en: "Republish Function"
zh: "重新发布函数"
}
}
console_function {
desc {
en: """Print the outputs to the console"""
zh: "将输出打印到控制台"
}
label: {
en: "Console Function"
zh: "控制台函数"
}
}
user_provided_function_function {
desc {
en: """
The user provided function. Should be in the format: '{module}:{function}'.<br>
Where {module} is the Erlang callback module and {function} is the Erlang function.
<br>
To write your own function, checkout the function <code>console</code> and
<code>republish</code> in the source file:
<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
"""
zh: """
用户提供的函数。 格式应为:'{module}:{function}'。<br>
其中 {module} 是 Erlang 回调模块, {function} 是 Erlang 函数。<br>
要编写自己的函数,请检查源文件:<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> 中的示例函数 <code>console</code> 和<code>republish</code> 。
"""
}
label: {
en: "User Provided Function"
zh: "用户提供的函数"
}
}
user_provided_function_args {
desc {
en: """
The args will be passed as the 3rd argument to module:function/3,
checkout the function <code>console</code> and <code>republish</code> in the source file:
<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
"""
zh: """
用户提供的参数将作为函数 module:function/3 的第三个参数,
请检查源文件:<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> 中的示例函数 <code>console</code> 和<code>republish</code> 。
"""
}
label: {
en: "User Provided Function Args"
zh: "用户提供函数的参数"
}
}
republish_args_topic {
desc {
en: """
The target topic of message to be re-published.<br>
Template with variables is allowed, see description of the 'republish_args'.
"""
zh: """
重新发布消息的目标主题。<br>
允许使用带有变量的模板请参阅“republish_args”的描述。
"""
}
label: {
en: "Target Topic"
zh: "目标主题"
}
}
republish_args_qos {
desc {
en: """
The qos of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.<br>
Defaults to ${qos}. If variable ${qos} is not found from the selected result of the rule,
0 is used.
"""
zh: """
要重新发布的消息的 qos。允许使用带有变量的模板请参阅“republish_args”的描述。<br>
默认为 ${qos}。 如果从规则的选择结果中没有找到变量 ${qos},则使用 0。
"""
}
label: {
en: "Message QoS"
zh: "消息 QoS 等级"
}
}
republish_args_retain {
desc {
en: """
The 'retain' flag of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.<br>
Defaults to ${retain}. If variable ${retain} is not found from the selected result
of the rule, false is used.
"""
zh: """
要重新发布的消息的“保留”标志。允许使用带有变量的模板请参阅“republish_args”的描述。<br>
默认为 ${retain}。 如果从所选结果中未找到变量 ${retain},则使用 false。
"""
}
label: {
en: "Retain Flag"
zh: "保留消息标志"
}
}
republish_args_payload {
desc {
en: """
The payload of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.<br>.
Defaults to ${payload}. If variable ${payload} is not found from the selected result
of the rule, then the string "undefined" is used.
"""
zh: """
要重新发布的消息的有效负载。允许使用带有变量的模板请参阅“republish_args”的描述。<br>。
默认为 ${payload}。 如果从所选结果中未找到变量 ${payload},则使用字符串 "undefined"。
"""
}
label: {
en: "Message Payload"
zh: "消息负载"
}
}
rule_engine_ignore_sys_message {
desc {
en: "When set to 'true' (default), rule-engine will ignore messages published to $SYS topics."
zh: "当设置为“true”默认规则引擎将忽略发布到 $SYS 主题的消息。"
}
label: {
en: "Ignore Sys Message"
zh: "忽略系统消息"
}
}
rule_engine_rules {
desc {
en: """The rules"""
zh: "规则"
}
label: {
en: "Rules"
zh: "规则"
}
}
}

View File

@ -3,6 +3,7 @@
-behaviour(hocon_schema). -behaviour(hocon_schema).
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-export([ check_params/2 -export([ check_params/2
@ -30,10 +31,10 @@ check_params(Params, Tag) ->
%% Hocon Schema Definitions %% Hocon Schema Definitions
roots() -> roots() ->
[ {"rule_creation", sc(ref("rule_creation"), #{desc => "Schema for creating rules"})} [ {"rule_creation", sc(ref("rule_creation"), #{desc => ?DESC("root_rule_creation")})}
, {"rule_info", sc(ref("rule_info"), #{desc => "Schema for rule info"})} , {"rule_info", sc(ref("rule_info"), #{desc => ?DESC("root_rule_info")})}
, {"rule_events", sc(ref("rule_events"), #{desc => "Schema for rule events"})} , {"rule_events", sc(ref("rule_events"), #{desc => ?DESC("root_rule_events")})}
, {"rule_test", sc(ref("rule_test"), #{desc => "Schema for testing rules"})} , {"rule_test", sc(ref("rule_test"), #{desc => ?DESC("root_rule_test")})}
]. ].
fields("rule_creation") -> fields("rule_creation") ->
@ -41,14 +42,14 @@ fields("rule_creation") ->
fields("rule_info") -> fields("rule_info") ->
[ rule_id() [ rule_id()
, {"metrics", sc(ref("metrics"), #{desc => "The metrics of the rule"})} , {"metrics", sc(ref("metrics"), #{desc => ?DESC("ri_metrics")})}
, {"node_metrics", sc(hoconsc:array(ref("node_metrics")), , {"node_metrics", sc(hoconsc:array(ref("node_metrics")),
#{ desc => "The metrics of the rule for each node" #{ desc => ?DESC("ri_node_metrics")
})} })}
, {"from", sc(hoconsc:array(binary()), , {"from", sc(hoconsc:array(binary()),
#{desc => "The topics of the rule", example => "t/#"})} #{desc => ?DESC("ri_from"), example => "t/#"})}
, {"created_at", sc(binary(), , {"created_at", sc(binary(),
#{ desc => "The created time of the rule" #{ desc => ?DESC("ri_created_at")
, example => "2021-12-01T15:00:43.153+08:00" , example => "2021-12-01T15:00:43.153+08:00"
})} })}
] ++ fields("rule_creation"); ] ++ fields("rule_creation");
@ -56,12 +57,12 @@ fields("rule_info") ->
%% TODO: we can delete this API if the Dashboard not depends on it %% TODO: we can delete this API if the Dashboard not depends on it
fields("rule_events") -> fields("rule_events") ->
ETopics = [binary_to_atom(emqx_rule_events:event_topic(E)) || E <- emqx_rule_events:event_names()], ETopics = [binary_to_atom(emqx_rule_events:event_topic(E)) || E <- emqx_rule_events:event_names()],
[ {"event", sc(hoconsc:enum(ETopics), #{desc => "The event topics", required => true})} [ {"event", sc(hoconsc:enum(ETopics), #{desc => ?DESC("rs_event"), required => true})}
, {"title", sc(binary(), #{desc => "The title", example => "some title"})} , {"title", sc(binary(), #{desc => ?DESC("rs_title"), example => "some title"})}
, {"description", sc(binary(), #{desc => "The description", example => "some desc"})} , {"description", sc(binary(), #{desc => ?DESC("rs_description"), example => "some desc"})}
, {"columns", sc(map(), #{desc => "The columns"})} , {"columns", sc(map(), #{desc => ?DESC("rs_columns")})}
, {"test_columns", sc(map(), #{desc => "The test columns"})} , {"test_columns", sc(map(), #{desc => ?DESC("rs_test_columns")})}
, {"sql_example", sc(binary(), #{desc => "The sql_example"})} , {"sql_example", sc(binary(), #{desc => ?DESC("rs_sql_example")})}
]; ];
fields("rule_test") -> fields("rule_test") ->
@ -77,183 +78,177 @@ fields("rule_test") ->
, ref("ctx_check_authz_complete") , ref("ctx_check_authz_complete")
, ref("ctx_bridge_mqtt") , ref("ctx_bridge_mqtt")
]), ]),
#{desc => "The context of the event for testing", #{desc => ?DESC("test_context"),
default => #{}})} default => #{}})}
, {"sql", sc(binary(), #{desc => "The SQL of the rule for testing", required => true})} , {"sql", sc(binary(), #{desc => ?DESC("test_sql"), required => true})}
]; ];
fields("metrics") -> fields("metrics") ->
[ {"sql.matched", sc(non_neg_integer(), #{ [ {"sql.matched", sc(non_neg_integer(), #{
desc => "How much times the FROM clause of the SQL is matched." desc => ?DESC("metrics_sql_matched")
})} })}
, {"sql.matched.rate", sc(float(), #{desc => "The rate of matched, times/second"})} , {"sql.matched.rate", sc(float(), #{desc => ?DESC("metrics_sql_matched_rate") })}
, {"sql.matched.rate.max", sc(float(), #{desc => "The max rate of matched, times/second"})} , {"sql.matched.rate.max", sc(float(), #{desc => ?DESC("metrics_sql_matched_rate_max") })}
, {"sql.matched.rate.last5m", sc(float(), , {"sql.matched.rate.last5m", sc(float(),
#{desc => "The average rate of matched in last 5 minutes, times/second"})} #{desc => ?DESC("metrics_sql_matched_rate_last5m") })}
, {"sql.passed", sc(non_neg_integer(), #{desc => "How much times the SQL is passed"})} , {"sql.passed", sc(non_neg_integer(), #{desc => ?DESC("metrics_sql_passed") })}
, {"sql.failed", sc(non_neg_integer(), #{desc => "How much times the SQL is failed"})} , {"sql.failed", sc(non_neg_integer(), #{desc => ?DESC("metrics_sql_failed") })}
, {"sql.failed.exception", sc(non_neg_integer(), #{ , {"sql.failed.exception", sc(non_neg_integer(), #{
desc => "How much times the SQL is failed due to exceptions. " desc => ?DESC("metrics_sql_failed_exception")
"This may because of a crash when calling a SQL function, or "
"trying to do arithmetic operation on undefined variables"
})} })}
, {"sql.failed.unknown", sc(non_neg_integer(), #{ , {"sql.failed.unknown", sc(non_neg_integer(), #{
desc => "How much times the SQL is failed due to an unknown error." desc => ?DESC("metrics_sql_failed_unknown")
})} })}
, {"outputs.total", sc(non_neg_integer(), #{ , {"outputs.total", sc(non_neg_integer(), #{
desc => "How much times the outputs are called by the rule. " desc => ?DESC("metrics_outputs_total")
"This value may several times of 'sql.matched', depending on the "
"number of the outputs of the rule."
})} })}
, {"outputs.success", sc(non_neg_integer(), #{ , {"outputs.success", sc(non_neg_integer(), #{
desc => "How much times the rule success to call the outputs." desc => ?DESC("metrics_outputs_success")
})} })}
, {"outputs.failed", sc(non_neg_integer(), #{ , {"outputs.failed", sc(non_neg_integer(), #{
desc => "How much times the rule failed to call the outputs." desc => ?DESC("metrics_outputs_failed")
})} })}
, {"outputs.failed.out_of_service", sc(non_neg_integer(), #{ , {"outputs.failed.out_of_service", sc(non_neg_integer(), #{
desc => "How much times the rule failed to call outputs due to the output is " desc => ?DESC("metrics_outputs_failed_out_of_service")
"out of service. For example, a bridge is disabled or stopped."
})} })}
, {"outputs.failed.unknown", sc(non_neg_integer(), #{ , {"outputs.failed.unknown", sc(non_neg_integer(), #{
desc => "How much times the rule failed to call outputs due to to an unknown error." desc => ?DESC("metrics_outputs_failed_unknown")
})} })}
]; ];
fields("node_metrics") -> fields("node_metrics") ->
[ {"node", sc(binary(), #{desc => "The node name", example => "emqx@127.0.0.1"})} [ {"node", sc(binary(), #{desc => ?DESC("node_node"), example => "emqx@127.0.0.1"})}
] ++ fields("metrics"); ] ++ fields("metrics");
fields("ctx_pub") -> fields("ctx_pub") ->
[ {"event_type", sc(message_publish, #{desc => "Event Type", required => true})} [ {"event_type", sc(message_publish, #{desc => ?DESC("event_event_type"), required => true})}
, {"id", sc(binary(), #{desc => "Message ID"})} , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"payload", sc(binary(), #{desc => "The Message Payload"})} , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
, {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})} , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
, {"topic", sc(binary(), #{desc => "Message Topic"})} , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
, {"publish_received_at", sc(integer(), #{ , {"publish_received_at", sc(integer(), #{
desc => "The Time that this Message is Received"})} desc => ?DESC("event_publish_received_at")})}
] ++ [qos()]; ] ++ [qos()];
fields("ctx_sub") -> fields("ctx_sub") ->
[ {"event_type", sc(session_subscribed, #{desc => "Event Type", required => true})} [ {"event_type", sc(session_subscribed, #{desc => ?DESC("event_event_type"), required => true})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"payload", sc(binary(), #{desc => "The Message Payload"})} , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
, {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})} , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
, {"topic", sc(binary(), #{desc => "Message Topic"})} , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
, {"publish_received_at", sc(integer(), #{ , {"publish_received_at", sc(integer(), #{
desc => "The Time that this Message is Received"})} desc => ?DESC("event_publish_received_at")})}
] ++ [qos()]; ] ++ [qos()];
fields("ctx_unsub") -> fields("ctx_unsub") ->
[{"event_type", sc(session_unsubscribed, #{desc => "Event Type", required => true})}] ++ [{"event_type", sc(session_unsubscribed, #{desc => ?DESC("event_event_type"), required => true})}] ++
proplists:delete("event_type", fields("ctx_sub")); proplists:delete("event_type", fields("ctx_sub"));
fields("ctx_delivered") -> fields("ctx_delivered") ->
[ {"event_type", sc(message_delivered, #{desc => "Event Type", required => true})} [ {"event_type", sc(message_delivered, #{desc => ?DESC("event_event_type"), required => true})}
, {"id", sc(binary(), #{desc => "Message ID"})} , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
, {"from_clientid", sc(binary(), #{desc => "The Client ID"})} , {"from_clientid", sc(binary(), #{desc => ?DESC("event_from_clientid")})}
, {"from_username", sc(binary(), #{desc => "The User Name"})} , {"from_username", sc(binary(), #{desc => ?DESC("event_from_username")})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"payload", sc(binary(), #{desc => "The Message Payload"})} , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
, {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})} , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
, {"topic", sc(binary(), #{desc => "Message Topic"})} , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
, {"publish_received_at", sc(integer(), #{ , {"publish_received_at", sc(integer(), #{
desc => "The Time that this Message is Received"})} desc => ?DESC("event_publish_received_at")})}
] ++ [qos()]; ] ++ [qos()];
fields("ctx_acked") -> fields("ctx_acked") ->
[{"event_type", sc(message_acked, #{desc => "Event Type", required => true})}] ++ [{"event_type", sc(message_acked, #{desc => ?DESC("event_event_type"), required => true})}] ++
proplists:delete("event_type", fields("ctx_delivered")); proplists:delete("event_type", fields("ctx_delivered"));
fields("ctx_dropped") -> fields("ctx_dropped") ->
[ {"event_type", sc(message_dropped, #{desc => "Event Type", required => true})} [ {"event_type", sc(message_dropped, #{desc => ?DESC("event_event_type"), required => true})}
, {"id", sc(binary(), #{desc => "Message ID"})} , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
, {"reason", sc(binary(), #{desc => "The Reason for Dropping"})} , {"reason", sc(binary(), #{desc => ?DESC("event_ctx_dropped")})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"payload", sc(binary(), #{desc => "The Message Payload"})} , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
, {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})} , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
, {"topic", sc(binary(), #{desc => "Message Topic"})} , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
, {"publish_received_at", sc(integer(), #{ , {"publish_received_at", sc(integer(), #{
desc => "The Time that this Message is Received"})} desc => ?DESC("event_publish_received_at")})}
] ++ [qos()]; ] ++ [qos()];
fields("ctx_connected") -> fields("ctx_connected") ->
[ {"event_type", sc(client_connected, #{desc => "Event Type", required => true})} [ {"event_type", sc(client_connected, #{desc => ?DESC("event_event_type"), required => true})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"mountpoint", sc(binary(), #{desc => "The Mountpoint"})} , {"mountpoint", sc(binary(), #{desc => ?DESC("event_mountpoint")})}
, {"peername", sc(binary(), #{desc => "The IP Address and Port of the Peer Client"})} , {"peername", sc(binary(), #{desc => ?DESC("event_peername")})}
, {"sockname", sc(binary(), #{desc => "The IP Address and Port of the Local Listener"})} , {"sockname", sc(binary(), #{desc => ?DESC("event_sockname")})}
, {"proto_name", sc(binary(), #{desc => "Protocol Name"})} , {"proto_name", sc(binary(), #{desc => ?DESC("event_proto_name")})}
, {"proto_ver", sc(binary(), #{desc => "Protocol Version"})} , {"proto_ver", sc(binary(), #{desc => ?DESC("event_proto_ver")})}
, {"keepalive", sc(integer(), #{desc => "KeepAlive"})} , {"keepalive", sc(integer(), #{desc => ?DESC("event_keepalive")})}
, {"clean_start", sc(boolean(), #{desc => "Clean Start", default => true})} , {"clean_start", sc(boolean(), #{desc => ?DESC("event_clean_start"), default => true})}
, {"expiry_interval", sc(integer(), #{desc => "Expiry Interval"})} , {"expiry_interval", sc(integer(), #{desc => ?DESC("event_expiry_interval")})}
, {"is_bridge", sc(boolean(), #{desc => "Is Bridge", default => false})} , {"is_bridge", sc(boolean(), #{desc => ?DESC("event_is_bridge"), default => false})}
, {"connected_at", sc(integer(), #{ , {"connected_at", sc(integer(), #{
desc => "The Time that this Client is Connected"})} desc => ?DESC("event_connected_at")})}
]; ];
fields("ctx_disconnected") -> fields("ctx_disconnected") ->
[ {"event_type", sc(client_disconnected, #{desc => "Event Type", required => true})} [ {"event_type", sc(client_disconnected, #{desc => ?DESC("event_event_type"), required => true})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"reason", sc(binary(), #{desc => "The Reason for Disconnect"})} , {"reason", sc(binary(), #{desc => ?DESC("event_ctx_disconnected_reason")})}
, {"peername", sc(binary(), #{desc => "The IP Address and Port of the Peer Client"})} , {"peername", sc(binary(), #{desc => ?DESC("event_peername")})}
, {"sockname", sc(binary(), #{desc => "The IP Address and Port of the Local Listener"})} , {"sockname", sc(binary(), #{desc => ?DESC("event_sockname")})}
, {"disconnected_at", sc(integer(), #{ , {"disconnected_at", sc(integer(), #{
desc => "The Time that this Client is Disconnected"})} desc => ?DESC("event_ctx_disconnected_da")})}
]; ];
fields("ctx_connack") -> fields("ctx_connack") ->
[ {"event_type", sc(client_connack, #{desc => "Event Type", required => true})} [ {"event_type", sc(client_connack, #{desc => ?DESC("event_event_type"), required => true})}
, {"reason_code", sc(binary(), #{desc => "The reason code"})} , {"reason_code", sc(binary(), #{desc => ?DESC("event_ctx_connack_reason_code")})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"clean_start", sc(boolean(), #{desc => "Clean Start", default => true})} , {"clean_start", sc(boolean(), #{desc => ?DESC("event_clean_start"), default => true})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"peername", sc(binary(), #{desc => "The IP Address and Port of the Peer Client"})} , {"peername", sc(binary(), #{desc => ?DESC("event_peername")})}
, {"sockname", sc(binary(), #{desc => "The IP Address and Port of the Local Listener"})} , {"sockname", sc(binary(), #{desc => ?DESC("event_sockname")})}
, {"proto_name", sc(binary(), #{desc => "Protocol Name"})} , {"proto_name", sc(binary(), #{desc => ?DESC("event_proto_name")})}
, {"proto_ver", sc(binary(), #{desc => "Protocol Version"})} , {"proto_ver", sc(binary(), #{desc => ?DESC("event_proto_ver")})}
, {"keepalive", sc(integer(), #{desc => "KeepAlive"})} , {"keepalive", sc(integer(), #{desc => ?DESC("event_keepalive")})}
, {"expiry_interval", sc(integer(), #{desc => "Expiry Interval"})} , {"expiry_interval", sc(integer(), #{desc => ?DESC("event_expiry_interval")})}
, {"connected_at", sc(integer(), #{ , {"connected_at", sc(integer(), #{
desc => "The Time that this Client is Connected"})} desc => ?DESC("event_connected_at")})}
]; ];
fields("ctx_check_authz_complete") -> fields("ctx_check_authz_complete") ->
[ {"event_type", sc(client_check_authz_complete, #{desc => "Event Type", required => true})} [ {"event_type", sc(client_check_authz_complete, #{desc => ?DESC("event_event_type"), required => true})}
, {"clientid", sc(binary(), #{desc => "The Client ID"})} , {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})}
, {"username", sc(binary(), #{desc => "The User Name"})} , {"username", sc(binary(), #{desc => ?DESC("event_username")})}
, {"peerhost", sc(binary(), #{desc => "The IP Address of the Peer Client"})} , {"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})}
, {"topic", sc(binary(), #{desc => "Message Topic"})} , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
, {"action", sc(binary(), #{desc => "Publish or Subscribe"})} , {"action", sc(binary(), #{desc => ?DESC("event_action")})}
, {"authz_source", sc(binary(), #{desc => "Cache, Plugs or Default"})} , {"authz_source", sc(binary(), #{desc => ?DESC("event_authz_source")})}
, {"result", sc(binary(), #{desc => "Allow or Deny"})} , {"result", sc(binary(), #{desc => ?DESC("event_result")})}
]; ];
fields("ctx_bridge_mqtt") -> fields("ctx_bridge_mqtt") ->
[ {"event_type", sc('$bridges/mqtt:*', #{desc => "Event Type", required => true})} [ {"event_type", sc('$bridges/mqtt:*', #{desc => ?DESC("event_event_type"), required => true})}
, {"id", sc(binary(), #{desc => "Message ID"})} , {"id", sc(binary(), #{desc => ?DESC("event_id")})}
, {"payload", sc(binary(), #{desc => "The Message Payload"})} , {"payload", sc(binary(), #{desc => ?DESC("event_payload")})}
, {"topic", sc(binary(), #{desc => "Message Topic"})} , {"topic", sc(binary(), #{desc => ?DESC("event_topic")})}
, {"server", sc(binary(), #{desc => "The IP address (or hostname) and port of the MQTT broker," , {"server", sc(binary(), #{desc => ?DESC("event_server")})}
" in IP:Port format"})} , {"dup", sc(binary(), #{desc => ?DESC("event_dup")})}
, {"dup", sc(binary(), #{desc => "The DUP flag of the MQTT message"})} , {"retain", sc(binary(), #{desc => ?DESC("event_retain")})}
, {"retain", sc(binary(), #{desc => "If is a retain message"})}
, {"message_received_at", sc(integer(), #{ , {"message_received_at", sc(integer(), #{
desc => "The Time that this Message is Received"})} desc => ?DESC("event_publish_received_at")})}
] ++ [qos()]. ] ++ [qos()].
qos() -> qos() ->
{"qos", sc(emqx_schema:qos(), #{desc => "The Message QoS"})}. {"qos", sc(emqx_schema:qos(), #{desc => ?DESC("event_qos")})}.
rule_id() -> rule_id() ->
{"id", sc(binary(), {"id", sc(binary(),
#{ desc => "The ID of the rule", required => true #{ desc => ?DESC("rule_id"), required => true
, example => "293fb66f" , example => "293fb66f"
})}. })}.

View File

@ -18,6 +18,7 @@
-include("rule_engine.hrl"). -include("rule_engine.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("hocon/include/hoconsc.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-behaviour(minirest_api). -behaviour(minirest_api).
@ -102,14 +103,14 @@ schema("/rules") ->
'operationId' => '/rules', 'operationId' => '/rules',
get => #{ get => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"List all rules">>, description => ?DESC("api1"),
summary => <<"List Rules">>, summary => <<"List Rules">>,
responses => #{ responses => #{
200 => mk(array(rule_info_schema()), #{desc => "List of rules"}) 200 => mk(array(rule_info_schema()), #{desc => ?DESC("desc9")})
}}, }},
post => #{ post => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"Create a new rule using given Id">>, description => ?DESC("api2"),
summary => <<"Create a Rule">>, summary => <<"Create a Rule">>,
'requestBody' => rule_creation_schema(), 'requestBody' => rule_creation_schema(),
responses => #{ responses => #{
@ -123,7 +124,7 @@ schema("/rule_events") ->
'operationId' => '/rule_events', 'operationId' => '/rule_events',
get => #{ get => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"List all events can be used in rules">>, description => ?DESC("api3"),
summary => <<"List Events">>, summary => <<"List Events">>,
responses => #{ responses => #{
200 => mk(ref(emqx_rule_api_schema, "rule_events"), #{}) 200 => mk(ref(emqx_rule_api_schema, "rule_events"), #{})
@ -136,7 +137,7 @@ schema("/rules/:id") ->
'operationId' => '/rules/:id', 'operationId' => '/rules/:id',
get => #{ get => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"Get a rule by given Id">>, description => ?DESC("api4"),
summary => <<"Get a Rule">>, summary => <<"Get a Rule">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
@ -146,7 +147,7 @@ schema("/rules/:id") ->
}, },
put => #{ put => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"Update a rule by given Id to all nodes in the cluster">>, description => ?DESC("api5"),
summary => <<"Update a Rule">>, summary => <<"Update a Rule">>,
parameters => param_path_id(), parameters => param_path_id(),
'requestBody' => rule_creation_schema(), 'requestBody' => rule_creation_schema(),
@ -157,7 +158,7 @@ schema("/rules/:id") ->
}, },
delete => #{ delete => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"Delete a rule by given Id from all nodes in the cluster">>, description => ?DESC("api6"),
summary => <<"Delete a Rule">>, summary => <<"Delete a Rule">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
@ -171,7 +172,7 @@ schema("/rules/:id/reset_metrics") ->
'operationId' => '/rules/:id/reset_metrics', 'operationId' => '/rules/:id/reset_metrics',
put => #{ put => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"Reset a rule metrics">>, description => ?DESC("api7"),
summary => <<"Reset a Rule Metrics">>, summary => <<"Reset a Rule Metrics">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
@ -186,7 +187,7 @@ schema("/rule_test") ->
'operationId' => '/rule_test', 'operationId' => '/rule_test',
post => #{ post => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => <<"Test a rule">>, description => ?DESC("api8"),
summary => <<"Test a Rule">>, summary => <<"Test a Rule">>,
'requestBody' => rule_test_schema(), 'requestBody' => rule_test_schema(),
responses => #{ responses => #{

View File

@ -17,6 +17,7 @@
-module(emqx_rule_engine_schema). -module(emqx_rule_engine_schema).
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl").
-behaviour(hocon_schema). -behaviour(hocon_schema).
@ -34,38 +35,21 @@ namespace() -> rule_engine.
roots() -> ["rule_engine"]. roots() -> ["rule_engine"].
fields("rule_engine") -> fields("rule_engine") ->
[ {ignore_sys_message, sc(boolean(), #{default => true, desc => [ {ignore_sys_message, sc(boolean(), #{default => true, desc => ?DESC("rule_engine_ignore_sys_message")
"When set to 'true' (default), rule-engine will ignore messages published to $SYS topics."
})} })}
, {rules, sc(hoconsc:map("id", ref("rules")), #{desc => "The rules", default => #{}})} , {rules, sc(hoconsc:map("id", ref("rules")), #{desc => ?DESC("rule_engine_rules"), default => #{}})}
]; ];
fields("rules") -> fields("rules") ->
[ rule_name() [ rule_name()
, {"sql", sc(binary(), , {"sql", sc(binary(),
#{ desc => " #{ desc => ?DESC("rules_sql")
SQL query to transform the messages.<br>
Example: <code>SELECT * FROM \"test/topic\" WHERE payload.x = 1</code><br>
"
, example => "SELECT * FROM \"test/topic\" WHERE payload.x = 1" , example => "SELECT * FROM \"test/topic\" WHERE payload.x = 1"
, required => true , required => true
, validator => fun ?MODULE:validate_sql/1 , validator => fun ?MODULE:validate_sql/1
})} })}
, {"outputs", sc(hoconsc:array(hoconsc:union(outputs())), , {"outputs", sc(hoconsc:array(hoconsc:union(outputs())),
#{ desc => " #{ desc => ?DESC("rules_outputs")
A list of outputs of the rule.<br>
An output can be a string that refers to the channel ID of an EMQX bridge, or an object
that refers to a function.<br>
There a some built-in functions like \"republish\" and \"console\", and we also support user
provided functions in the format: \"{module}:{function}\".<br>
The outputs in the list are executed sequentially.
This means that if one of the output is executing slowly, all the following outputs will not
be executed until it returns.<br>
If one of the output crashed, all other outputs come after it will still be executed, in the
original order.<br>
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 => [] , default => []
, example => [ , example => [
<<"http:my_http_bridge">>, <<"http:my_http_bridge">>,
@ -74,21 +58,21 @@ counter of the function output or the bridge channel will increase.
#{function => console} #{function => console}
] ]
})} })}
, {"enable", sc(boolean(), #{desc => "Enable or disable the rule", default => true})} , {"enable", sc(boolean(), #{desc => ?DESC("rules_enable"), default => true})}
, {"description", sc(binary(), , {"description", sc(binary(),
#{ desc => "The description of the rule" #{ desc => ?DESC("rules_description")
, example => "Some description" , example => "Some description"
, default => <<>> , default => <<>>
})} })}
]; ];
fields("builtin_output_republish") -> fields("builtin_output_republish") ->
[ {function, sc(republish, #{desc => "Republish the message as a new MQTT message"})} [ {function, sc(republish, #{desc => ?DESC("republish_function")})}
, {args, sc(ref("republish_args"), #{default => #{}})} , {args, sc(ref("republish_args"), #{default => #{}})}
]; ];
fields("builtin_output_console") -> fields("builtin_output_console") ->
[ {function, sc(console, #{desc => "Print the outputs to the console"})} [ {function, sc(console, #{desc => ?DESC("console_function")})}
%% we may support some args for the console output in the future %% we may support some args for the console output in the future
%, {args, sc(map(), #{desc => "The arguments of the built-in 'console' output", %, {args, sc(map(), #{desc => "The arguments of the built-in 'console' output",
% default => #{}})} % default => #{}})}
@ -96,62 +80,33 @@ fields("builtin_output_console") ->
fields("user_provided_function") -> fields("user_provided_function") ->
[ {function, sc(binary(), [ {function, sc(binary(),
#{ desc => " #{ desc => ?DESC("user_provided_function_function")
The user provided function. Should be in the format: '{module}:{function}'.<br>
Where {module} is the Erlang callback module and {function} is the Erlang function.
<br>
To write your own function, checkout the function <code>console</code> and
<code>republish</code> in the source file:
<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
"
, example => "module:function" , example => "module:function"
})} })}
, {args, sc(map(), , {args, sc(map(),
#{ desc => " #{ desc => ?DESC("user_provided_function_args")
The args will be passed as the 3rd argument to module:function/3,
checkout the function <code>console</code> and <code>republish</code> in the source file:
<code>apps/emqx_rule_engine/src/emqx_rule_outputs.erl</code> as an example.
"
, default => #{} , default => #{}
})} })}
]; ];
fields("republish_args") -> fields("republish_args") ->
[ {topic, sc(binary(), [ {topic, sc(binary(),
#{ desc =>" #{ desc => ?DESC("republish_args_topic")
The target topic of message to be re-published.<br>
Template with variables is allowed, see description of the 'republish_args'.
"
, required => true , required => true
, example => <<"a/1">> , example => <<"a/1">>
})} })}
, {qos, sc(qos(), , {qos, sc(qos(),
#{ desc => " #{ desc => ?DESC("republish_args_qos")
The qos of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.<br>
Defaults to ${qos}. If variable ${qos} is not found from the selected result of the rule,
0 is used.
"
, default => <<"${qos}">> , default => <<"${qos}">>
, example => <<"${qos}">> , example => <<"${qos}">>
})} })}
, {retain, sc(hoconsc:union([binary(), boolean()]), , {retain, sc(hoconsc:union([binary(), boolean()]),
#{ desc => " #{ desc => ?DESC("republish_args_retain")
The 'retain' flag of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.<br>
Defaults to ${retain}. If variable ${retain} is not found from the selected result
of the rule, false is used.
"
, default => <<"${retain}">> , default => <<"${retain}">>
, example => <<"${retain}">> , example => <<"${retain}">>
})} })}
, {payload, sc(binary(), , {payload, sc(binary(),
#{ desc => " #{ desc => ?DESC("republish_args_payload")
The payload of the message to be re-published.
Template with variables is allowed, see description of the 'republish_args'.<br>.
Defaults to ${payload}. If variable ${payload} is not found from the selected result
of the rule, then the string \"undefined\" is used.
"
, default => <<"${payload}">> , default => <<"${payload}">>
, example => <<"${payload}">> , example => <<"${payload}">>
})} })}
@ -191,7 +146,7 @@ desc(_) ->
rule_name() -> rule_name() ->
{"name", sc(binary(), {"name", sc(binary(),
#{ desc => "The name of the rule" #{ desc => ?DESC("rules_name")
, default => "" , default => ""
, required => true , required => true
, example => "foo" , example => "foo"