Expand description
§Chapter 5: OpenAPI Customization
This chapter covers how to customize the generated OpenAPI specification with tags, descriptions, and metadata.
§Adding Operation Tags
Tags help organize operations in the generated documentation:
// Single tag
client.get("/users")?
.with_tag("users")
.await?;
// Multiple tags
client.post("/admin/users")?
.with_tags(["users", "admin"])
.await?;Tags appear in the OpenAPI spec and are used by documentation tools to group related endpoints.
§Operation Descriptions
Add descriptions to document what operations do:
client.get("/users")?
.with_tag("users")
.with_description("List all users with optional pagination")
.await?;
client.post("/users")?
.with_tag("users")
.with_description("Create a new user account")
.await?;§Response Descriptions
Document what responses mean:
client.get("/users/123")?
.with_response_description("User details or 404 if not found")
.await?;
client.post("/users")?
.with_response_description("The newly created user with generated ID")
.await?;§API Info Configuration
Configure the API’s metadata when building the client:
use clawspec_core::ApiClient;
use utoipa::openapi::{ContactBuilder, InfoBuilder, LicenseBuilder};
let info = InfoBuilder::new()
.title("My API")
.version("1.0.0")
.description(Some("A comprehensive REST API for managing resources"))
.contact(Some(
ContactBuilder::new()
.name(Some("API Support"))
.email(Some("support@example.com"))
.url(Some("https://example.com/support"))
.build(),
))
.license(Some(
LicenseBuilder::new()
.name("MIT")
.url(Some("https://opensource.org/licenses/MIT"))
.build(),
))
.build();
let client = ApiClient::builder()
.with_host("api.example.com")
.with_info(info)
.build()?;§Server Configuration
Define servers in the OpenAPI spec:
use clawspec_core::ApiClient;
use utoipa::openapi::ServerBuilder;
let client = ApiClient::builder()
.with_host("api.example.com")
.add_server(
ServerBuilder::new()
.url("https://api.example.com")
.description(Some("Production server"))
.build(),
)
.add_server(
ServerBuilder::new()
.url("https://staging-api.example.com")
.description(Some("Staging server"))
.build(),
)
.build()?;§Manual Schema Registration
Sometimes you need to register schemas that aren’t automatically captured:
use clawspec_core::{ApiClient, register_schemas};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
struct Address {
street: String,
city: String,
country: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
struct User {
id: u64,
name: String,
address: Address, // Nested schema
}
#[derive(Serialize, Deserialize, ToSchema)]
struct ApiError {
code: String,
message: String,
}
let mut client = ApiClient::builder().build()?;
// Register nested schemas and error types
register_schemas!(client, User, Address, ApiError).await;This is particularly useful for:
- Nested schemas that might not be fully resolved
- Error response types
- Schemas used in headers or other non-body locations
§Combining Everything
Here’s a complete example with all customizations:
use clawspec_core::{ApiClient, register_schemas};
use utoipa::openapi::{ContactBuilder, InfoBuilder, ServerBuilder};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Serialize, ToSchema)]
struct CreateUser { name: String }
#[derive(Deserialize, ToSchema)]
struct User { id: u64, name: String }
#[derive(Deserialize, ToSchema)]
struct ApiError { code: String, message: String }
// Configure client with full metadata
let info = InfoBuilder::new()
.title("User Management API")
.version("2.0.0")
.description(Some("API for managing user accounts"))
.build();
let mut client = ApiClient::builder()
.with_host("api.example.com")
.with_info(info)
.add_server(
ServerBuilder::new()
.url("https://api.example.com/v2")
.description(Some("Production"))
.build(),
)
.build()?;
// Register error schema
register_schemas!(client, ApiError).await;
// Make requests with full documentation
let user: User = client.post("/users")?
.json(&CreateUser { name: "Alice".to_string() })?
.with_tag("users")
.with_description("Create a new user account")
.with_response_description("The created user with assigned ID")
.await?
.as_json()
.await?;
// Generate the OpenAPI spec
let spec = client.collected_openapi().await;
println!("{}", spec.to_yaml()?);§Key Points
- Use
.with_tag()and.with_tags()to organize operations - Use
.with_description()to document operations - Configure API info and servers at the client builder level
- Use
register_schemas!for nested or error schemas
Next: Chapter 6: Redaction - Creating stable examples with dynamic value redaction.