import re
import sys
from pathlib import Path
SRC_TYPES = Path("src/types")
OPENAI_TYPES = Path("openai-types/src")
DOMAIN_MAP = {
"batch": "batch",
"embedding": "embedding",
"file": "file",
"upload": "uploads",
"moderation": "moderation",
"fine_tuning": "fine_tuning",
"image": "image",
"chat": "chat",
"beta": "beta",
"realtime": "realtime",
}
SKIP = {"audio", "model", "common", "responses"}
CRATE_IMPORTS = re.compile(
r"^use (?:crate|super)::.*$|^pub use openai_types::.*\{[^}]*\};?$|^pub use openai_types::.*$|^use crate::openai_enum;$",
re.MULTILINE,
)
ORPHAN_USE_LINES = re.compile(
r"^ \w+.*,$\n|^\};$",
re.MULTILINE,
)
OPENAI_ENUM_RE = re.compile(
r"openai_enum!\s*\{(.*?)\}", re.DOTALL
)
def expand_openai_enum(match: re.Match) -> str:
body = match.group(1).strip()
lines = body.split("\n")
result = []
in_enum = False
for line in lines:
stripped = line.strip()
if stripped.startswith("///"):
result.append(line)
elif stripped.startswith("pub enum"):
result.append("#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]")
result.append('#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]')
result.append("#[non_exhaustive]")
enum_name = stripped.split("{")[0].strip()
result.append(f"{enum_name} {{")
in_enum = True
elif in_enum and "=" in stripped:
parts = stripped.split("=")
variant = parts[0].strip()
value = parts[1].strip().strip('",').strip('"')
variant_lower = re.sub(r"([A-Z])", r"_\1", variant).lower().strip("_")
simple_snake = variant.lower()
if value == simple_snake or value == variant_lower:
result.append(f" {variant},")
else:
result.append(f' #[serde(rename = "{value}")]')
result.append(f" {variant},")
elif in_enum and stripped == "}":
result.append("}")
in_enum = False
elif in_enum and stripped:
result.append(line)
return "\n".join(result)
def transform_source(source: str, domain: str) -> str:
source = CRATE_IMPORTS.sub("", source)
source = ORPHAN_USE_LINES.sub("", source)
source = OPENAI_ENUM_RE.sub(expand_openai_enum, source)
source = source.replace("crate::types::file::FilePurpose", "super::FilePurpose")
source = source.replace("crate::types::file::FileObject", "super::FileObject")
source = source.replace("crate::types::common::", "")
test_start = source.find("#[cfg(test)]")
if test_start != -1:
source = source[:test_start]
source = re.sub(r"^// .* types — .*$", "", source, flags=re.MULTILINE)
header = f"// Manual: hand-crafted {domain} types (enums, builders, precise Optional fields).\n\n"
header += "use serde::{Deserialize, Serialize};\n"
source = re.sub(r"\n{3,}", "\n\n", source).strip()
return header + "\n" + source + "\n"
def extract_tests(source: str) -> str:
test_start = source.find("#[cfg(test)]")
if test_start == -1:
return ""
return source[test_start:]
def migrate_domain(src_name: str, dst_name: str):
src_file = SRC_TYPES / f"{src_name}.rs"
dst_dir = OPENAI_TYPES / dst_name
manual_file = dst_dir / "manual.rs"
if not src_file.exists():
print(f" SKIP {src_name} — not found")
return
source = src_file.read_text()
if source.strip().startswith("pub use openai_types::"):
print(f" SKIP {src_name} — already re-exported")
return
if manual_file.exists():
print(f" SKIP {src_name} — manual.rs already exists")
return
manual_source = transform_source(source, src_name)
type_count = len(re.findall(r"pub (?:struct|enum|type) \w+", manual_source))
dst_dir.mkdir(parents=True, exist_ok=True)
manual_file.write_text(manual_source)
tests = extract_tests(source)
reexport = f"// {src_name.replace('_', ' ').title()} types — re-exported from openai-types.\n\n"
reexport += f"pub use openai_types::{dst_name}::*;\n"
if tests:
reexport += "\n" + tests
src_file.write_text(reexport)
print(f" {src_name} → openai-types/{dst_name}/manual.rs ({type_count} types)")
def main():
print("Migrating hand-crafted types to openai-types...\n")
for src_name, dst_name in sorted(DOMAIN_MAP.items()):
if src_name in SKIP:
continue
migrate_domain(src_name, dst_name)
print("\nNext steps:")
print(" 1. python3 scripts/py2rust.py sync ~/startups/shared/openai-python/src/openai/types/ openai-types/src/")
print(" 2. cargo check && cargo test")
print(" 3. Fix any cross-module references (super:: paths, etc.)")
if __name__ == "__main__":
main()