#!/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()