#!/usr/bin/env python3 """Post-process openapi.json before handing it to Orval. oatpp 1.3's Swagger generator produces a valid spec but with rough edges that make the generated client less ergonomic: - Missing operationIds on some endpoints → orval falls back to path-based names that collide. We synthesise an operationId from method + path. - summary/description strings occasionally contain stray control characters. - Some endpoints have no tags, which throws off orval's file-splitting. Run between `fetch-openapi.sh` and `orval` (see package.json `codegen`). """ import json import re import sys from pathlib import Path SRC = Path("openapi.json") if not SRC.exists(): sys.exit(f"{SRC} not found — run fetch-openapi.sh first") spec = json.loads(SRC.read_text()) def op_id(method: str, path: str) -> str: """`getApiAnnouncementsEntityIdHistory` from `GET /api/announcements/{entity_id}/history`.""" parts = [method.lower()] for seg in path.strip("/").split("/"): if seg.startswith("{") and seg.endswith("}"): parts.append(seg[1:-1]) else: parts.append(seg) return re.sub(r"[^A-Za-z0-9]+(\w)", lambda m: m.group(1).upper(), "_".join(parts)) paths = spec.get("paths", {}) for path, methods in paths.items(): for method, op in methods.items(): if not isinstance(op, dict): continue op.setdefault("operationId", op_id(method, path)) op.setdefault("tags", [path.strip("/").split("/")[1] if "/" in path.strip("/") else "default"]) # oatpp 1.3 emits `"required": []` on schemas that have no required fields. # OpenAPI 3.0.x rejects this (`must NOT have fewer than 1 items`), which # breaks strict consumers like Orval's Zod generator. Strip empty arrays — # absence of the keyword has the same semantics. schemas = spec.get("components", {}).get("schemas", {}) stripped = 0 for sch in schemas.values(): if isinstance(sch, dict) and sch.get("required") == []: del sch["required"] stripped += 1 SRC.write_text(json.dumps(spec, indent=2)) print(f" postprocessed {SRC} — {len(paths)} paths" + (f", stripped empty `required` from {stripped} schemas" if stripped else ""))