Skip to content

Commit 270d48e

Browse files
committed
fix: validate plugin config schema metadata
1 parent 7a9fb33 commit 270d48e

4 files changed

Lines changed: 102 additions & 8 deletions

File tree

astrbot/core/config/astrbot_config.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,61 @@
1818
logger = logging.getLogger("astrbot")
1919

2020

21+
def _is_config_number(value) -> bool:
22+
return isinstance(value, (int, float)) and not isinstance(value, bool)
23+
24+
25+
_SCHEMA_TYPE_VALIDATORS = {
26+
"int": lambda v: isinstance(v, int) and not isinstance(v, bool),
27+
"float": _is_config_number,
28+
"bool": lambda v: isinstance(v, bool),
29+
"string": lambda v: isinstance(v, str),
30+
"text": lambda v: isinstance(v, str),
31+
"list": lambda v: isinstance(v, list),
32+
"file": lambda v: isinstance(v, list),
33+
"object": lambda v: isinstance(v, dict),
34+
"dict": lambda v: isinstance(v, dict),
35+
"template_list": lambda v: isinstance(v, list),
36+
}
37+
38+
39+
def _validate_schema_default(field: str, typ: str, default) -> None:
40+
if not _SCHEMA_TYPE_VALIDATORS[typ](default):
41+
raise TypeError(f"配置项 {field} 的 default 与类型 {typ} 不匹配")
42+
43+
44+
def _validate_schema_slider(field: str, typ: str, slider: dict) -> None:
45+
if typ not in ("int", "float"):
46+
raise TypeError(f"配置项 {field} 只有 int/float 类型支持 slider")
47+
if not isinstance(slider, dict) or not all(
48+
_is_config_number(slider.get(key)) for key in ("min", "max", "step")
49+
):
50+
raise TypeError(
51+
f"配置项 {field} 的 slider 必须包含数字 min/max/step",
52+
)
53+
54+
55+
def _validate_config_schema_item(field: str, item: dict) -> None:
56+
typ = item["type"]
57+
if typ not in DEFAULT_VALUE_MAP:
58+
raise TypeError(
59+
f"不受支持的配置类型 {typ}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}",
60+
)
61+
if "options" in item and not isinstance(item["options"], list):
62+
raise TypeError(f"配置项 {field} 的 options 必须是列表")
63+
if "obvious_hint" in item and not isinstance(item["obvious_hint"], bool):
64+
raise TypeError(f"配置项 {field} 的 obvious_hint 必须是布尔值")
65+
if "slider" in item:
66+
_validate_schema_slider(field, typ, item["slider"])
67+
if typ == "object" and not isinstance(item.get("items"), dict):
68+
raise TypeError(f"配置项 {field} 的 items 必须是对象")
69+
default = item["default"] if "default" in item else DEFAULT_VALUE_MAP[typ]
70+
_validate_schema_default(field, typ, default)
71+
if typ == "object":
72+
for child_key, child_item in item["items"].items():
73+
_validate_config_schema_item(f"{field}.{child_key}", child_item)
74+
75+
2176
class RateLimitStrategy(enum.Enum):
2277
STALL = "stall"
2378
DISCARD = "discard"
@@ -133,10 +188,7 @@ def _config_schema_to_default_config(self, schema: dict) -> dict:
133188

134189
def _parse_schema(schema: dict, conf: dict) -> None:
135190
for k, v in schema.items():
136-
if v["type"] not in DEFAULT_VALUE_MAP:
137-
raise TypeError(
138-
f"不受支持的配置类型 {v['type']}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}",
139-
)
191+
_validate_config_schema_item(k, v)
140192
if "default" in v:
141193
default = v["default"]
142194
else:

astrbot/core/config/default.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4301,5 +4301,6 @@
43014301
"list": [],
43024302
"file": [],
43034303
"object": {},
4304+
"dict": {},
43044305
"template_list": [],
43054306
}

