Code generation quickstart
Altstack generators consume OpenAPI or AsyncAPI documents and emit language-native validation models and lookup structures. They do not require the source service to use an Altstack server.
Choose the target you need:
| Input | Output | Generator |
|---|---|---|
| OpenAPI | TypeScript interfaces, Zod schemas, Request/Response maps | @alt-stack/zod-openapi |
| OpenAPI | Python Pydantic models, Request/Response maps, and an asyncio client | alt-stack-openapi-pydantic (runtime: alt-stack-http-client-httpx) |
| OpenAPI | Rust Serde models and route modules | rust-openapi |
| OpenAPI | complete Rust crate scaffold | rust-openapi-crate-gen |
| AsyncAPI | TypeScript Zod schemas and a Topics map | @alt-stack/zod-asyncapi |
TypeScript from OpenAPI
Install the generator and its generated-code runtime:
pnpm add zod
pnpm add -D @alt-stack/zod-openapi
Create openapi.json:
{
"openapi": "3.0.0",
"info": { "title": "Users", "version": "1.0.0" },
"paths": {
"/users/{id}": {
"get": {
"parameters": [
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
],
"responses": {
"200": {
"description": "Found",
"content": {
"application/json": { "schema": { "$ref": "#/components/schemas/User" } }
}
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": { "id": { "type": "string" }, "name": { "type": "string" } },
"required": ["id", "name"],
"additionalProperties": false
}
}
}
}
Generate:
pnpm zod-openapi openapi.json --output src/generated-api.ts
The output includes User, UserSchema, route schemas, and Request/Response values suitable for @alt-stack/http-client-fetch or @alt-stack/http-client-ky:
import { createApiClient } from "@alt-stack/http-client-fetch";
import { Request, Response, UserSchema } from "./generated-api.js";
const api = createApiClient({
baseUrl: "http://127.0.0.1:3000",
Request,
Response,
});
const localUser = UserSchema.parse({ id: "u_1", name: "Ada" });
const result = await api.get("/users/{id}", { params: { id: localUser.id } });
The final request assumes the example API is running on port 3000. See the HTTP client quickstart for a complete local server-and-client run.
Python from OpenAPI
Python generation requires Python 3.11 or newer and Pydantic 2.7 or newer.
python -m pip install alt-stack-openapi-pydantic
alt-stack-openapi-pydantic openapi.json --output generated_types.py
Validate with the generated model:
from generated_types import User
user = User.model_validate({"id": "u_1", "name": "Ada"})
print(user.name)
The generated module also contains Request and Response dictionaries whose leaves are Pydantic model classes. Their keys are statically typed, so indexing with literal paths, methods, and status codes yields the exact model class:
from generated_types import Request, Response
body_class = Request["/users"]["POST"]["body"] # type[CreateUserBody]
user_class = Response["/users/{id}"]["GET"]["200"] # type[User]
Response["/users/{id}"]["GET"]["999"] # type checker error: unknown status
The module also emits an asyncio HttpxApiClient with one typed method per route. SDK consumers install the runtime package (python -m pip install alt-stack-http-client-httpx), or use ApiClient(base_url, transport=..., request_map=..., response_map=...) with their own Transport implementation:
import asyncio
from alt_stack_http_client_httpx import ApiSuccess
from generated_types import GetUsersIdParams, HttpxApiClient, Request, Response
async def main() -> None:
async with HttpxApiClient(
"https://api.example.com", request_map=Request, response_map=Response
) as client:
result = await client.get("/users/{id}", params=GetUsersIdParams(id="u_1"))
if isinstance(result, ApiSuccess):
print(result.body.name) # result.body is User
asyncio.run(main())
Rust models from OpenAPI
The Rust generators are workspace crates in this repository. From the repository root:
cargo run -p rust-openapi -- openapi.json --output /tmp/generated.rs
The generated source contains Serde models, route type modules, the original document as OPENAPI_JSON, and a default_http_client re-export. A standalone file still needs a crate manifest with the dependencies used by its emitted types.
For a complete crate scaffold instead:
cargo run -p rust-openapi-crate-gen -- \
openapi.json \
--package-name users-sdk \
--output /tmp/users-sdk \
--runtime-path "$PWD/packages/http-client-rust-tokio"
cargo check --manifest-path /tmp/users-sdk/Cargo.toml
Use --runtime-version 0.1.0 instead of --runtime-path when the runtime is available from your configured Cargo registry.
TypeScript from AsyncAPI
Install:
pnpm add zod
pnpm add -D @alt-stack/zod-asyncapi
Create asyncapi.json using the AsyncAPI 3 channel shape consumed by the generator:
{
"asyncapi": "3.0.0",
"info": { "title": "Jobs", "version": "1.0.0" },
"channels": {
"emailJob": {
"address": "email.send",
"messages": {
"SendEmail": {
"payload": {
"type": "object",
"properties": {
"to": { "type": "string", "format": "email" },
"subject": { "type": "string" }
},
"required": ["to", "subject"],
"additionalProperties": false
}
}
}
}
}
}
Generate and use the topic map:
pnpm zod-asyncapi asyncapi.json --output src/generated-topics.ts
import { Topics, type MessageType } from "./generated-topics.js";
type EmailMessage = MessageType<"email.send">;
const message: EmailMessage = Topics["email.send"].parse({
to: "ada@example.com",
subject: "Welcome",
});
Generated files are replaceable
Every emitted TypeScript and Python module, Rust source file, and scaffold README identifies itself as generated. Do not hand-edit generated output. Keep changes in the source document, registry/include file, or generator configuration, then regenerate.
Read Code generation common patterns for registries, include files, route shapes, CI drift checks, and current input constraints. The package-specific API pages document every flag and programmatic export.