switchy_http 0.4.0

Switchy HTTP Networking package
Documentation
# Switchy HTTP

A generic HTTP client abstraction library providing unified interfaces for HTTP operations with pluggable backend implementations.

## Features

- **Generic Client Interface**: Unified traits for HTTP clients, request builders, and responses
- **Multiple Backend Support**: Abstraction over different HTTP implementations (reqwest, simulator)
- **Request Building**: Fluent API for building HTTP requests with headers, query parameters, and body
- **Response Handling**: Unified response interface for status, headers, text, bytes, and streaming
- **JSON Support**: Built-in JSON serialization/deserialization support
- **Streaming Support**: Byte stream responses for large data handling
- **Method Support**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS methods with dedicated functions; CONNECT and TRACE via `request()` method
- **Error Handling**: Unified error types across different backends

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
# Default enables: reqwest + simulator + json + stream
switchy_http = "0.1.4"

# Real network backend
# switchy_http = { version = "0.1.4", default-features = false, features = ["reqwest", "json", "stream"] }

# Simulated backend (no network requests)
# switchy_http = { version = "0.1.4", default-features = false, features = ["simulator", "json", "stream"] }
```

When both `reqwest` and `simulator` are enabled, `switchy_http::Client` uses the simulator backend. Enable only one backend if you need deterministic backend selection.

## Usage

### Basic HTTP Requests

```rust
use switchy_http::{Client, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = Client::new();

    // GET request
    let mut response = client
        .get("https://api.example.com/users")
        .header("Authorization", "Bearer token123")
        .query_param("page", "1")
        .query_param("limit", "10")
        .send()
        .await?;

    println!("Status: {:?}", response.status());
    let text = response.text().await?;
    println!("Response: {}", text);

    Ok(())
}
```

### POST with JSON Body

```rust
use switchy_http::Client;
use serde_json::json;

async fn create_user() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let user_data = json!({
        "name": "John Doe",
        "email": "john@example.com"
    });

    let response = client
        .post("https://api.example.com/users")
        .header("Content-Type", "application/json")
        .json(&user_data)
        .send()
        .await?;

    if response.status().is_success() {
        println!("User created successfully");
    }

    Ok(())
}
```

### Handling Different Response Types

```rust
use switchy_http::Client;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

async fn fetch_user_data() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    // JSON response
    let user: User = client
        .get("https://api.example.com/users/1")
        .send()
        .await?
        .json()
        .await?;

    println!("User: {} ({})", user.name, user.email);

    // Raw bytes
    let image_data = client
        .get("https://api.example.com/users/1/avatar")
        .send()
        .await?
        .bytes()
        .await?;

    println!("Downloaded {} bytes", image_data.len());

    // Text response
    let readme = client
        .get("https://raw.githubusercontent.com/example/repo/README.md")
        .send()
        .await?
        .text()
        .await?;

    println!("README content: {}", readme);

    Ok(())
}
```

### Streaming Large Responses

```rust
use switchy_http::Client;
use futures::StreamExt;

async fn download_large_file() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let response = client
        .get("https://example.com/large-file.zip")
        .send()
        .await?;

    let mut stream = response.bytes_stream();
    let mut total_bytes = 0;

    while let Some(chunk) = stream.next().await {
        let chunk = chunk?;
        total_bytes += chunk.len();
        println!("Downloaded {} bytes so far", total_bytes);

        // Process chunk (e.g., write to file)
    }

    println!("Download complete: {} total bytes", total_bytes);
    Ok(())
}
```

### Custom Headers and Query Parameters

```rust
use switchy_http::{Client, Header};

async fn api_request_with_auth() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let response = client
        .get("https://api.example.com/protected")
        .header(Header::Authorization.as_ref(), "Bearer secret-token")
        .header(Header::UserAgent.as_ref(), "MoosicBox/1.0")
        .query_param("format", "json")
        .query_param_opt("filter", Some("active"))
        .query_params(&[("sort", "name"), ("order", "asc")])
        .send()
        .await?;

    println!("Response status: {:?}", response.status());
    Ok(())
}
```

### Error Handling

```rust
use switchy_http::{Client, Error};

async fn handle_errors() {
    let client = Client::new();

    match client.get("https://invalid-url").send().await {
        Ok(response) => {
            println!("Request succeeded: {:?}", response.status());
        }
        Err(Error::Reqwest(e)) => {
            println!("Network error: {}", e);
        }
        Err(Error::Deserialize(e)) => {
            println!("JSON parsing error: {}", e);
        }
        Err(Error::Decode) => {
            println!("Response decoding error");
        }
    }
}
```

## Architecture

### Core Traits

- **`GenericClient<RB>`**: Main HTTP client interface with method shortcuts
- **`GenericRequestBuilder<R>`**: Request builder interface for headers, params, and body
- **`GenericResponse`**: Response interface for status, headers, and body access
- **`GenericClientBuilder<RB, C>`**: Client builder interface

### Wrapper Types

- **`ClientWrapper`**: Wraps backend-specific clients with unified interface
- **`RequestBuilderWrapper`**: Wraps backend-specific request builders
- **`ResponseWrapper`**: Wraps backend-specific responses

### Header Enumeration

Common HTTP headers are available as enum variants:

- `Authorization`
- `UserAgent`
- `Range`
- `ContentLength`

## Backend Features

### `reqwest` Feature

Enables integration with the popular `reqwest` HTTP client library.

### `simulator` Feature

Enables a simulated HTTP backend for testing and development.

### `json` Feature

Adds JSON serialization/deserialization support using `serde_json`.

### `stream` Feature

Enables streaming response support for handling large responses.

### Compression Features

Enable automatic decompression of HTTP responses (requires `reqwest` feature):

- `brotli` - Brotli decompression
- `deflate` - Deflate decompression
- `gzip` - Gzip decompression
- `zstd` - Zstandard decompression

## Error Types

- `Error::Decode` - Response decoding failures
- `Error::Deserialize` - JSON deserialization errors (with `json` feature)
- `Error::Reqwest` - Reqwest-specific errors (with `reqwest` feature)

## Thread Safety

All client types implement `Send + Sync` for safe usage across async tasks and threads.