v0-json-schema

This commit is contained in:
gongwenxin
2025-05-16 15:18:02 +08:00
parent 32676a314f
commit 6d0b70fe11
260 changed files with 280278 additions and 0 deletions
@@ -0,0 +1 @@
@@ -0,0 +1,91 @@
"""Base adapter interface for rule storage."""
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any, Union
from ...models.rule_models import AnyRule, RuleQuery, BaseRule
class BaseRuleStorageAdapter(ABC):
"""Base class for rule storage adapters."""
@abstractmethod
def initialize(self) -> None:
"""初始化适配器(例如,连接到数据库,验证文件路径等)。"""
pass
@abstractmethod
def load_rule_by_id(self, rule_id: str, version: Optional[str] = None) -> Optional[AnyRule]:
"""
按ID加载单个规则。
Args:
rule_id: 规则的唯一标识符。
version: 可选的版本标识符。如果未提供,则使用配置的默认版本策略。
Returns:
找到的规则对象,或者如果未找到则返回None。
"""
pass
@abstractmethod
def query_rules(self, query: RuleQuery) -> List[AnyRule]:
"""
根据查询条件返回匹配的规则列表。
Args:
query: 规则查询条件。
Returns:
匹配规则的列表。
"""
pass
@abstractmethod
def save_rule(self, rule: BaseRule) -> bool:
"""
保存单个规则。
Args:
rule: 要保存的规则对象。
Returns:
操作是否成功。
"""
pass
@abstractmethod
def delete_rule(self, rule_id: str, version: Optional[str] = None) -> bool:
"""
删除单个规则。
Args:
rule_id: 规则的唯一标识符。
version: 可选的版本标识符。如果未提供,通常会删除所有版本。
Returns:
操作是否成功。
"""
pass
@abstractmethod
def list_all_rule_ids(self) -> List[str]:
"""
列出存储中的所有规则ID。
Returns:
规则ID的列表。
"""
pass
@abstractmethod
def get_rule_versions(self, rule_id: str) -> List[str]:
"""
获取特定规则ID的所有可用版本。
Args:
rule_id: 规则的唯一标识符。
Returns:
版本标识符的列表。
"""
pass
@@ -0,0 +1,324 @@
"""File system adapter for rule storage using JSON files."""
import os
import json
import glob
from typing import List, Dict, Optional, Any, Tuple, Type, Union, cast
import logging
from datetime import datetime
from .base_adapter import BaseRuleStorageAdapter
from ...models.rule_models import (
AnyRule, RuleQuery, BaseRule, RuleCategory, TargetType
)
from .rule_adapter_utils import parse_rule_data
class FilesystemAdapter(BaseRuleStorageAdapter):
"""
基于文件系统的规则存储适配器,使用JSON文件保存规则。
文件结构约定:
规则文件将按以下方式组织:
rules/
json_schemas/
rule_id1/
1.0.0.json
1.1.0.json
rule_id2/
1.0.0.json
business_logic/
rule_id3/
1.0.0.json
...
"""
def __init__(self, base_path: str = "./rules", file_pattern: str = "*.json"):
"""
初始化适配器。
Args:
base_path: 存储规则的基本目录路径。
file_pattern: 匹配规则文件的glob模式。
"""
self.base_path = os.path.abspath(base_path)
self.file_pattern = file_pattern
self.logger = logging.getLogger(__name__)
def initialize(self) -> None:
"""确保基本目录存在,并验证其可访问性。"""
if not os.path.exists(self.base_path):
try:
os.makedirs(self.base_path)
self.logger.info(f"Created rules directory at {self.base_path}")
except Exception as e:
self.logger.error(f"Failed to create rules directory at {self.base_path}: {e}")
raise ValueError(f"Rules directory {self.base_path} does not exist and could not be created")
if not os.access(self.base_path, os.R_OK | os.W_OK):
self.logger.error(f"Rules directory {self.base_path} is not readable and writable")
raise ValueError(f"Rules directory {self.base_path} is not readable and writable")
# 确保每个规则类别的子目录存在
for category in RuleCategory:
category_dir = self._get_category_dir(category)
if not os.path.exists(category_dir):
try:
os.makedirs(category_dir)
self.logger.debug(f"Created category directory at {category_dir}")
except Exception as e:
self.logger.warning(f"Failed to create category directory at {category_dir}: {e}")
def _get_category_dir(self, category: RuleCategory) -> str:
"""获取给定规则类别的目录路径。"""
return os.path.join(self.base_path, category.value.lower())
def _get_rule_dir(self, rule_id: str, category: Optional[RuleCategory] = None) -> str:
"""
获取给定规则ID的目录路径。
如果指定了类别,则直接使用该类别的目录;否则尝试在所有类别中查找。
"""
if category:
return os.path.join(self._get_category_dir(category), rule_id)
# 如果未指定类别,检查所有类别目录
for cat in RuleCategory:
rule_dir = os.path.join(self._get_category_dir(cat), rule_id)
if os.path.exists(rule_dir):
return rule_dir
# 如果未找到任何匹配项,默认返回通用类别目录
return os.path.join(self._get_category_dir(RuleCategory.GENERIC), rule_id)
def _get_rule_file_path(self, rule_id: str, version: str, category: Optional[RuleCategory] = None) -> str:
"""获取给定规则ID和版本的文件路径。"""
rule_dir = self._get_rule_dir(rule_id, category)
return os.path.join(rule_dir, f"{version}.json")
def _get_rule_from_file(self, file_path: str) -> Optional[AnyRule]:
"""从文件加载规则。"""
if not os.path.exists(file_path):
self.logger.debug(f"Rule file {file_path} does not exist")
return None
try:
with open(file_path, 'r', encoding='utf-8') as f:
raw_data = json.load(f)
# 使用工具函数解析规则数据
return parse_rule_data(raw_data)
except json.JSONDecodeError as e:
self.logger.error(f"Failed to parse JSON from {file_path}: {e}")
return None
except Exception as e:
self.logger.error(f"Error loading rule from {file_path}: {e}")
return None
def _save_rule_to_file(self, rule: BaseRule, file_path: str) -> bool:
"""将规则保存到文件。"""
try:
# 确保目录存在
dir_path = os.path.dirname(file_path)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
# 序列化规则为JSON并写入文件
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(rule.model_dump(), f, indent=2, ensure_ascii=False)
return True
except Exception as e:
self.logger.error(f"Failed to save rule to {file_path}: {e}")
return False
def _get_latest_version(self, rule_id: str, category: Optional[RuleCategory] = None) -> Optional[str]:
"""获取给定规则ID的最新版本。"""
versions = self.get_rule_versions(rule_id, category)
if not versions:
return None
# 简单地按字符串排序,假设版本格式是类似于"1.0.0"的语义化版本
# 如果需要更复杂的版本比较,可以使用packaging.version
versions.sort()
return versions[-1]
def get_rule_versions(self, rule_id: str, category: Optional[RuleCategory] = None) -> List[str]:
"""获取给定规则ID的所有版本。"""
rule_dir = self._get_rule_dir(rule_id, category)
if not os.path.exists(rule_dir):
return []
# 查找目录中所有匹配的JSON文件,并提取版本号(文件名)
pattern = os.path.join(rule_dir, self.file_pattern)
version_files = glob.glob(pattern)
versions = []
for vf in version_files:
version = os.path.splitext(os.path.basename(vf))[0] # 移除.json扩展名
versions.append(version)
return versions
def load_rule_by_id(self, rule_id: str, version: Optional[str] = None) -> Optional[AnyRule]:
"""
按ID加载单个规则。
Args:
rule_id: 规则的唯一标识符。
version: 可选的版本标识符。如果未提供,则加载最新版本。
Returns:
找到的规则对象,或者如果未找到则返回None。
"""
if not version:
version = self._get_latest_version(rule_id)
if not version:
self.logger.debug(f"No versions found for rule ID {rule_id}")
return None
file_path = self._get_rule_file_path(rule_id, version)
return self._get_rule_from_file(file_path)
def query_rules(self, query: RuleQuery) -> List[AnyRule]:
"""
根据查询条件查询规则。
Args:
query: 包含筛选条件的查询对象。
Returns:
匹配查询条件的规则列表。
"""
results = []
# 如果指定了规则ID,直接加载该规则
if query.rule_id:
rule = self.load_rule_by_id(query.rule_id, query.version if query.version != "latest" else None)
if rule and self._rule_matches_query(rule, query):
results.append(rule)
return results
# 否则,根据查询条件扫描规则文件
categories_to_search = [query.category] if query.category else list(RuleCategory)
for category in categories_to_search:
category_dir = self._get_category_dir(category)
if not os.path.exists(category_dir):
continue
# 获取该类别下的所有规则ID(子目录)
rule_dirs = [d for d in os.listdir(category_dir)
if os.path.isdir(os.path.join(category_dir, d))]
for rule_id in rule_dirs:
# 对于每个规则ID,加载指定版本或最新版本
if query.version and query.version != "latest":
file_path = self._get_rule_file_path(rule_id, query.version, category)
rule = self._get_rule_from_file(file_path)
if rule and self._rule_matches_query(rule, query):
results.append(rule)
else:
latest_version = self._get_latest_version(rule_id, category)
if latest_version:
file_path = self._get_rule_file_path(rule_id, latest_version, category)
rule = self._get_rule_from_file(file_path)
if rule and self._rule_matches_query(rule, query):
results.append(rule)
return results
def _rule_matches_query(self, rule: BaseRule, query: RuleQuery) -> bool:
"""检查规则是否匹配查询条件。"""
# 检查是否启用
if query.is_enabled is not None and rule.is_enabled != query.is_enabled:
return False
# 检查目标类型
if query.target_type and rule.target_type != query.target_type:
return False
# 检查目标标识符
if query.target_identifier and rule.target_identifier != query.target_identifier:
return False
# 检查标签
if query.tags:
if not rule.tags:
return False
# 检查是否所有查询标签都在规则标签中
if not all(tag in rule.tags for tag in query.tags):
return False
return True
def save_rule(self, rule: BaseRule) -> bool:
"""
保存规则到文件系统。
Args:
rule: 要保存的规则对象。
Returns:
操作是否成功。
"""
file_path = self._get_rule_file_path(rule.id, rule.version, rule.category)
return self._save_rule_to_file(rule, file_path)
def delete_rule(self, rule_id: str, version: Optional[str] = None) -> bool:
"""
删除规则。
Args:
rule_id: 规则的唯一标识符。
version: 如果提供,仅删除该版本;否则删除所有版本。
Returns:
操作是否成功。
"""
try:
if version:
# 删除特定版本
file_path = self._get_rule_file_path(rule_id, version)
if os.path.exists(file_path):
os.remove(file_path)
self.logger.info(f"Deleted rule file: {file_path}")
else:
self.logger.warning(f"Rule file not found for deletion: {file_path}")
return False
else:
# 删除所有版本(整个规则目录)
rule_dir = self._get_rule_dir(rule_id)
if os.path.exists(rule_dir):
# 递归删除目录及其内容
for root, dirs, files in os.walk(rule_dir, topdown=False):
for file in files:
os.remove(os.path.join(root, file))
for dir in dirs:
os.rmdir(os.path.join(root, dir))
os.rmdir(rule_dir)
self.logger.info(f"Deleted rule directory: {rule_dir}")
else:
self.logger.warning(f"Rule directory not found for deletion: {rule_dir}")
return False
return True
except Exception as e:
self.logger.error(f"Error deleting rule {rule_id} (version={version}): {e}")
return False
def list_all_rule_ids(self) -> List[str]:
"""列出所有规则ID。"""
all_rule_ids = set()
# 扫描所有类别目录
for category in RuleCategory:
category_dir = self._get_category_dir(category)
if not os.path.exists(category_dir):
continue
# 获取该类别下的所有规则ID(子目录)
rule_dirs = [d for d in os.listdir(category_dir)
if os.path.isdir(os.path.join(category_dir, d))]
all_rule_ids.update(rule_dirs)
return list(all_rule_ids)
@@ -0,0 +1,60 @@
"""规则适配器工具函数"""
import logging
from typing import Dict, Any, Optional, Type
from ...models.rule_models import (
AnyRule, BaseRule, RuleCategory,
JSONSchemaDefinition, APILintingRuleset, BusinessAssertionTemplate, DataQualityRule,
PythonCodeRule, PerformanceRule, SecurityRule, RESTfulDesignRule, ErrorHandlingRule
)
# 规则类别到Pydantic模型类的映射
RULE_CLASSES = {
RuleCategory.JSON_SCHEMA: JSONSchemaDefinition,
RuleCategory.API_LINTING: APILintingRuleset,
RuleCategory.BUSINESS_LOGIC: BusinessAssertionTemplate,
RuleCategory.DATA_QUALITY: DataQualityRule,
RuleCategory.PYTHON_CODE: PythonCodeRule,
RuleCategory.PERFORMANCE: PerformanceRule,
RuleCategory.SECURITY: SecurityRule,
RuleCategory.API_DESIGN: RESTfulDesignRule,
RuleCategory.ERROR_HANDLING: ErrorHandlingRule,
}
logger = logging.getLogger(__name__)
def get_rule_class_by_category(category: RuleCategory) -> Type[BaseRule]:
"""根据规则类别获取对应的规则类"""
return RULE_CLASSES.get(category, BaseRule)
def parse_rule_data(raw_data: Dict[str, Any]) -> Optional[AnyRule]:
"""
解析规则数据,返回对应类型的规则对象
Args:
raw_data: 从文件加载的原始规则数据
Returns:
规则对象,如果无法解析则返回None
"""
try:
# 确保数据包含类别信息
if 'category' not in raw_data:
logger.warning("Rule data missing 'category' field")
return None
# 尝试将字符串类别转换为枚举值
try:
category = RuleCategory(raw_data['category'])
except ValueError:
logger.warning(f"Rule data has invalid category: {raw_data['category']}")
return None
# 获取对应的Pydantic模型类
rule_class = get_rule_class_by_category(category)
# 使用Pydantic模型解析数据
return rule_class(**raw_data)
except Exception as e:
logger.error(f"Error parsing rule data: {e}")
return None