Module chapter_5

Module chapter_5 

Source
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.