API 契约测试引擎(专业版)
输入
check_contract(actual, expected);is_contract_valid(problems)
expected 叶子值为类型字符串,嵌套结构用 dict 递归。
输出
问题元组列表 (kind,path,expected,actual);契约是否通过 bool
硬性规则
- 三类问题:missing/type_mismatch/extra,路径以 $.字段 定位。
- boolean 与 integer 分别判定,不混为数字。
- 仅 missing/type_mismatch 阻断;extra 不阻断。
示例
check_contract({{"id":1}},{{"id":"integer","name":"string"}}) → 含 missing $.name
is_contract_valid([("extra","$.remark",None,None)]) → True
Python 引擎(可独立运行)
# -*- coding: utf-8 -*-
def _typeof(v):
if isinstance(v, bool): return "boolean"
if isinstance(v, int): return "integer"
if isinstance(v, float): return "number"
if isinstance(v, str): return "string"
if isinstance(v, list): return "array"
if isinstance(v, dict): return "object"
if v is None: return "null"
return "unknown"
def check_contract(actual, expected):
"""递归比对响应与契约。expected 值为类型字符串;嵌套 dict 递归。
返回问题列表:missing(缺字段)/type_mismatch(类型不符)/extra(多余字段,不阻断)。"""
problems = []
def walk(a, e, path="$"):
if isinstance(e, dict):
if not isinstance(a, dict):
problems.append(("type_mismatch", path, "object", _typeof(a))); return
for k, t in e.items():
kp = f"{path}.{k}"
if k not in a:
problems.append(("missing", kp, t, None)); continue
if isinstance(t, dict):
walk(a[k], t, kp)
elif _typeof(a[k]) != t:
problems.append(("type_mismatch", kp, t, _typeof(a[k])))
for k in a:
if k not in e:
problems.append(("extra", f"{path}.{k}", None, None))
elif _typeof(a) != e:
problems.append(("type_mismatch", path, e, _typeof(a)))
walk(actual, expected)
return problems
def is_contract_valid(problems):
"""missing/type_mismatch 阻断;extra(向后兼容新增字段)不阻断。"""
return not any(p[0] in ("missing", "type_mismatch") for p in problems)