File size: 5,309 Bytes
621645b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""

Schema清理和验证工具

"""
from typing import Any, Dict, List

def is_empty_value(value: Any) -> bool:
    """检查值是否为空"""
    if value is None:
        return True
    if isinstance(value, str) and value.strip() == "":
        return True
    if isinstance(value, (list, dict)) and len(value) == 0:
        return True
    return False


def deep_clean(value: Any) -> Any:
    """深度清理值,移除空值"""
    if isinstance(value, dict):
        cleaned: Dict[str, Any] = {}
        for k, v in value.items():
            vv = deep_clean(v)
            if is_empty_value(vv):
                continue
            cleaned[k] = vv
        return cleaned
    if isinstance(value, list):
        cleaned_list = []
        for item in value:
            ii = deep_clean(item)
            if is_empty_value(ii):
                continue
            cleaned_list.append(ii)
        return cleaned_list
    if isinstance(value, str):
        return value.strip()
    return value


def infer_type_for_property(prop_name: str) -> str:
    """根据属性名推断类型"""
    name = prop_name.lower()
    if name in ("url", "uri", "href", "link"):
        return "string"
    if name in ("headers", "options", "params", "payload", "data"):
        return "object"
    return "string"


def ensure_property_schema(name: str, schema: Dict[str, Any]) -> Dict[str, Any]:
    """确保属性schema的完整性"""
    prop = dict(schema) if isinstance(schema, dict) else {}
    
    # 对于空dict,先不清理,保留以便后续处理
    if not prop:
        prop = {}
    
    # 必填:type & description
    if "type" not in prop or not isinstance(prop.get("type"), str) or not prop["type"].strip():
        prop["type"] = infer_type_for_property(name)
    if "description" not in prop or not isinstance(prop.get("description"), str) or not prop["description"].strip():
        prop["description"] = f"{name} parameter"

    # 特殊处理 headers
    if name.lower() == "headers":
        prop["type"] = "object"
        headers_props = prop.get("properties")
        if not isinstance(headers_props, dict):
            headers_props = {}
        headers_props = deep_clean(headers_props)
        if not headers_props:
            headers_props = {
                "user-agent": {
                    "type": "string",
                    "description": "User-Agent header for the request",
                }
            }
        else:
            # 清理并保证每个 header 的子属性都具备 type/description
            fixed_headers: Dict[str, Any] = {}
            for hk, hv in headers_props.items():
                sub = deep_clean(hv if isinstance(hv, dict) else {})
                if "type" not in sub or not isinstance(sub.get("type"), str) or not sub["type"].strip():
                    sub["type"] = "string"
                if "description" not in sub or not isinstance(sub.get("description"), str) or not sub["description"].strip():
                    sub["description"] = f"{hk} header"
                fixed_headers[hk] = sub
            headers_props = fixed_headers
        prop["properties"] = headers_props
        # 处理 required 空数组
        if isinstance(prop.get("required"), list):
            req = [r for r in prop["required"] if isinstance(r, str) and r in headers_props]
            if req:
                prop["required"] = req
            else:
                prop.pop("required", None)
        # additionalProperties 若为空 dict,删除
        if isinstance(prop.get("additionalProperties"), dict) and len(prop["additionalProperties"]) == 0:
            prop.pop("additionalProperties", None)

    return prop


def sanitize_json_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
    """清理和修正JSON Schema"""
    s = dict(schema) if isinstance(schema, dict) else {}

    # 如果存在 properties,则顶层应为 object
    if "properties" in s and not isinstance(s.get("type"), str):
        s["type"] = "object"

    # 修正 $schema
    if "$schema" in s and not isinstance(s["$schema"], str):
        s.pop("$schema", None)
    if "$schema" not in s:
        s["$schema"] = "http://json-schema.org/draft-07/schema#"

    properties = s.get("properties")
    if isinstance(properties, dict):
        fixed_props: Dict[str, Any] = {}
        for name, subschema in properties.items():
            fixed_props[name] = ensure_property_schema(name, subschema if isinstance(subschema, dict) else {})
        s["properties"] = fixed_props

    # required:去掉不存在的属性,且不允许为空列表
    if isinstance(s.get("required"), list):
        if isinstance(properties, dict):
            req = [r for r in s["required"] if isinstance(r, str) and r in properties]
        else:
            req = []
        if req:
            s["required"] = req
        else:
            s.pop("required", None)

    # additionalProperties:空 dict 视为无效,删除
    if isinstance(s.get("additionalProperties"), dict) and len(s["additionalProperties"]) == 0:
        s.pop("additionalProperties", None)

    return s