docs/zh/dev/star/guides/plugin-config.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ AstrBot 提供了“强大”的配置解析和可视化功能。能够让用户
4343
}
4444
```
4545

46-
- `type`: **此项必填**。配置的类型。支持 `string`, `text`, `int`, `float`, `bool`, `object`, `list`, `dict`, `template_list`。当类型为 `text` 时,将会可视化为一个更大的可拖拽宽高的 textarea 组件,以适应大文本。
46+
- `type`: **此项必填**。配置的类型。支持 `string`, `text`, `int`, `float`, `bool`, `object`, `list`, `dict`, `file`, `template_list`。当类型为 `text` 时,将会可视化为一个更大的可拖拽宽高的 textarea 组件,以适应大文本。
4747
- `description`: 可选。配置的描述。建议一句话描述配置的行为。
4848
- `hint`: 可选。配置的提示信息,表现在上图中右边的问号按钮,当鼠标悬浮在问号按钮上时显示。
49-
- `obvious_hint`: 可选。配置的 hint 是否醒目显示。如上图的 `token`
50-
- `default`: 可选。配置的默认值。如果用户没有配置,将使用默认值。int 是 0,float 是 0.0,bool 是 False,string 是 "",object 是 {},list 是 []
49+
- `obvious_hint`: 可选,布尔值。配置的 hint 是否醒目显示;只有同时配置了 `hint` 时才会显示。如上图的 `token`
50+
- `default`: 可选。配置的默认值。如果用户没有配置,将使用默认值。int 是 0,float 是 0.0,bool 是 False,string/text 是 "",object/dict 是 {},list/file/template_list[]
5151
- `items`: 可选。如果配置的类型是 `object`,需要添加 `items` 字段。`items` 的内容是这个配置项的子 Schema。理论上可以无限嵌套,但是不建议过多嵌套。
5252
- `invisible`: 可选。配置是否隐藏。默认是 `false`。如果设置为 `true`,则不会在管理面板上显示。
53-
- `options`: 可选。一个列表,如 `"options": ["chat", "agent", "workflow"]`。提供下拉列表可选项。
53+
- `options`: 可选,必须是列表,如 `"options": ["chat", "agent", "workflow"]`。提供下拉列表可选项。
54+
- `slider`: 可选,仅支持 `int` / `float` 类型。必须包含数字类型的 `min``max``step`
5455
- `editor_mode`: 可选。是否启用代码编辑器模式。需要 AstrBot >= `v3.5.10`, 低于这个版本不会报错,但不会生效。默认是 false。
5556
- `editor_language`: 可选。代码编辑器的代码语言,默认为 `json`
5657
- `editor_theme`: 可选。代码编辑器的主题,可选值有 `vs-light`(默认), `vs-dark`

tests/unit/test_config.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,46 @@ def test_template_list_type(self, temp_config_path):
596596

597597
assert config.templates == []
598598

599+
def test_dict_schema_type(self, temp_config_path):
600+
"""Test dict schema type."""
601+
schema = {
602+
"headers": {"type": "dict"},
603+
}
604+
605+
config = AstrBotConfig(config_path=temp_config_path, schema=schema)
606+
607+
assert config.headers == {}
608+
609+
@pytest.mark.parametrize(
610+
("schema", "error"),
611+
[
612+
(
613+
{"field": {"type": "string", "default": 1}},
614+
"default 与类型 string 不匹配",
615+
),
616+
(
617+
{"field": {"type": "list", "options": "bad"}},
618+
"options 必须是列表",
619+
),
620+
(
621+
{"field": {"type": "string", "obvious_hint": "yes"}},
622+
"obvious_hint 必须是布尔值",
623+
),
624+
(
625+
{"field": {"type": "float", "slider": {"min": "0", "max": 1, "step": 1}}},
626+
"slider 必须包含数字 min/max/step",
627+
),
628+
(
629+
{"field": {"type": "string", "slider": {"min": 0, "max": 1, "step": 1}}},
630+
"只有 int/float 类型支持 slider",
631+
),
632+
],
633+
)
634+
def test_schema_metadata_validation(self, temp_config_path, schema, error):
635+
"""Test schema metadata validation."""
636+
with pytest.raises(TypeError, match=error):
637+
AstrBotConfig(config_path=temp_config_path, schema=schema)
638+
599639
def test_nested_object_schema(self, temp_config_path):
600640
"""Test nested object schema conversion."""
601641
schema = {

0 commit comments

Comments
 (0)