OpenAPI Support
Ultimo automatically generates OpenAPI 3.0 specifications from your RPC procedures, enabling integration with Swagger UI, Postman, and code generation tools.
Overview
The OpenAPI generator creates complete API specifications including:
- All RPC endpoints with paths and methods
- Request/response schemas from TypeScript type definitions
- Parameter definitions (path, query, body)
- HTTP status codes and error responses
- Server information and metadata
Basic Usage
Generate an OpenAPI spec from your RPC registry:
use ultimo::prelude::*;
use ultimo::rpc::RpcMode;
let rpc = RpcRegistry::new_with_mode(RpcMode::Rest);
// Register procedures
rpc.query("getUser", handler, "{ id: number }", "User");
rpc.mutation("createUser", handler, "{ name: string; email: string }", "User");
// Generate OpenAPI spec
let openapi = rpc.generate_openapi(
"My API", // API title
"1.0.0", // Version
"/api" // Base path
);
// Write to file
openapi.write_to_file("openapi.json")?;Complete Example
use ultimo::prelude::*;
use ultimo::rpc::RpcMode;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct GetUserInput {
id: u32,
}
#[derive(Serialize)]
struct User {
id: u32,
name: String,
email: String,
created_at: String,
}
#[derive(Deserialize)]
struct CreateUserInput {
name: String,
email: String,
}
#[derive(Deserialize)]
struct ListUsersInput {
page: Option<u32>,
limit: Option<u32>,
}
#[tokio::main]
async fn main() -> Result<()> {
let mut app = Ultimo::new();
let rpc = RpcRegistry::new_with_mode(RpcMode::Rest);
// Register queries (GET)
rpc.query(
"listUsers",
|input: ListUsersInput| async move {
Ok(json!({
"users": [],
"total": 0,
"page": input.page.unwrap_or(1)
}))
},
r#"{
page?: number;
limit?: number;
}"#.to_string(),
r#"{
users: User[];
total: number;
page: number;
}"#.to_string(),
);
rpc.query(
"getUser",
|input: GetUserInput| async move {
Ok(User {
id: input.id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
created_at: "2024-01-01T00:00:00Z".to_string(),
})
},
"{ id: number }".to_string(),
r#"{
id: number;
name: string;
email: string;
created_at: string;
}"#.to_string(),
);
// Register mutations (POST)
rpc.mutation(
"createUser",
|input: CreateUserInput| async move {
Ok(User {
id: 1,
name: input.name,
email: input.email,
created_at: "2024-01-01T00:00:00Z".to_string(),
})
},
r#"{
name: string;
email: string;
}"#.to_string(),
"User".to_string(),
);
rpc.mutation(
"deleteUser",
|input: GetUserInput| async move {
Ok(json!({ "success": true }))
},
"{ id: number }".to_string(),
"{ success: boolean }".to_string(),
);
// Generate OpenAPI specification
let openapi = rpc.generate_openapi(
"User Management API",
"1.0.0",
"/api"
);
openapi.write_to_file("openapi.json")?;
println!("✅ OpenAPI spec generated: openapi.json");
app.listen("127.0.0.1:3000").await
}Generated OpenAPI Structure
The generated openapi.json looks like:
{
"openapi": "3.0.0",
"info": {
"title": "User Management API",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:3000/api"
}
],
"paths": {
"/listUsers": {
"get": {
"operationId": "listUsers",
"parameters": [
{
"name": "page",
"in": "query",
"schema": { "type": "number" }
},
{
"name": "limit",
"in": "query",
"schema": { "type": "number" }
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"users": {
"type": "array",
"items": { "$ref": "#/components/schemas/User" }
},
"total": { "type": "number" },
"page": { "type": "number" }
}
}
}
}
}
}
}
},
"/createUser": {
"post": {
"operationId": "createUser",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
},
"required": ["name", "email"]
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/User" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" },
"email": { "type": "string" },
"created_at": { "type": "string" }
}
}
}
}
}View with Swagger UI
Use Docker to quickly view your API docs:
docker run -p 8080:8080 \
-e SWAGGER_JSON=/openapi.json \
-v $(pwd)/openapi.json:/openapi.json \
swaggerapi/swagger-uiThen open http://localhost:8080
Mock Server with Prism
Create a mock server for testing:
# Install Prism
npm install -g @stoplight/prism-cli
# Run mock server
prism mock openapi.json
# Test it
curl http://localhost:4010/api/getUser?id=1Generate Clients
Use OpenAPI Generator to create clients in any language:
# Install generator
npm install -g @openapitools/openapi-generator-cli
# Generate TypeScript client
openapi-generator-cli generate \
-i openapi.json \
-g typescript-fetch \
-o ./typescript-client
# Generate Python client
openapi-generator-cli generate \
-i openapi.json \
-g python \
-o ./python-client
# Generate Go client
openapi-generator-cli generate \
-i openapi.json \
-g go \
-o ./go-clientAdvanced Type Definitions
Complex Types
rpc.query(
"searchUsers",
handler,
r#"{
query: string;
filters?: {
role?: 'admin' | 'user' | 'guest';
active?: boolean;
createdAfter?: string;
};
sort?: {
field: 'name' | 'email' | 'created_at';
order: 'asc' | 'desc';
};
pagination?: {
page: number;
limit: number;
};
}"#.to_string(),
r#"{
results: User[];
total: number;
page: number;
hasMore: boolean;
}"#.to_string(),
);Arrays and Objects
rpc.mutation(
"bulkCreateUsers",
handler,
r#"{
users: Array<{
name: string;
email: string;
}>;
}"#.to_string(),
r#"{
created: User[];
failed: Array<{
email: string;
error: string;
}>;
}"#.to_string(),
);Union Types
rpc.query(
"getResource",
handler,
"{ id: number }".to_string(),
r#"{
data: User | Post | Comment;
type: 'user' | 'post' | 'comment';
}"#.to_string(),
);RPC Mode Differences
REST Mode
Each procedure gets its own path:
{
"paths": {
"/getUser": {
"get": { ... }
},
"/createUser": {
"post": { ... }
}
}
}JSON-RPC Mode
Single endpoint with method parameter:
{
"paths": {
"/rpc": {
"post": {
"requestBody": {
"schema": {
"properties": {
"method": {
"type": "string",
"enum": ["getUser", "createUser", "deleteUser"]
},
"params": { ... }
}
}
}
}
}
}
}CI/CD Integration
Generate specs automatically in CI:
# .github/workflows/openapi.yml
name: Generate OpenAPI Spec
on:
push:
branches: [main]
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate OpenAPI spec
run: |
cargo run --bin generate-openapi
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: openapi-spec
path: openapi.jsonBest Practices
✅ Version Your API
let openapi = rpc.generate_openapi(
"My API",
"2.1.0", // Semantic versioning
"/api/v2" // Version in URL
);✅ Document Complex Types
rpc.query(
"getUserProfile",
handler,
"{ id: number }".to_string(),
r#"{
user: {
id: number;
name: string;
email: string;
};
stats: {
posts: number;
followers: number;
following: number;
};
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}"#.to_string(),
);✅ Keep Specs Updated
Regenerate OpenAPI specs when you change your API:
# Add to your build script
cargo run --bin generate-openapi
git add openapi.json
git commit -m "Update OpenAPI spec"✅ Test with Mock Server
Use Prism to test your frontend against the OpenAPI spec:
# Start mock server
prism mock openapi.json &
# Run frontend tests
npm test
# Stop mock server
killall prism