webapp-scaffold/bin/postprocess-openapi.py
Uwe Schuster 4c4d52e3de postprocess-openapi: strip empty required: [] arrays
oatpp 1.3 emits `"required": []` on schemas with no required fields.
OpenAPI 3.0.x rejects this with "must NOT have fewer than 1 items",
which silently breaks strict consumers. Orval's typed fetch client
swallowed it (the violation surfaces only as a non-fatal warning), but
Orval's Zod generator (added downstream in fewo-webapp's #469 increment 2)
fails hard:

  must NOT have fewer than 1 items at /components/schemas/<DTO>/required
  must have required property '$ref'                  at /components/schemas/<DTO>
  must match exactly one schema in oneOf              at /components/schemas/<DTO>

83 of fewo-webapp's 198 DTOs trip this. Stripping the empty array (absence
of the keyword has the same semantics) is the spec-compliant fix.

Bumps to v0.4.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:22:30 +02:00

59 lines
2.2 KiB
Python
Executable file

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