from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
ROOTS = [
"CreateCollectionRequestV2",
"TextUpsertRequest",
"TextSearchRequest",
"AddSourceRequest",
"RefreshRequest",
]
HERE = Path(__file__).resolve().parent.parent
SPEC = HERE.parent.parent / "docs" / "reference" / "openapi.json"
OUT = HERE / "src" / "generated.rs"
def rust_type(schema: dict, schemas: dict, needed: list) -> str:
if "$ref" in schema:
name = schema["$ref"].rsplit("/", 1)[1]
if name not in needed:
needed.append(name)
return name
if "anyOf" in schema:
inner = [s for s in schema["anyOf"] if s.get("type") != "null"]
if len(inner) == 1:
return rust_type(inner[0], schemas, needed)
return "serde_json::Value"
kind = schema.get("type")
if kind == "string":
return "String"
if kind == "integer":
return "i64"
if kind == "number":
return "f64"
if kind == "boolean":
return "bool"
if kind == "array":
return f"Vec<{rust_type(schema.get('items', {}), schemas, needed)}>"
if kind == "object":
return "serde_json::Map<String, serde_json::Value>"
return "serde_json::Value"
def doc_lines(text: str | None, indent: str) -> list[str]:
if not text:
return []
first = text.strip().splitlines()[0].strip()
return [f"{indent}/// {first}"]
def emit_struct(name: str, schema: dict, schemas: dict, needed: list) -> list[str]:
required = set(schema.get("required", []))
lines = doc_lines(schema.get("description"), "")
lines += [
"#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]",
f"pub struct {name} {{",
]
for field, prop in sorted(schema.get("properties", {}).items()):
lines += doc_lines(prop.get("description"), " ")
ty = rust_type(prop, schemas, needed)
if field in required:
lines.append(f" pub {field}: {ty},")
else:
lines.append(' #[serde(default, skip_serializing_if = "Option::is_none")]')
lines.append(f" pub {field}: Option<{ty}>,")
lines.append("}")
lines.append("")
return lines
def main() -> None:
spec = json.loads(SPEC.read_text())
schemas = spec["components"]["schemas"]
needed = list(ROOTS)
out = [
"// Generated by scripts/generate.py from docs/reference/openapi.json; do not edit.",
"// Regenerate with: python3 -I scripts/generate.py (run from sdk/rust).",
f"// Schemas: {', '.join(ROOTS)} and the ones they reference.",
"//",
"// The request bodies the client sends. Every field the spec does not list",
"// as required is an Option that is omitted from the JSON when None.",
"",
]
i = 0
while i < len(needed):
name = needed[i]
out += emit_struct(name, schemas[name], schemas, needed)
i += 1
OUT.write_text("\n".join(out).rstrip() + "\n")
if shutil.which("rustfmt"):
subprocess.run(["rustfmt", "--edition", "2021", str(OUT)], check=True)
print(f"wrote {OUT} ({len(needed)} structs)")
if __name__ == "__main__":
main()