forked from xiongyuxing/tiku-backend.net
96 lines
3.4 KiB
Python
Executable File
96 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Compare two OpenAPI documents by normalized HTTP method and path."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import csv
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
HTTP_METHODS = {"delete", "get", "head", "options", "patch", "post", "put", "trace"}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--legacy", required=True, type=Path, help="Legacy OpenAPI JSON")
|
|
parser.add_argument("--target", required=True, type=Path, help="Target OpenAPI JSON")
|
|
parser.add_argument("--output", required=True, type=Path, help="Output CSV")
|
|
return parser.parse_args()
|
|
|
|
|
|
def load_operations(path: Path) -> dict[tuple[str, str], dict[str, str]]:
|
|
with path.open(encoding="utf-8") as source:
|
|
document: dict[str, Any] = json.load(source)
|
|
|
|
operations: dict[tuple[str, str], dict[str, str]] = {}
|
|
for route, path_item in document.get("paths", {}).items():
|
|
if not isinstance(path_item, dict):
|
|
continue
|
|
for method, operation in path_item.items():
|
|
normalized_method = method.lower()
|
|
if normalized_method not in HTTP_METHODS or not isinstance(operation, dict):
|
|
continue
|
|
operations[(normalized_method.upper(), route)] = {
|
|
"operation_id": str(operation.get("operationId", "")),
|
|
"summary": str(operation.get("summary", "")),
|
|
}
|
|
return operations
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
legacy = load_operations(args.legacy)
|
|
target = load_operations(args.target)
|
|
keys = sorted(legacy.keys() | target.keys(), key=lambda key: (key[1], key[0]))
|
|
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
with args.output.open("w", encoding="utf-8", newline="") as destination:
|
|
writer = csv.DictWriter(
|
|
destination,
|
|
fieldnames=(
|
|
"method",
|
|
"path",
|
|
"status",
|
|
"legacy_operation_id",
|
|
"legacy_summary",
|
|
"target_operation_id",
|
|
"target_summary",
|
|
),
|
|
lineterminator="\n",
|
|
)
|
|
writer.writeheader()
|
|
for method, route in keys:
|
|
legacy_operation = legacy.get((method, route), {})
|
|
target_operation = target.get((method, route), {})
|
|
status = (
|
|
"exact_match"
|
|
if legacy_operation and target_operation
|
|
else "legacy_only"
|
|
if legacy_operation
|
|
else "target_only"
|
|
)
|
|
writer.writerow(
|
|
{
|
|
"method": method,
|
|
"path": route,
|
|
"status": status,
|
|
"legacy_operation_id": legacy_operation.get("operation_id", ""),
|
|
"legacy_summary": legacy_operation.get("summary", ""),
|
|
"target_operation_id": target_operation.get("operation_id", ""),
|
|
"target_summary": target_operation.get("summary", ""),
|
|
}
|
|
)
|
|
|
|
exact_matches = len(legacy.keys() & target.keys())
|
|
print(
|
|
f"legacy={len(legacy)} target={len(target)} exact={exact_matches} "
|
|
f"legacy_only={len(legacy) - exact_matches} target_only={len(target) - exact_matches}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|