forked from xiongyuxing/tiku-backend.net
chore: formalize dotnet backend repository
This commit is contained in:
95
scripts/compare_openapi.py
Executable file
95
scripts/compare_openapi.py
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user