355 lines
14 KiB
Python
355 lines
14 KiB
Python
"""Input Parser Module"""
|
||
|
||
import json
|
||
import os
|
||
from typing import Any, Dict, Optional, List, Union
|
||
from pydantic import BaseModel # For defining the structure of parsed inputs
|
||
from dataclasses import dataclass, field
|
||
import logging
|
||
|
||
logger = logging.getLogger("InputParser")
|
||
|
||
class ParsedOpenAPISpec(BaseModel):
|
||
# Placeholder for OpenAPI spec details relevant to the compliance suite
|
||
spec: Dict[str, Any]
|
||
info: Dict[str, Any] # Swagger 'info' object with title, version, etc.
|
||
paths: Dict[str, Dict[str, Any]] # API paths and their operations
|
||
tags: Optional[List[Dict[str, str]]] = None # API tags
|
||
basePath: Optional[str] = None # Base path for all APIs
|
||
swagger_version: str # Swagger specification version
|
||
|
||
@dataclass
|
||
class YAPIEndpoint:
|
||
"""YAPI API端点信息"""
|
||
path: str
|
||
method: str
|
||
title: str = ""
|
||
description: str = ""
|
||
category_name: str = ""
|
||
req_params: List[Dict[str, Any]] = field(default_factory=list)
|
||
req_query: List[Dict[str, Any]] = field(default_factory=list)
|
||
req_headers: List[Dict[str, Any]] = field(default_factory=list)
|
||
req_body_type: str = ""
|
||
req_body_other: str = ""
|
||
res_body_type: str = ""
|
||
res_body: str = ""
|
||
|
||
@dataclass
|
||
class ParsedYAPISpec:
|
||
"""解析后的YAPI规范"""
|
||
endpoints: List[YAPIEndpoint]
|
||
categories: List[Dict[str, Any]]
|
||
total_count: int
|
||
|
||
@dataclass
|
||
class SwaggerEndpoint:
|
||
"""Swagger API端点信息"""
|
||
path: str
|
||
method: str
|
||
summary: str = ""
|
||
description: str = ""
|
||
operation_id: str = ""
|
||
tags: List[str] = field(default_factory=list)
|
||
parameters: List[Dict[str, Any]] = field(default_factory=list)
|
||
responses: Dict[str, Any] = field(default_factory=dict)
|
||
consumes: List[str] = field(default_factory=list)
|
||
produces: List[str] = field(default_factory=list)
|
||
request_body: Dict[str, Any] = field(default_factory=dict)
|
||
|
||
@dataclass
|
||
class ParsedSwaggerSpec:
|
||
"""解析后的Swagger规范"""
|
||
endpoints: List[SwaggerEndpoint]
|
||
info: Dict[str, Any]
|
||
swagger_version: str
|
||
host: str = ""
|
||
base_path: str = ""
|
||
schemes: List[str] = field(default_factory=list)
|
||
tags: List[Dict[str, Any]] = field(default_factory=list)
|
||
categories: List[Dict[str, Any]] = field(default_factory=list)
|
||
|
||
class ParsedBusinessLogic(BaseModel):
|
||
# Placeholder for parsed business logic flow
|
||
name: str
|
||
steps: list # List of steps, each could be another Pydantic model
|
||
|
||
class InputParser:
|
||
"""
|
||
Responsible for parsing DDMS supplier's input materials like API specs, etc.
|
||
"""
|
||
|
||
def __init__(self):
|
||
pass
|
||
|
||
def parse_openapi_spec(self, spec_path: str) -> Optional[ParsedOpenAPISpec]:
|
||
"""
|
||
Parses an OpenAPI specification from a file path.
|
||
|
||
Args:
|
||
spec_path: The file path of the OpenAPI specification.
|
||
|
||
Returns:
|
||
A ParsedOpenAPISpec object containing the parsed specification,
|
||
or None if parsing fails.
|
||
"""
|
||
try:
|
||
# Check if file exists
|
||
if not os.path.exists(spec_path):
|
||
print(f"Error: File not found: {spec_path}")
|
||
return None
|
||
|
||
# Read and parse JSON file
|
||
with open(spec_path, 'r', encoding='utf-8') as f:
|
||
swagger_data = json.load(f)
|
||
|
||
# Extract basic information
|
||
swagger_version = swagger_data.get('swagger', swagger_data.get('openapi', 'Unknown'))
|
||
info = swagger_data.get('info', {})
|
||
paths = swagger_data.get('paths', {})
|
||
tags = swagger_data.get('tags', [])
|
||
base_path = swagger_data.get('basePath', '')
|
||
|
||
# Create and return ParsedOpenAPISpec
|
||
return ParsedOpenAPISpec(
|
||
spec=swagger_data,
|
||
info=info,
|
||
paths=paths,
|
||
tags=tags,
|
||
basePath=base_path,
|
||
swagger_version=swagger_version
|
||
)
|
||
|
||
except FileNotFoundError:
|
||
print(f"File not found: {spec_path}")
|
||
return None
|
||
except json.JSONDecodeError as e:
|
||
print(f"Error parsing JSON from {spec_path}: {e}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"Error parsing OpenAPI spec from {spec_path}: {e}")
|
||
return None
|
||
|
||
def parse_yapi_spec(self, file_path: str) -> Optional[ParsedYAPISpec]:
|
||
"""
|
||
解析YAPI规范文件
|
||
|
||
Args:
|
||
file_path: YAPI JSON文件路径
|
||
|
||
Returns:
|
||
Optional[ParsedYAPISpec]: 解析后的YAPI规范,如果解析失败则返回None
|
||
"""
|
||
if not os.path.isfile(file_path):
|
||
logger.error(f"文件不存在: {file_path}")
|
||
return None
|
||
|
||
try:
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
yapi_data = json.load(f)
|
||
|
||
if not isinstance(yapi_data, list):
|
||
logger.error(f"无效的YAPI文件格式: 顶层元素应该是数组")
|
||
return None
|
||
|
||
endpoints = []
|
||
categories = []
|
||
|
||
# 处理分类
|
||
for category_data in yapi_data:
|
||
if not isinstance(category_data, dict):
|
||
logger.warning(f"YAPI 分类条目格式不正确,应为字典类型,已跳过: {category_data}")
|
||
continue
|
||
|
||
category_name = category_data.get('name', '')
|
||
category_desc = category_data.get('desc', '')
|
||
|
||
# 添加到分类列表
|
||
categories.append({
|
||
'name': category_name,
|
||
'desc': category_desc
|
||
})
|
||
|
||
# 处理API接口
|
||
api_list = category_data.get('list', [])
|
||
if not isinstance(api_list, list):
|
||
logger.warning(f"分类 '{category_name}' 中的 API列表 (list) 格式不正确,应为数组类型,已跳过。")
|
||
continue
|
||
|
||
for api_item in api_list:
|
||
if not isinstance(api_item, dict):
|
||
logger.warning(f"分类 '{category_name}' 中的 API条目格式不正确,应为字典类型,已跳过: {api_item}")
|
||
continue
|
||
|
||
# 提取API信息
|
||
path = api_item.get('path', '')
|
||
if not path:
|
||
logger.info(f"分类 '{category_name}' 中的 API条目缺少 'path',使用空字符串。 API: {api_item.get('title', '未命名')}")
|
||
method = api_item.get('method', 'GET')
|
||
if api_item.get('method') is None: # 仅当原始数据中完全没有 method 字段时记录
|
||
logger.info(f"分类 '{category_name}' 中的 API条目 '{path}' 缺少 'method',使用默认值 'GET'。")
|
||
title = api_item.get('title', '')
|
||
if not title:
|
||
logger.info(f"分类 '{category_name}' 中的 API条目 '{path}' ({method}) 缺少 'title',使用空字符串。")
|
||
description = api_item.get('desc', '')
|
||
|
||
# 提取请求参数
|
||
req_params = api_item.get('req_params', [])
|
||
req_query = api_item.get('req_query', [])
|
||
req_headers = api_item.get('req_headers', [])
|
||
|
||
# 提取请求体信息
|
||
req_body_type = api_item.get('req_body_type', '')
|
||
req_body_other = api_item.get('req_body_other', '')
|
||
|
||
# 提取响应体信息
|
||
res_body_type = api_item.get('res_body_type', '')
|
||
res_body = api_item.get('res_body', '')
|
||
|
||
# 创建端点对象
|
||
endpoint = YAPIEndpoint(
|
||
path=path,
|
||
method=method,
|
||
title=title,
|
||
description=description,
|
||
category_name=category_name,
|
||
req_params=req_params,
|
||
req_query=req_query,
|
||
req_headers=req_headers,
|
||
req_body_type=req_body_type,
|
||
req_body_other=req_body_other,
|
||
res_body_type=res_body_type,
|
||
res_body=res_body
|
||
)
|
||
|
||
endpoints.append(endpoint)
|
||
|
||
return ParsedYAPISpec(
|
||
endpoints=endpoints,
|
||
categories=categories,
|
||
total_count=len(endpoints)
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"解析YAPI文件时出错: {str(e)}")
|
||
return None
|
||
|
||
def parse_swagger_spec(self, file_path: str) -> Optional[ParsedSwaggerSpec]:
|
||
"""
|
||
解析Swagger规范文件
|
||
|
||
Args:
|
||
file_path: Swagger JSON文件路径
|
||
|
||
Returns:
|
||
Optional[ParsedSwaggerSpec]: 解析后的Swagger规范,如果解析失败则返回None
|
||
"""
|
||
if not os.path.isfile(file_path):
|
||
logger.error(f"文件不存在: {file_path}")
|
||
return None
|
||
|
||
try:
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
swagger_data = json.load(f)
|
||
|
||
if not isinstance(swagger_data, dict):
|
||
logger.error(f"无效的Swagger文件格式: 顶层元素应该是对象")
|
||
return None
|
||
|
||
# 提取基本信息
|
||
swagger_version = swagger_data.get('swagger', swagger_data.get('openapi', ''))
|
||
info = swagger_data.get('info', {})
|
||
host = swagger_data.get('host', '')
|
||
base_path = swagger_data.get('basePath', '')
|
||
schemes = swagger_data.get('schemes', [])
|
||
tags = swagger_data.get('tags', [])
|
||
|
||
# 创建分类列表
|
||
categories = []
|
||
for tag in tags:
|
||
categories.append({
|
||
'name': tag.get('name', ''),
|
||
'desc': tag.get('description', '')
|
||
})
|
||
|
||
# 处理API路径
|
||
paths = swagger_data.get('paths', {})
|
||
endpoints = []
|
||
|
||
for path, path_item in paths.items():
|
||
if not isinstance(path_item, dict):
|
||
continue
|
||
|
||
# 处理每个HTTP方法 (GET, POST, PUT, DELETE等)
|
||
for method, operation in path_item.items():
|
||
if method in ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace']:
|
||
if not isinstance(operation, dict):
|
||
continue
|
||
|
||
# 提取操作信息
|
||
summary = operation.get('summary', '')
|
||
description = operation.get('description', '')
|
||
operation_id = operation.get('operationId', '')
|
||
operation_tags = operation.get('tags', [])
|
||
|
||
# 提取参数信息
|
||
parameters = operation.get('parameters', [])
|
||
|
||
# 提取响应信息
|
||
responses = operation.get('responses', {})
|
||
|
||
# 提取请求和响应的内容类型
|
||
consumes = operation.get('consumes', swagger_data.get('consumes', []))
|
||
produces = operation.get('produces', swagger_data.get('produces', []))
|
||
|
||
# 提取请求体信息 (OpenAPI 3.0 格式)
|
||
request_body = operation.get('requestBody', {})
|
||
|
||
# 创建端点对象
|
||
endpoint = SwaggerEndpoint(
|
||
path=path,
|
||
method=method.upper(),
|
||
summary=summary,
|
||
description=description,
|
||
operation_id=operation_id,
|
||
tags=operation_tags,
|
||
parameters=parameters,
|
||
responses=responses,
|
||
consumes=consumes,
|
||
produces=produces,
|
||
request_body=request_body
|
||
)
|
||
|
||
endpoints.append(endpoint)
|
||
|
||
# 创建返回对象
|
||
return ParsedSwaggerSpec(
|
||
endpoints=endpoints,
|
||
info=info,
|
||
swagger_version=swagger_version,
|
||
host=host,
|
||
base_path=base_path,
|
||
schemes=schemes,
|
||
tags=tags,
|
||
categories=categories
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"解析Swagger文件时出错: {str(e)}")
|
||
return None
|
||
|
||
def parse_business_logic_flow(self, flow_description: str) -> Optional[ParsedBusinessLogic]:
|
||
"""
|
||
Parses a business logic flow description.
|
||
The format of this description is TBD and this parser would need to be built accordingly.
|
||
|
||
Args:
|
||
flow_description: The string content describing the business logic flow.
|
||
|
||
Returns:
|
||
A ParsedBusinessLogic object or None if parsing fails.
|
||
"""
|
||
print(f"[InputParser] Placeholder: Parsing business logic flow. Content: {flow_description[:100]}...")
|
||
# Placeholder: Actual parsing logic will depend on the defined format.
|
||
return ParsedBusinessLogic(name="Example Flow", steps=["Step 1 API call", "Step 2 Validate Response"])
|
||
|
||
# Add other parsers as needed (e.g., for data object definitions) |