feat(openapi): unified master over all 36 services (regenerable via merge.py)
hanzo.yaml was only aggregating ~5 services. merge.py now regenerates it from EVERY per-service spec: 1581 paths, 729 schemas, 0 dangling $refs. Component names namespaced per service (<svc>_<Name>) so specs never clobber. Edit a per-service <svc>/openapi.yaml and re-run merge.py — one unified /v1/ surface. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+71206
-90
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regenerate the unified master `hanzo.yaml` from every per-service spec.
|
||||
|
||||
The master is the ONE unified OpenAPI surface for all Hanzo services. It is a
|
||||
pure aggregation — edit a per-service `<svc>/openapi.yaml`, then run this:
|
||||
|
||||
python3 merge.py
|
||||
|
||||
Every service is exposed at `https://api.hanzo.ai/v1/<service>/*`, so paths are
|
||||
already namespaced and never collide. Component schemas/responses/parameters ARE
|
||||
namespaced here (`<svc>_<Name>`) with their `$ref`s rewritten to match, so two
|
||||
services can both define `Error`/`User`/etc. without clobbering each other.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def prefix(node, svc):
|
||||
"""Rewrite local component $refs to the namespaced `<svc>_` form."""
|
||||
if isinstance(node, dict):
|
||||
out = {}
|
||||
for k, v in node.items():
|
||||
if k == "$ref" and isinstance(v, str) and v.startswith("#/components/"):
|
||||
kind = v.split("/")[2] # schemas | responses | parameters | ...
|
||||
if kind in ("schemas", "responses", "parameters"):
|
||||
name = "/".join(v.split("/")[3:])
|
||||
out[k] = f"#/components/{kind}/{svc}_{name}"
|
||||
continue
|
||||
out[k] = prefix(v, svc)
|
||||
return out
|
||||
if isinstance(node, list):
|
||||
return [prefix(x, svc) for x in node]
|
||||
return node
|
||||
|
||||
|
||||
def main():
|
||||
services = sorted(
|
||||
d for d in os.listdir(ROOT)
|
||||
if os.path.isdir(os.path.join(ROOT, d))
|
||||
and os.path.isfile(os.path.join(ROOT, d, "openapi.yaml"))
|
||||
and d != "shared"
|
||||
)
|
||||
|
||||
paths, schemas, responses, params, secschemes, tags = {}, {}, {}, {}, {}, []
|
||||
for svc in services:
|
||||
spec = yaml.safe_load(open(os.path.join(ROOT, svc, "openapi.yaml")))
|
||||
comps = spec.get("components", {}) or {}
|
||||
for p, item in (spec.get("paths", {}) or {}).items():
|
||||
paths[p] = prefix(item, svc)
|
||||
for n, x in (comps.get("schemas", {}) or {}).items():
|
||||
schemas[f"{svc}_{n}"] = prefix(x, svc)
|
||||
for n, x in (comps.get("responses", {}) or {}).items():
|
||||
responses[f"{svc}_{n}"] = prefix(x, svc)
|
||||
for n, x in (comps.get("parameters", {}) or {}).items():
|
||||
params[f"{svc}_{n}"] = prefix(x, svc)
|
||||
for n, x in (comps.get("securitySchemes", {}) or {}).items():
|
||||
secschemes.setdefault(n, x)
|
||||
tags.append({"name": svc, "description": spec.get("info", {}).get("title", svc)})
|
||||
|
||||
components = {
|
||||
"securitySchemes": secschemes or {"bearerAuth": {"type": "http", "scheme": "bearer"}},
|
||||
"schemas": dict(sorted(schemas.items())),
|
||||
}
|
||||
if responses:
|
||||
components["responses"] = dict(sorted(responses.items()))
|
||||
if params:
|
||||
components["parameters"] = dict(sorted(params.items()))
|
||||
|
||||
out = {
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Hanzo Cloud — Unified API",
|
||||
"description": (
|
||||
"The single unified OpenAPI surface for ALL Hanzo services, "
|
||||
"aggregated from the per-service specs. Every route is "
|
||||
"https://api.hanzo.ai/v1/<service>/*. Regenerated by merge.py — "
|
||||
"edit the per-service <svc>/openapi.yaml, not this file."
|
||||
),
|
||||
"version": "1.0.0",
|
||||
"contact": {"name": "Hanzo AI", "url": "https://hanzo.ai", "email": "[email protected]"},
|
||||
"license": {"name": "Proprietary"},
|
||||
},
|
||||
"servers": [{"url": "https://api.hanzo.ai", "description": "Hanzo Gateway"}],
|
||||
"tags": tags,
|
||||
"security": [{"bearerAuth": []}],
|
||||
"paths": dict(sorted(paths.items())),
|
||||
"components": components,
|
||||
}
|
||||
yaml.dump(out, open(os.path.join(ROOT, "hanzo.yaml"), "w"),
|
||||
default_flow_style=False, sort_keys=False, width=120)
|
||||
print(f"merged {len(services)} services → {len(paths)} paths, "
|
||||
f"{len(schemas)} schemas, {len(responses)} responses, {len(params)} params")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user