hy-app-monitor/monitors/yumi/config_validation.py

162 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
# JSON 校验直接走标准库。
import json
# XML 校验直接走标准库。
import xml.etree.ElementTree as element_tree
from typing import Any
def normalized_config_type(config_type: str, data_id: str = "") -> str:
# 统一把类型名压平,避免前后端大小写不一致。
lowered = (config_type or "").strip().lower()
if lowered in {"yml", "yaml"}:
return "yaml"
if lowered in {"json", "properties", "xml", "html", "text"}:
return lowered
# 没显式类型时,按常见后缀兜底。
suffix = data_id.strip().lower()
if suffix.endswith((".yml", ".yaml")):
return "yaml"
if suffix.endswith(".json"):
return "json"
if suffix.endswith(".properties"):
return "properties"
if suffix.endswith(".xml"):
return "xml"
if suffix.endswith(".html"):
return "html"
return "text"
def dependency_error(message: str) -> dict[str, Any]:
# 缺少解析依赖时也返回统一结构,前端方便直接展示。
return {
"ok": False,
"strict": False,
"type": "",
"message": message,
}
def validation_success(config_type: str, message: str, *, strict: bool = True, details: dict[str, Any] | None = None) -> dict[str, Any]:
# 成功结果也统一带上 strict 标记,告诉前端是不是强校验。
return {
"ok": True,
"strict": strict,
"type": config_type,
"message": message,
"details": details or {},
}
def validation_error(config_type: str, message: str, *, details: dict[str, Any] | None = None) -> dict[str, Any]:
# 失败结构保持和成功结构一致。
return {
"ok": False,
"strict": True,
"type": config_type,
"message": message,
"details": details or {},
}
def validate_yaml(content: str) -> dict[str, Any]:
try:
# 按需导入 PyYAML避免依赖缺失时影响其他功能启动。
import yaml
except Exception as exc: # noqa: BLE001
return dependency_error(f"yaml validator unavailable: {exc}")
try:
# 空 YAML 允许解析成 null。
parsed = yaml.safe_load(content)
except yaml.YAMLError as exc:
# PyYAML 异常里通常会带问题行列。
problem_mark = getattr(exc, "problem_mark", None)
details = {}
if problem_mark is not None:
details = {"line": int(problem_mark.line) + 1, "column": int(problem_mark.column) + 1}
return validation_error("yaml", str(exc), details=details)
parsed_kind = type(parsed).__name__ if parsed is not None else "null"
return validation_success("yaml", f"yaml format ok ({parsed_kind})", details={"parsedKind": parsed_kind})
def validate_json_text(content: str) -> dict[str, Any]:
try:
# JSON 必须是合法文本,空串直接视为错误。
parsed = json.loads(content)
except json.JSONDecodeError as exc:
return validation_error(
"json",
exc.msg,
details={"line": int(exc.lineno), "column": int(exc.colno)},
)
parsed_kind = type(parsed).__name__
return validation_success("json", f"json format ok ({parsed_kind})", details={"parsedKind": parsed_kind})
def validate_properties_text(content: str) -> dict[str, Any]:
try:
# Java properties 校验直接走专用解析器,避免手写规则误判。
import javaproperties
except Exception as exc: # noqa: BLE001
return dependency_error(f"properties validator unavailable: {exc}")
try:
# loads 会在格式异常时抛出 ParseError。
parsed = javaproperties.loads(content)
except Exception as exc: # noqa: BLE001
line = getattr(exc, "lineno", None)
details = {"line": int(line)} if isinstance(line, int) else {}
return validation_error("properties", str(exc), details=details)
return validation_success("properties", f"properties format ok ({len(parsed)} keys)", details={"keysCount": len(parsed)})
def validate_xml_text(content: str) -> dict[str, Any]:
try:
# XML 用标准库直接做语法解析。
root = element_tree.fromstring(content)
except element_tree.ParseError as exc:
line, column = getattr(exc, "position", (0, 0))
return validation_error(
"xml",
str(exc),
details={"line": int(line), "column": int(column) + 1},
)
return validation_success("xml", f"xml format ok (<{root.tag}>)", details={"rootTag": root.tag})
def validate_text_like(config_type: str) -> dict[str, Any]:
# text/html 先不做严格语法校验,只给出明确提示。
return validation_success(
config_type,
f"{config_type} saved as plain text; strict syntax validation is not enabled",
strict=False,
)
def validate_config_content(content: str, *, config_type: str = "", data_id: str = "") -> dict[str, Any]:
# 统一解析类型。
normalized_type = normalized_config_type(config_type, data_id)
# 按类型路由到对应校验器。
if normalized_type == "yaml":
return validate_yaml(content)
if normalized_type == "json":
return validate_json_text(content)
if normalized_type == "properties":
return validate_properties_text(content)
if normalized_type == "xml":
return validate_xml_text(content)
if normalized_type in {"html", "text"}:
return validate_text_like(normalized_type)
# 理论上不会走到这里,这里只是兜底。
return validation_success(normalized_type, "no validator configured", strict=False)