This is an automated email from the ASF dual-hosted git repository.
shreemaanabhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new d6e288b0f feat(limit-count): support configuring multiple rules
(#12977)
d6e288b0f is described below
commit d6e288b0f3941f559415cf600ec00a03f826626c
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Tue Feb 10 11:25:07 2026 +0545
feat(limit-count): support configuring multiple rules (#12977)
---
apisix/plugins/limit-count/init.lua | 176 ++++++++++++----
docs/en/latest/plugins/limit-count.md | 8 +-
docs/zh/latest/plugins/limit-count.md | 8 +-
t/admin/consumer-group.t | 2 +-
t/admin/plugin-configs.t | 2 +-
t/plugin/limit-count-rules.t | 380 ++++++++++++++++++++++++++++++++++
t/plugin/limit-count-variable.t | 2 -
t/plugin/limit-count.t | 4 +-
t/plugin/workflow.t | 4 +-
9 files changed, 533 insertions(+), 53 deletions(-)
diff --git a/apisix/plugins/limit-count/init.lua
b/apisix/plugins/limit-count/init.lua
index 7d5fe7ca9..1a1427e10 100644
--- a/apisix/plugins/limit-count/init.lua
+++ b/apisix/plugins/limit-count/init.lua
@@ -25,6 +25,7 @@ local get_phase = ngx.get_phase
local tonumber = tonumber
local type = type
local tostring = tostring
+local str_format = string.format
local limit_redis_cluster_new
local limit_redis_new
@@ -82,6 +83,28 @@ local schema = {
{type = "string"},
},
},
+ rules = {
+ type = "array",
+ items = {
+ type = "object",
+ properties = {
+ count = {
+ oneOf = {
+ {type = "integer", exclusiveMinimum = 0},
+ {type = "string"},
+ },
+ },
+ time_window = {
+ oneOf = {
+ {type = "integer", exclusiveMinimum = 0},
+ {type = "string"},
+ },
+ },
+ key = {type = "string"},
+ },
+ required = {"count", "time_window", "key"},
+ },
+ },
group = {type = "string"},
key = {type = "string", default = "remote_addr"},
key_type = {type = "string",
@@ -102,7 +125,14 @@ local schema = {
allow_degradation = {type = "boolean", default = false},
show_limit_quota_header = {type = "boolean", default = true}
},
- required = {"count", "time_window"},
+ oneOf = {
+ {
+ required = {"count", "time_window"},
+ },
+ {
+ required = {"rules"},
+ }
+ },
["if"] = {
properties = {
policy = {
@@ -180,51 +210,34 @@ function _M.check_schema(conf, schema_type)
end
end
- return true
-end
-
-
-local function create_limit_obj(conf, ctx, plugin_name)
- core.log.info("create new " .. plugin_name .. " plugin instance")
-
- local count = conf.count
- if type(count) == "string" then
- local err, _
- count, err, _ = core.utils.resolve_var(count, ctx.var)
- if err then
- return nil, "could not resolve vars in count: " .. err
- end
- count = tonumber(count)
- if not count then
- return nil, "resolved count is not a number: " .. tostring(count)
+ local keys = {}
+ for _, rule in ipairs(conf.rules or {}) do
+ if keys[rule.key] then
+ return false, str_format("duplicate key '%s' in rules", rule.key)
end
+ keys[rule.key] = true
end
- local time_window = conf.time_window
- if type(time_window) == "string" then
- local err, _
- time_window, err, _ = core.utils.resolve_var(time_window, ctx.var)
- if err then
- return nil, "could not resolve vars in time_window: " .. err
- end
- time_window = tonumber(time_window)
- if not time_window then
- return nil, "resolved time_window is not a number: " ..
tostring(time_window)
- end
- end
+ return true
+end
+
- core.log.info("limit count: ", count, ", time_window: ", time_window)
+local function create_limit_obj(conf, rule, plugin_name)
+ core.log.info("create new ", plugin_name, " plugin instance",
+ ", rule: ", core.json.delay_encode(rule, true))
if not conf.policy or conf.policy == "local" then
- return limit_local_new("plugin-" .. plugin_name, count, time_window)
+ return limit_local_new("plugin-" .. plugin_name, rule.count,
+ rule.time_window)
end
if conf.policy == "redis" then
- return limit_redis_new("plugin-" .. plugin_name, count, time_window,
conf)
+ return limit_redis_new("plugin-" .. plugin_name, rule.count,
rule.time_window, conf)
end
if conf.policy == "redis-cluster" then
- return limit_redis_cluster_new("plugin-" .. plugin_name, count,
time_window, conf)
+ return limit_redis_cluster_new("plugin-" .. plugin_name, rule.count,
+ rule.time_window, conf)
end
return nil
@@ -258,11 +271,71 @@ local function gen_limit_key(conf, ctx, key)
end
-function _M.rate_limit(conf, ctx, name, cost, dry_run)
- core.log.info("ver: ", ctx.conf_version)
- core.log.info("conf: ", core.json.delay_encode(conf, true))
+local function resolve_var(ctx, value)
+ if type(value) == "string" then
+ local err, _
+ value, err, _ = core.utils.resolve_var(value, ctx.var)
+ if err then
+ return nil, "could not resolve var for value: " .. value .. ",
err: " .. err
+ end
+ value = tonumber(value)
+ if not value then
+ return nil, "resolved value is not a number: " .. tostring(value)
+ end
+ end
+ return value
+end
- local lim, err = create_limit_obj(conf, ctx, name)
+
+local function get_rules(ctx, conf)
+ if not conf.rules then
+ local count, err = resolve_var(ctx, conf.count)
+ if err then
+ return nil, err
+ end
+ local time_window, err2 = resolve_var(ctx, conf.time_window)
+ if err2 then
+ return nil, err2
+ end
+ return {
+ {
+ count = count,
+ time_window = time_window,
+ key = conf.key,
+ key_type = conf.key_type,
+ }
+ }
+ end
+
+ local rules = {}
+ for _, rule in ipairs(conf.rules) do
+ local count, err = resolve_var(ctx, rule.count)
+ if err then
+ goto CONTINUE
+ end
+ local time_window, err2 = resolve_var(ctx, rule.time_window)
+ if err2 then
+ goto CONTINUE
+ end
+ local key, _, n_resolved = core.utils.resolve_var(rule.key, ctx.var)
+ if n_resolved == 0 then
+ goto CONTINUE
+ end
+ core.table.insert(rules, {
+ count = count,
+ time_window = time_window,
+ key_type = "constant",
+ key = key,
+ })
+
+ ::CONTINUE::
+ end
+ return rules
+end
+
+
+local function run_rate_limit(conf, rule, ctx, name, cost, dry_run)
+ local lim, err = create_limit_obj(conf, rule, name)
if not lim then
core.log.error("failed to fetch limit.count object: ", err)
@@ -272,9 +345,9 @@ function _M.rate_limit(conf, ctx, name, cost, dry_run)
return 500
end
- local conf_key = conf.key
+ local conf_key = rule.key
local key
- if conf.key_type == "var_combination" then
+ if rule.key_type == "var_combination" then
local err, n_resolved
key, err, n_resolved = core.utils.resolve_var(conf_key, ctx.var)
if err then
@@ -284,7 +357,7 @@ function _M.rate_limit(conf, ctx, name, cost, dry_run)
if n_resolved == 0 then
key = nil
end
- elseif conf.key_type == "constant" then
+ elseif rule.key_type == "constant" then
key = conf_key
else
key = ctx.var[conf_key]
@@ -353,4 +426,25 @@ function _M.rate_limit(conf, ctx, name, cost, dry_run)
end
+function _M.rate_limit(conf, ctx, name, cost, dry_run)
+ core.log.info("ver: ", ctx.conf_version)
+
+ local rules, err = get_rules(ctx, conf)
+ if not rules or #rules == 0 then
+ core.log.error("failed to get rate limit rules: ", err)
+ if conf.allow_degradation then
+ return
+ end
+ return 500
+ end
+
+ for _, rule in ipairs(rules) do
+ local code, msg = run_rate_limit(conf, rule, ctx, name, cost, dry_run)
+ if code then
+ return code, msg
+ end
+ end
+end
+
+
return _M
diff --git a/docs/en/latest/plugins/limit-count.md
b/docs/en/latest/plugins/limit-count.md
index 3f872c215..cba8f8d6a 100644
--- a/docs/en/latest/plugins/limit-count.md
+++ b/docs/en/latest/plugins/limit-count.md
@@ -44,8 +44,12 @@ You may see the following rate limiting headers in the
response:
| Name | Type | Required
| Default | Valid values | Description
[...]
| ----------------------- | ------- |
----------------------------------------- | ------------- |
-------------------------------------- |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[...]
-| count | integer | True
| | > 0 | The maximum number of
requests allowed within a given time interval.
[...]
-| time_window | integer | True
| | > 0 | The time interval corresponding
to the rate limiting `count` in seconds.
[...]
+| count | integer | False
| | > 0 | The maximum number of
requests allowed within a given time interval. Required if `rules` is not
configured.
[...]
+| time_window | integer | False
| | > 0 | The time interval corresponding
to the rate limiting `count` in seconds. Required if `rules` is not configured.
[...]
+| rules | array[object] | False
| | | A list of rate limiting rules.
Each rule is an object containing `count`, `time_window`, and `key`.
|
+| rules.count | integer | True
| | > 0 | The maximum number of requests
allowed within a given time interval.
|
+| rules.time_window | integer | True
| | > 0 | The time interval corresponding
to the rate limiting `count` in seconds.
|
+| rules.key | string | True
| | | The key to count requests by.
If the configured key does not exist, the rule will not be executed. The `key`
is interpreted as a combination of variables, for example: `$http_custom_a
$http_custom_b`.
[...]
| key_type | string | False
| var | ["var","var_combination","constant"] | The type of key. If the
`key_type` is `var`, the `key` is interpreted a variable. If the `key_type` is
`var_combination`, the `key` is interpreted as a combination of variables. If
the `key_type` is `constant`, the `key` is interpreted as a constant.
|
| key | string | False
| remote_addr | | The key to count
requests by. If the `key_type` is `var`, the `key` is interpreted a variable.
The variable does not need to be prefixed by a dollar sign (`$`). If the
`key_type` is `var_combination`, the `key` is interpreted as a combination of
variables. All variables should be prefixed by dollar signs (`$`). For example,
to configure the `key` to use [...]
| rejected_code | integer | False
| 503 | [200,...,599] | The HTTP status
code returned when a request is rejected for exceeding the threshold.
[...]
diff --git a/docs/zh/latest/plugins/limit-count.md
b/docs/zh/latest/plugins/limit-count.md
index 44fe97970..df4acfa71 100644
--- a/docs/zh/latest/plugins/limit-count.md
+++ b/docs/zh/latest/plugins/limit-count.md
@@ -45,8 +45,12 @@ description: limit-count 插件使用固定窗口算法,通过给定时间间
| 名称 | 类型 | 必选项 | 默认值 | 有效值
| 描述
|
| ------------------- | ------- | ---------- | ------------- |
--------------------------------------- |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-| count | integer | 是 | | > 0 | 给定时间间隔内允许的最大请求数。 |
-| time_window | integer | 是 | | > 0 | 速率限制 `count` 对应的时间间隔(以秒为单位)。 |
+| count | integer | 否 | | > 0 | 给定时间间隔内允许的最大请求数。如果未配置 `rules`,则此项必填。 |
+| time_window | integer | 否 | | > 0 | 速率限制 `count` 对应的时间间隔(以秒为单位)。如果未配置
`rules`,则此项必填。 |
+| rules | array[object] | 否 | | | 速率限制规则列表。每个规则是一个包含 `count`、`time_window` 和
`key` 的对象。如果配置了 `rules`,则顶层的 `count` 和 `time_window` 将被忽略。 |
+| rules.count | integer | 是 | | > 0 | 给定时间间隔内允许的最大请求数。 |
+| rules.time_window | integer | 是 | | > 0 | 速率限制 `count` 对应的时间间隔(以秒为单位)。 |
+| rules.key | string | 是 | | | 用于统计请求的键。如果配置的键不存在,则不会执行该规则。`key`
被解释为变量的组合,例如:`$http_custom_a $http_custom_b`。|
| key_type | string | 否 | var | ["var","var_combination","constant"] | key
的类型。如果`key_type` 为 `var`,则 `key` 将被解释为变量。如果 `key_type` 为 `var_combination`,则
`key` 将被解释为变量的组合。如果 `key_type` 为 `constant`,则 `key` 将被解释为常量。 |
| key | string | 否 | remote_addr | | 用于计数请求的 key。如果 `key_type` 为 `var`,则 `key`
将被解释为变量。变量不需要以美元符号(`$`)为前缀。如果 `key_type` 为 `var_combination`,则 `key`
会被解释为变量的组合。所有变量都应该以美元符号 (`$`) 为前缀。例如,要配置 `key` 使用两个请求头 `custom-a` 和 `custom-b`
的组合,则 `key` 应该配置为 `$http_custom_a $http_custom_b`。如果 `key_type` 为 `constant`,则
`key` 会被解释为常量值。|
| rejection_code | integer | 否 | 503 | [200,...,599] | 请求因超出阈值而被拒绝时返回的 HTTP
状态代码。|
diff --git a/t/admin/consumer-group.t b/t/admin/consumer-group.t
index 305afe48f..fd28570f1 100644
--- a/t/admin/consumer-group.t
+++ b/t/admin/consumer-group.t
@@ -287,7 +287,7 @@ passed
}
}
--- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err:
property \"count\" is required"}
+{"error_msg":"failed to check the configuration of plugin limit-count err:
value should match only one schema, but matches none"}
--- error_code: 400
diff --git a/t/admin/plugin-configs.t b/t/admin/plugin-configs.t
index ab6d2592b..4b72bc5c9 100644
--- a/t/admin/plugin-configs.t
+++ b/t/admin/plugin-configs.t
@@ -287,7 +287,7 @@ passed
}
}
--- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err:
property \"count\" is required"}
+{"error_msg":"failed to check the configuration of plugin limit-count err:
value should match only one schema, but matches none"}
--- error_code: 400
diff --git a/t/plugin/limit-count-rules.t b/t/plugin/limit-count-rules.t
new file mode 100644
index 000000000..cda541e65
--- /dev/null
+++ b/t/plugin/limit-count-rules.t
@@ -0,0 +1,380 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+log_level('info');
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!$block->request) {
+ $block->set_value("request", "GET /t");
+ }
+
+ if (!$block->error_log && !$block->no_error_log) {
+ $block->set_value("no_error_log", "[error]\n[alert]");
+ }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: configure count/time_window and rules at same time
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "methods": ["GET"],
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 5,
+ "rejected_code": 503,
+ "key_type": "var",
+ "key": "remote_addr",
+ "rules": [
+ {
+ "count": 1,
+ "time_window": 10,
+ "key": "${http_company}"
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.print(body)
+ }
+ }
+--- error_code: 400
+--- response_body
+{"error_msg":"failed to check the configuration of plugin limit-count err:
value should match only one schema, but matches both schemas 1 and 2"}
+
+
+
+=== TEST 2: configure multiple rules with same key
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "methods": ["GET"],
+ "plugins": {
+ "limit-count": {
+ "rejected_code": 503,
+ "rules": [
+ {
+ "count": 5,
+ "time_window": 10,
+ "key": "${http_company}"
+ },
+ {
+ "count": 8,
+ "time_window": 20,
+ "key": "${http_company}"
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.print(body)
+ }
+ }
+--- error_code: 400
+--- response_body
+{"error_msg":"failed to check the configuration of plugin limit-count err:
duplicate key '${http_company}' in rules"}
+
+
+
+=== TEST 3: setup route with rules
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "methods": ["GET"],
+ "plugins": {
+ "limit-count": {
+ "rejected_code": 503,
+ "rejected_msg" : "rejected",
+ "rules": [
+ {
+ "key": "${http_user}",
+ "count": "${http_jack_count}",
+ "time_window": 60
+ },
+ {
+ "key": "${http_project}",
+ "count": "${http_apisix_count}",
+ "time_window": 60
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 4: no any rule matched
+--- request
+GET /hello
+--- error_code: 500
+--- error_log
+failed to get rate limit rules
+
+
+
+=== TEST 5: match user rule
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- more_headers
+user: jack
+jack-count: 2
+--- error_code eval
+[200, 200, 503]
+--- response_body eval
+["hello world\n", "hello world\n", "{\"error_msg\":\"rejected\"}\n"]
+
+
+
+=== TEST 6: match project rule
+--- pipelined_requests eval
+["GET /hello", "GET /hello"]
+--- more_headers
+project: apisix
+apisix-count: 3
+--- error_code eval
+[200, 200, 200, 503]
+--- response_body eval
+["hello world\n", "hello world\n", "hello world\n",
"{\"error_msg\":\"rejected\"}\n"]
+
+
+
+=== TEST 7: setup route with rules with variables with default values
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "methods": ["GET"],
+ "plugins": {
+ "limit-count": {
+ "rejected_code": 503,
+ "rejected_msg" : "rejected",
+ "rules": [
+ {
+ "count": "${http_count ?? 2}",
+ "time_window": "${http_tw ?? 5}",
+ "key": "${remote_addr}"
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 8: rules with variables in count - default value
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]
+--- response_body eval
+["hello world\n", "hello world\n", "{\"error_msg\":\"rejected\"}\n"]
+
+
+
+=== TEST 9: rules with variables in count - with header
+--- setup
+ ngx.sleep(5)
+--- pipelined_requests eval
+["GET /hello", "GET /hello"]
+--- more_headers
+count: 1
+--- error_code eval
+[200, 503]
+--- response_body eval
+["hello world\n", "{\"error_msg\":\"rejected\"}\n"]
+
+
+
+=== TEST 10: rules with same key and custom headers
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "methods": ["GET"],
+ "plugins": {
+ "limit-count": {
+ "rejected_code": 503,
+ "rejected_msg" : "rejected",
+ "show_limit_quota_header": true,
+ "rules": [
+ {
+ "count": 2,
+ "time_window": 2,
+ "key": "${remote_addr}_2s"
+ },
+ {
+ "count": 3,
+ "time_window": 5,
+ "key": "${remote_addr}_5s"
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 11: test rules with same key
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require("resty.http")
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+
+ for i = 1, 2, 1 do
+ local res = httpc:request_uri(uri)
+ if res.status ~= 200 then
+ ngx.say("first two requests failed, status: " ..
res.status)
+ return
+ end
+ end
+
+ -- req 3, rejected by rule 1
+ res = httpc:request_uri(uri)
+ if res.status ~= 503 then
+ ngx.say("req 3 should be rejected by rule 1, but got status:
", res.status)
+ return
+ end
+
+ ngx.sleep(2)
+
+ -- req 4, after sleep
+ res = httpc:request_uri(uri)
+ if res.status ~= 200 then
+ ngx.say("req 4 failed, status: ", res.status)
+ return
+ end
+
+ -- req 5, rejected by rule 2
+ res = httpc:request_uri(uri)
+ if res.status ~= 503 then
+ ngx.say("req 5 should be rejected by rule 2, but got status:
", res.status)
+ return
+ end
+
+ ngx.say("passed")
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
diff --git a/t/plugin/limit-count-variable.t b/t/plugin/limit-count-variable.t
index ad021c2d5..c26a858fa 100644
--- a/t/plugin/limit-count-variable.t
+++ b/t/plugin/limit-count-variable.t
@@ -141,5 +141,3 @@ GET /t
--- timeout: 10
--- response_body
passed
---- error_log
-limit count: 3, time_window: 2
diff --git a/t/plugin/limit-count.t b/t/plugin/limit-count.t
index 402c93dd2..a1c96d93d 100644
--- a/t/plugin/limit-count.t
+++ b/t/plugin/limit-count.t
@@ -215,7 +215,7 @@ passed
}
--- error_code: 400
--- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err:
property \"count\" is required"}
+{"error_msg":"failed to check the configuration of plugin limit-count err:
value should match only one schema, but matches none"}
@@ -326,7 +326,7 @@ passed
}
--- error_code: 400
--- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err:
property \"count\" is required"}
+{"error_msg":"failed to check the configuration of plugin limit-count err:
value should match only one schema, but matches none"}
diff --git a/t/plugin/workflow.t b/t/plugin/workflow.t
index 022aa4851..a8e8e2d57 100644
--- a/t/plugin/workflow.t
+++ b/t/plugin/workflow.t
@@ -541,8 +541,8 @@ hello1 world
}
--- response_body
done
-failed to validate the 'limit-count' action: property "time_window" is required
-failed to validate the 'limit-count' action: property "count" is required
+failed to validate the 'limit-count' action: value should match only one
schema, but matches none
+failed to validate the 'limit-count' action: value should match only one
schema, but matches none
failed to validate the 'limit-count' action: group is not supported