Crate clawspec_core

Source
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 control
  • TestClient - 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));
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:

§See Also

§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.
ApiClientBuilder
Builder for creating ApiClient instances with comprehensive configuration options.
CallBody
Represents the body of an HTTP request with its content type and schema information.
CallCookies
Represents HTTP cookies for an API call.
CallHeaders
Represents HTTP headers for an API call.
CallPath
A parameterized HTTP path with type-safe parameter substitution.
CallQuery
A collection of query parameters for HTTP requests with OpenAPI 3.1 support.
CallResult
Represents the result of an API call with response processing capabilities.
ExpectedStatusCodes
Expected status codes for HTTP requests.
ParamValue
A parameter value with its serialization style
RawResult
Represents the raw response data from an HTTP request.
SecureString
Secure wrapper for sensitive string data that automatically zeroes memory on drop.

Enums§

ApiClientError
Errors that can occur when using the ApiClient.
Authentication
Authentication configuration for API requests.
AuthenticationError
Errors that can occur during authentication processing.
ParamStyle
Parameter styles supported by OpenAPI 3.1 specification.
RawBody
Represents the body content of a raw HTTP response.

Traits§

ParameterValue
A trait alias for types that can be used as parameter values.