pub struct ApiClient { /* private fields */ }
Expand description
A type-safe HTTP client for API testing and OpenAPI documentation generation.
ApiClient
is the core component of clawspec that enables you to make HTTP requests
while automatically capturing request/response schemas for OpenAPI specification generation.
It provides a fluent API for building requests with comprehensive parameter support,
status code validation, and automatic schema collection.
§Key Features
- Test-Driven Documentation: Automatically generates OpenAPI specifications from test execution
- Type Safety: Compile-time guarantees for API parameters and response types
- Flexible Status Code Validation: Support for ranges, specific codes, and custom patterns
- Comprehensive Parameter Support: Path, query, and header parameters with multiple styles
- Request Body Formats: JSON, form-encoded, multipart, and raw binary data
- Schema Collection: Automatic detection and collection of request/response schemas
- OpenAPI Metadata: Configurable API info, servers, and operation documentation
§Basic Usage
use clawspec_core::ApiClient;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Deserialize, ToSchema)]
struct User {
id: u32,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create an API client
let mut client = ApiClient::builder()
.with_host("api.example.com")
.with_base_path("/v1")?
.build()?;
// Make a request and capture the schema
let user: User = client
.get("/users/123")?
.await?
.as_json()
.await?;
println!("User: {:?}", user);
// Generate OpenAPI specification from collected data
let openapi_spec = client.collected_openapi().await;
let yaml = serde_yaml::to_string(&openapi_spec)?;
println!("{yaml}");
Ok(())
}
§Builder Pattern
The client is created using a builder pattern that allows you to configure various aspects:
use clawspec_core::ApiClient;
use http::uri::Scheme;
use utoipa::openapi::{InfoBuilder, ServerBuilder};
let client = ApiClient::builder()
.with_scheme(Scheme::HTTPS)
.with_host("api.github.com")
.with_port(443)
.with_base_path("/api/v3")?
.with_info(
InfoBuilder::new()
.title("GitHub API Client")
.version("1.0.0")
.description(Some("Auto-generated from tests"))
.build()
)
.add_server(
ServerBuilder::new()
.url("https://api.github.com/api/v3")
.description(Some("GitHub API v3"))
.build()
)
.build()?;
§Making Requests
The client supports all standard HTTP methods with a fluent API:
use clawspec_core::{ApiClient, expected_status_codes, CallQuery, CallHeaders, ParamValue};
use serde::{Serialize, Deserialize};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
struct UserData { name: String }
let mut client = ApiClient::builder().build()?;
let user_data = UserData { name: "John".to_string() };
// GET request with query parameters and headers
let users = client
.get("/users")?
.with_query(
CallQuery::new()
.add_param("page", ParamValue::new(1))
.add_param("per_page", ParamValue::new(50))
)
.with_header("Authorization", "Bearer token123")
.with_expected_status_codes(expected_status_codes!(200, 404))
.await?
.as_json::<Vec<UserData>>()
.await?;
// POST request with JSON body
let new_user = client
.post("/users")?
.json(&user_data)?
.with_expected_status_codes(expected_status_codes!(201, 409))
.await?
.as_json::<UserData>()
.await?;
§Schema Registration
For types that aren’t automatically detected, you can manually register them:
use clawspec_core::{ApiClient, register_schemas};
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
struct ErrorType { message: String }
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
struct NestedType { value: i32 }
let mut client = ApiClient::builder().build()?;
// Register multiple schemas at once
register_schemas!(client, ErrorType, NestedType);
// Or register individually
client.register_schema::<ErrorType>().await;
§OpenAPI Generation
The client automatically collects information during test execution and can generate comprehensive OpenAPI specifications:
let mut client = ApiClient::builder().build()?;
let user_data = UserData { name: "John".to_string() };
// Make some API calls...
client.get("/users")?.await?.as_json::<Vec<UserData>>().await?;
client.post("/users")?.json(&user_data)?.await?.as_json::<UserData>().await?;
// Generate OpenAPI specification
let openapi = client.collected_openapi().await;
// Convert to YAML or JSON
let yaml = serde_yaml::to_string(&openapi)?;
let json = serde_json::to_string_pretty(&openapi)?;
§Error Handling
The client provides comprehensive error handling for various scenarios:
use clawspec_core::{ApiClient, ApiClientError};
let mut client = ApiClient::builder().build()?;
match client.get("/users/999")?.await {
Ok(response) => {
// Handle successful response
println!("Success!");
}
Err(ApiClientError::UnexpectedStatusCode { status_code, body }) => {
// Handle HTTP errors
println!("HTTP {} error: {}", status_code, body);
}
Err(ApiClientError::ReqwestError(source)) => {
// Handle network/request errors
println!("Request failed: {}", source);
}
Err(err) => {
// Handle other errors
println!("Other error: {}", err);
}
}
§Thread Safety
ApiClient
is designed to be safe to use across multiple threads. The internal schema
collection is protected by async locks, allowing concurrent request execution while
maintaining data consistency.
§Performance Considerations
- Schema collection has minimal runtime overhead
- Request bodies are streamed when possible
- Response processing is lazy - schemas are only collected when responses are consumed
- Internal caching reduces redundant schema processing
Implementations§
Source§impl ApiClient
impl ApiClient
pub async fn collected_paths(&mut self) -> Paths
Sourcepub async fn collected_openapi(&mut self) -> OpenApi
pub async fn collected_openapi(&mut self) -> OpenApi
Generates a complete OpenAPI specification from collected request/response data.
This method aggregates all the information collected during API calls and produces a comprehensive OpenAPI 3.1 specification including paths, components, schemas, operation metadata, and server information.
§Features
- Automatic Path Collection: All endpoint calls are automatically documented
- Schema Generation: Request/response schemas are extracted from Rust types
- Operation Metadata: Includes operation IDs, descriptions, and tags
- Server Information: Configurable server URLs and descriptions
- Tag Collection: Automatically computed from all operations
- Component Schemas: Reusable schema definitions with proper references
§Example
use clawspec_core::ApiClient;
use utoipa::openapi::{InfoBuilder, ServerBuilder};
use serde::{Serialize, Deserialize};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
struct UserData { name: String }
let mut client = ApiClient::builder()
.with_host("api.example.com")
.with_info(
InfoBuilder::new()
.title("My API")
.version("1.0.0")
.build()
)
.add_server(
ServerBuilder::new()
.url("https://api.example.com")
.description(Some("Production server"))
.build()
)
.build()?;
let user_data = UserData { name: "John".to_string() };
// Make some API calls to collect data
client.get("/users")?.await?.as_json::<Vec<UserData>>().await?;
client.post("/users")?.json(&user_data)?.await?.as_json::<UserData>().await?;
// Generate complete OpenAPI specification
let openapi = client.collected_openapi().await;
// The generated spec includes:
// - API info (title, version, description)
// - Server definitions
// - All paths with operations
// - Component schemas
// - Computed tags from operations
// Export to different formats
let yaml = serde_yaml::to_string(&openapi)?;
let json = serde_json::to_string_pretty(&openapi)?;
§Generated Content
The generated OpenAPI specification includes:
- Info: API metadata (title, version, description) if configured
- Servers: Server URLs and descriptions if configured
- Paths: All documented endpoints with operations
- Components: Reusable schema definitions
- Tags: Automatically computed from operation tags
§Tag Generation
Tags are automatically computed from all operations and include:
- Explicit tags set on operations
- Auto-generated tags based on path patterns
- Deduplicated and sorted alphabetically
§Performance Notes
- This method acquires read locks on internal collections
- Schema processing is cached to avoid redundant work
- Tags are computed on-demand from operation metadata
Sourcepub async fn register_schema<T>(&mut self)where
T: ToSchema + 'static,
pub async fn register_schema<T>(&mut self)where
T: ToSchema + 'static,
Manually registers a type in the schema collection.
This method allows you to explicitly add types to the OpenAPI schema collection that might not be automatically detected. This is useful for types that are referenced indirectly, such as nested types.
§Type Parameters
T
- The type to register, must implementToSchema
and'static
§Example
use clawspec_core::ApiClient;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
struct NestedErrorType {
message: String,
}
let mut client = ApiClient::builder().build()?;
// Register the nested type that might not be automatically detected
client.register_schema::<NestedErrorType>().await;
// Now when you generate the OpenAPI spec, NestedErrorType will be included
let openapi = client.collected_openapi().await;