Expand description
§Clawspec Core
Generate OpenAPI specifications from your HTTP client test code.
This crate provides two main ways to generate OpenAPI documentation:
ApiClient
- Direct HTTP client for fine-grained controlTestClient
- Test server integration with automatic lifecycle management
§Quick Start
§Using ApiClient directly
use clawspec_core::ApiClient;
let mut client = ApiClient::builder()
.with_host("api.example.com")
.build()?;
// Make requests - schemas are captured automatically
let user: User = client
.get("/users/123")?
.await? // ← Direct await using IntoFuture
.as_json() // ← Important: Must consume result for OpenAPI generation!
.await?;
// Generate OpenAPI specification
let spec = client.collected_openapi().await;
§Using TestClient with a test server
For a complete working example, see the axum example.
use clawspec_core::test_client::{TestClient, TestServer};
use std::net::TcpListener;
#[tokio::test]
async fn test_api() -> Result<(), Box<dyn std::error::Error>> {
let mut client = TestClient::start(MyServer).await?;
// Test your API
let response = client.get("/users")?.await?.as_json::<serde_json::Value>().await?;
// Write OpenAPI spec
client.write_openapi("api.yml").await?;
Ok(())
}
§Working with Parameters
use clawspec_core::{ApiClient, CallPath, CallQuery, CallHeaders, CallCookies, ParamValue, ParamStyle};
// Path parameters
let path = CallPath::from("/users/{id}")
.add_param("id", ParamValue::new(123));
// Query parameters
let query = CallQuery::new()
.add_param("page", ParamValue::new(1))
.add_param("limit", ParamValue::new(10));
// Headers
let headers = CallHeaders::new()
.add_header("Authorization", "Bearer token");
// Cookies
let cookies = CallCookies::new()
.add_cookie("session_id", "abc123")
.add_cookie("user_id", 456);
// Direct await with parameters:
let response = client
.get(path)?
.with_query(query)
.with_headers(headers)
.with_cookies(cookies)
.await?; // Direct await using IntoFuture
§OpenAPI 3.1.0 Parameter Styles
This library supports all OpenAPI 3.1.0 parameter styles for different parameter types:
§Path Parameters
use clawspec_core::{CallPath, ParamValue, ParamStyle};
// Simple style (default): /users/123
let path = CallPath::from("/users/{id}")
.add_param("id", ParamValue::new(123));
// Label style: /users/.123
let path = CallPath::from("/users/{id}")
.add_param("id", ParamValue::with_style(123, ParamStyle::Label));
// Matrix style: /users/;id=123
let path = CallPath::from("/users/{id}")
.add_param("id", ParamValue::with_style(123, ParamStyle::Matrix));
// Arrays with different styles
let tags = vec!["rust", "web", "api"];
// Simple: /search/rust,web,api
let path = CallPath::from("/search/{tags}")
.add_param("tags", ParamValue::with_style(tags.clone(), ParamStyle::Simple));
// Label: /search/.rust,web,api
let path = CallPath::from("/search/{tags}")
.add_param("tags", ParamValue::with_style(tags.clone(), ParamStyle::Label));
// Matrix: /search/;tags=rust,web,api
let path = CallPath::from("/search/{tags}")
.add_param("tags", ParamValue::with_style(tags, ParamStyle::Matrix));
§Query Parameters
use clawspec_core::{CallQuery, ParamValue, ParamStyle};
let tags = vec!["rust", "web", "api"];
// Form style (default): ?tags=rust&tags=web&tags=api
let query = CallQuery::new()
.add_param("tags", ParamValue::new(tags.clone()));
// Space delimited: ?tags=rust%20web%20api
let query = CallQuery::new()
.add_param("tags", ParamValue::with_style(tags.clone(), ParamStyle::SpaceDelimited));
// Pipe delimited: ?tags=rust|web|api
let query = CallQuery::new()
.add_param("tags", ParamValue::with_style(tags, ParamStyle::PipeDelimited));
// Deep object style: ?user[name]=john&user[age]=30
let user_data = serde_json::json!({"name": "john", "age": 30});
let query = CallQuery::new()
.add_param("user", ParamValue::with_style(user_data, ParamStyle::DeepObject));
§Cookie Parameters
use clawspec_core::{CallCookies, ParamValue};
// Simple cookie values
let cookies = CallCookies::new()
.add_cookie("session_id", "abc123")
.add_cookie("user_id", 456)
.add_cookie("is_admin", true);
// Array values in cookies (comma-separated)
let cookies = CallCookies::new()
.add_cookie("preferences", vec!["dark_mode", "notifications"])
.add_cookie("selected_tags", vec!["rust", "web", "api"]);
// Custom types with automatic serialization
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
struct UserId(u64);
let cookies = CallCookies::new()
.add_cookie("user", UserId(12345));
§Authentication
The library supports various authentication methods that can be configured at the client level or overridden for individual requests.
§Client-Level Authentication
use clawspec_core::{ApiClient, Authentication};
// Bearer token authentication
let client = ApiClient::builder()
.with_host("api.example.com")
.with_authentication(Authentication::Bearer("my-api-token".into()))
.build()?;
// Basic authentication
let client = ApiClient::builder()
.with_host("api.example.com")
.with_authentication(Authentication::Basic {
username: "user".to_string(),
password: "pass".into(),
})
.build()?;
// API key authentication
let client = ApiClient::builder()
.with_host("api.example.com")
.with_authentication(Authentication::ApiKey {
header_name: "X-API-Key".to_string(),
key: "secret-key".into(),
})
.build()?;
§Per-Request Authentication Override
use clawspec_core::{ApiClient, Authentication};
// Client with default authentication
let mut client = ApiClient::builder()
.with_host("api.example.com")
.with_authentication(Authentication::Bearer("default-token".into()))
.build()?;
// Use different authentication for admin endpoints
let admin_users = client
.get("/admin/users")?
.with_authentication(Authentication::Bearer("admin-token".into()))
.await?
.as_json::<serde_json::Value>()
.await?;
// Remove authentication for public endpoints
let public_data = client
.get("/public/health")?
.with_authentication_none()
.await?
.as_text()
.await?;
§Authentication Types
- Bearer: Adds
Authorization: Bearer <token>
header - Basic: Adds
Authorization: Basic <base64(username:password)>
header - ApiKey: Adds custom header with API key
§Security Best Practices
- Store credentials securely using environment variables or secret management tools
- Rotate tokens regularly
- Use HTTPS for all authenticated requests
- Avoid logging authentication headers
§Status Code Validation
By default, requests expect status codes in the range 200-499 (inclusive of 200, exclusive of 500). You can customize this behavior:
use clawspec_core::{ApiClient, expected_status_codes};
// Single codes
client.post("/users")?
.with_expected_status_codes(expected_status_codes!(201, 202))
.await?;
// Ranges
client.get("/health")?
.with_expected_status_codes(expected_status_codes!(200-299))
.await?;
§Response Descriptions
Add descriptive text to your OpenAPI responses for better documentation:
use clawspec_core::ApiClient;
// Set a description for the actual returned status code
client.get("/users/{id}")?
.with_response_description("User details if found, or error information")
.await?;
// The description applies to whatever status code is actually returned
client.post("/users")?
.with_response_description("User created successfully or validation error")
.await?;
§Schema Registration
§Automatic Schema Capture
JSON request and response body schemas are automatically captured when using .json()
and .as_json()
methods:
use clawspec_core::ApiClient;
#[derive(Serialize, Deserialize, ToSchema)]
struct CreateUser { name: String, email: String }
#[derive(Deserialize, ToSchema)]
struct User { id: u32, name: String, email: String }
// Schemas are captured automatically - no explicit registration needed
let user: User = client
.post("/users")?
.json(&CreateUser { name: "Alice".to_string(), email: "alice@example.com".to_string() })?
.await?
.as_json()
.await?;
§Manual Schema Registration
For nested schemas or when you need to ensure all dependencies are included, use the register_schemas!
macro:
use clawspec_core::{ApiClient, register_schemas};
#[derive(Serialize, Deserialize, ToSchema)]
struct Address { street: String, city: String }
#[derive(Serialize, Deserialize, ToSchema)]
struct CreateUser { name: String, email: String, address: Address }
#[derive(Deserialize, ToSchema)]
struct ErrorResponse { code: String, message: String }
// Register nested schemas and error types for complete documentation
register_schemas!(client, CreateUser, Address, ErrorResponse).await;
§⚠️ Nested Schema Limitation
Current Limitation: While main JSON body schemas are captured automatically, nested schemas may not be fully resolved. If you encounter missing nested schemas in your OpenAPI specification, use the register_schemas!
macro to explicitly register them:
use clawspec_core::{ApiClient, register_schemas};
#[derive(Serialize, Deserialize, ToSchema)]
struct Position { lat: f64, lng: f64 }
#[derive(Serialize, Deserialize, ToSchema)]
struct Location { name: String, position: Position } // Position is nested
// Register both main and nested schemas to ensure complete OpenAPI generation
register_schemas!(client, Location, Position).await;
Workaround: Always register nested schemas explicitly when you need complete OpenAPI documentation with all referenced types properly defined.
§Error Handling
The library provides two main error types:
ApiClientError
- HTTP client errors (network, parsing, validation)TestAppError
- Test server lifecycle errors
§See Also
ApiClient
- HTTP client with OpenAPI collectionApiCall
- Request builder with parameter supporttest_client
- Test server integration moduleExpectedStatusCodes
- Status code validation
§Re-exports
All commonly used types are re-exported from the crate root for convenience.
Modules§
- test_
client - Generic test client framework for async server testing.
Macros§
- expected_
status_ codes - Creates an
ExpectedStatusCodes
instance with the specified status codes and ranges. - register_
schemas - Registers multiple schema types with the ApiClient for OpenAPI documentation.
Structs§
- ApiCall
- Builder for configuring HTTP API calls with comprehensive parameter and validation support.
- ApiClient
- A type-safe HTTP client for API testing and OpenAPI documentation generation.
- ApiClient
Builder - Builder for creating
ApiClient
instances with comprehensive configuration options. - Call
Body - Represents the body of an HTTP request with its content type and schema information.
- Call
Cookies - Represents HTTP cookies for an API call.
- Call
Headers - Represents HTTP headers for an API call.
- Call
Path - A parameterized HTTP path with type-safe parameter substitution.
- Call
Query - A collection of query parameters for HTTP requests with OpenAPI 3.1 support.
- Call
Result - Represents the result of an API call with response processing capabilities.
- Expected
Status Codes - Expected status codes for HTTP requests.
- Param
Value - A parameter value with its serialization style
- RawResult
- Represents the raw response data from an HTTP request.
- Secure
String - Secure wrapper for sensitive string data that automatically zeroes memory on drop.
Enums§
- ApiClient
Error - Errors that can occur when using the ApiClient.
- Authentication
- Authentication configuration for API requests.
- Authentication
Error - Errors that can occur during authentication processing.
- Param
Style - Parameter styles supported by OpenAPI 3.1 specification.
- RawBody
- Represents the body content of a raw HTTP response.
Traits§
- Parameter
Value - A trait alias for types that can be used as parameter values.