reinhardt-http 0.1.0-alpha.9

HTTP primitives, request and response handling for Reinhardt
Documentation
# reinhardt-http

HTTP request and response handling for the Reinhardt framework

## Overview

Core HTTP abstractions for the Reinhardt framework. Provides comprehensive request and response types, header handling, cookie management, content negotiation, and streaming support with a Django/DRF-inspired API design.

## Features

### Implemented ✓

#### Request Type

- **Complete HTTP request representation** with all standard components
  - HTTP method, URI, version, headers, body
  - Path parameters (`path_params`) and query string parsing (`query_params`)
  - HTTPS detection (`is_secure`)
  - Remote address tracking (`remote_addr`)
  - Type-safe extensions system (`Extensions`)
- **Builder pattern** for fluent request construction
  - `Request::builder()` - Start building
  - `.method()` - Set HTTP method
  - `.uri()` - Set URI (with automatic query parsing)
  - `.version()` - Set HTTP version (defaults to HTTP/1.1)
  - `.headers()` - Set headers
  - `.header()` - Set single header
  - `.body()` - Set request body
  - `.secure()` - Set HTTPS flag
  - `.remote_addr()` - Set remote address
  - `.build()` - Finalize construction
- **Request parsing** (with `parsers` feature)
  - JSON body parsing
  - Form data parsing
  - Multipart form data
  - Lazy parsing (parse on first access)

#### Response Type

- **Flexible HTTP response creation** with status code helpers
  - `Response::ok()` - 200 OK
  - `Response::created()` - 201 Created
  - `Response::no_content()` - 204 No Content
  - `Response::bad_request()` - 400 Bad Request
  - `Response::unauthorized()` - 401 Unauthorized
  - `Response::forbidden()` - 403 Forbidden
  - `Response::not_found()` - 404 Not Found
  - `Response::gone()` - 410 Gone
  - `Response::internal_server_error()` - 500 Internal Server Error
- **Redirect responses**
  - `Response::permanent_redirect(url)` - 301 Moved Permanently
  - `Response::temporary_redirect(url)` - 302 Found
  - `Response::temporary_redirect_preserve_method(url)` - 307 Temporary Redirect
- **Builder pattern methods**
  - `.with_body(data)` - Set response body (bytes or string)
  - `.with_header(name, value)` - Add single header
  - `.with_typed_header(header)` - Add typed header
  - `.with_json(data)` - Serialize data to JSON and set Content-Type
  - `.with_location(url)` - Set Location header (for redirects)
  - `.with_stop_chain(bool)` - Control middleware chain execution
- **JSON serialization support** with automatic Content-Type
- **Middleware chain control** via `stop_chain` flag

#### StreamingResponse

- **Streaming response support** for large data or real-time content
  - Custom media type configuration
  - Header support
  - Stream-based body (any type implementing `Stream`)

#### Extensions System

- **Type-safe request extensions** for storing arbitrary typed data
  - `request.extensions.insert::<T>(value)` - Store typed data
  - `request.extensions.get::<T>()` - Retrieve typed data
  - Thread-safe with `Arc<Mutex<TypeMap>>`
  - Common use cases: authentication context, request ID, user data

#### Error Integration

- Re-exports `reinhardt_exception::Error` and `Result` for consistent error handling

## Installation

Add `reinhardt` to your `Cargo.toml`:

```toml
[dependencies]
reinhardt = "0.1.0-alpha.1"

# Or use a preset with parsers support:
# reinhardt = { version = "0.1.0-alpha.1", features = ["standard"] }  # Recommended
# reinhardt = { version = "0.1.0-alpha.1", features = ["full"] }      # All features
```

**Note:** HTTP types are available through the main `reinhardt` crate, which provides a unified interface to all framework components.

## Usage Examples

### Basic Request Construction

```rust
use reinhardt::http::Request;
use hyper::Method;
use bytes::Bytes;

// Using builder pattern
let request = Request::builder()
	.method(Method::POST)
	.uri("/api/users?page=1")
	.body(Bytes::from(r#"{"name": "Alice"}"#))
	.build()
	.unwrap();

assert_eq!(request.method, Method::POST);
assert_eq!(request.path(), "/api/users");
assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
```

### Path and Query Parameters

```rust
use reinhardt::http::Request;
use hyper::Method;

let mut request = Request::builder()
	.method(Method::GET)
	.uri("/api/users/123?sort=name&order=asc")
	.build()
	.unwrap();

// Access query parameters
assert_eq!(request.query_params.get("sort"), Some(&"sort".to_string()));
assert_eq!(request.query_params.get("order"), Some(&"asc".to_string()));

// Add path parameters (typically done by router)
request.path_params.insert("id".to_string(), "123".to_string());
assert_eq!(request.path_params.get("id"), Some(&"123".to_string()));
```

### Request Extensions

```rust
use reinhardt::http::Request;
use hyper::Method;

#[derive(Clone)]
struct UserId(i64);

let mut request = Request::builder()
	.method(Method::GET)
	.uri("/api/profile")
	.build()
	.unwrap();

// Store typed data in extensions
request.extensions.insert(UserId(42));

// Retrieve typed data
let user_id = request.extensions.get::<UserId>().unwrap();
assert_eq!(user_id.0, 42);
```

### Response Helpers

```rust
use reinhardt::http::Response;

// Success responses
let response = Response::ok()
    .with_body("Success");
assert_eq!(response.status, hyper::StatusCode::OK);

let response = Response::created()
    .with_json(&serde_json::json!({
        "id": 123,
        "name": "Alice"
    }))
    .unwrap();
assert_eq!(response.status, hyper::StatusCode::CREATED);
assert_eq!(
    response.headers.get("content-type").unwrap(),
    "application/json"
);

// Error responses
let response = Response::bad_request()
    .with_body("Invalid input");
assert_eq!(response.status, hyper::StatusCode::BAD_REQUEST);

let response = Response::not_found()
    .with_body("Resource not found");
assert_eq!(response.status, hyper::StatusCode::NOT_FOUND);
```

### Redirect Responses

```rust
use reinhardt::http::Response;

// Permanent redirect (301)
let response = Response::permanent_redirect("/new-location");
assert_eq!(response.status, hyper::StatusCode::MOVED_PERMANENTLY);
assert_eq!(
	response.headers.get("location").unwrap().to_str().unwrap(),
	"/new-location"
);

// Temporary redirect (302)
let response = Response::temporary_redirect("/login");
assert_eq!(response.status, hyper::StatusCode::FOUND);

// Temporary redirect preserving method (307)
let response = Response::temporary_redirect_preserve_method("/users/123");
assert_eq!(response.status, hyper::StatusCode::TEMPORARY_REDIRECT);
```

### JSON Response

```rust
use reinhardt::http::Response;
use serde::{Serialize, Deserialize};

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

let user = User {
    id: 1,
    name: "Alice".to_string(),
};

let response = Response::ok()
    .with_json(&user)
    .unwrap();

// Automatically sets Content-Type: application/json
assert_eq!(
    response.headers.get("content-type").unwrap(),
    "application/json"
);
```

### Middleware Chain Control

```rust
use reinhardt::http::Response;

// Stop middleware chain (useful for authentication, rate limiting)
let response = Response::unauthorized()
    .with_body("Authentication required")
    .with_stop_chain(true);

// This response will stop further middleware execution
assert!(response.should_stop_chain());
```

### Streaming Response

```rust
use reinhardt::http::StreamingResponse;
use futures::stream::{self, StreamExt};
use bytes::Bytes;
use hyper::StatusCode;

let data = vec![
	Bytes::from("chunk1"),
	Bytes::from("chunk2"),
	Bytes::from("chunk3"),
];

let stream = stream::iter(data.into_iter().map(Ok));

// Create streaming response (default status: 200 OK)
let response = StreamingResponse::new(Box::pin(stream))
	.status(StatusCode::OK)
	.media_type("text/plain");

// Or use with_status for custom status code
let response = StreamingResponse::with_status(
	Box::pin(stream),
	StatusCode::OK,
)
.media_type("text/plain");

// Use for large files, server-sent events, etc.
```

## API Reference

### Request

**Fields:**
- `method: Method` - HTTP method (GET, POST, etc.)
- `uri: Uri` - Request URI
- `version: Version` - HTTP version
- `headers: HeaderMap` - HTTP headers
- `path_params: HashMap<String, String>` - Path parameters from URL routing
- `query_params: HashMap<String, String>` - Query string parameters
- `is_secure: bool` - Whether request is over HTTPS
- `remote_addr: Option<SocketAddr>` - Client's remote address
- `extensions: Extensions` - Type-safe extension storage

**Methods:**
- `Request::builder()` - Create builder
- `.path()` - Get URI path without query
- `.body()` - Get request body as `Option<&Bytes>`
- `.json::<T>()` - Parse body as JSON (requires `parsers` feature)
- `.post()` - Parse POST data (form/JSON, requires `parsers` feature)
- `.data()` - Get parsed data from body
- `.set_di_context::<T>()` - Set DI context for type T
- `.get_di_context::<T>()` - Get DI context for type T
- `.decoded_query_params()` - Get URL-decoded query parameters
- `.get_accepted_languages()` - Parse Accept-Language header
- `.get_preferred_language()` - Get user's preferred language
- `.is_secure()` - Check if request is over HTTPS
- `.scheme()` - Get URI scheme
- `.build_absolute_uri()` - Build absolute URI from request

### Response

**Fields:**
- `status: StatusCode` - HTTP status code
- `headers: HeaderMap` - HTTP headers
- `body: Bytes` - Response body

**Constructor Methods:**
- `Response::new(status)` - Create with status code
- `Response::ok()` - 200 OK
- `Response::created()` - 201 Created
- `Response::no_content()` - 204 No Content
- `Response::bad_request()` - 400 Bad Request
- `Response::unauthorized()` - 401 Unauthorized
- `Response::forbidden()` - 403 Forbidden
- `Response::not_found()` - 404 Not Found
- `Response::gone()` - 410 Gone
- `Response::internal_server_error()` - 500 Internal Server Error
- `Response::permanent_redirect(url)` - 301 Moved Permanently
- `Response::temporary_redirect(url)` - 302 Found
- `Response::temporary_redirect_preserve_method(url)` - 307 Temporary Redirect

**Builder Methods:**
- `.with_body(data)` - Set body (bytes or string)
- `.with_header(name, value)` - Add header
- `.with_typed_header(header)` - Add typed header
- `.with_json(data)` - Serialize to JSON
- `.with_location(url)` - Set Location header
- `.with_stop_chain(bool)` - Control middleware chain
- `.should_stop_chain()` - Check if chain should stop

### Extensions

**Methods:**
- `.insert::<T>(value)` - Store typed value
- `.get::<T>()` - Retrieve typed value (returns `Option<T>`)
- `.remove::<T>()` - Remove typed value

## Feature Flags

- `parsers` - Enable request body parsing (JSON, form data, multipart)
  - Adds `parse_json()`, `parse_form()` methods to Request
  - Requires `reinhardt-parsers` crate

## Dependencies

- `hyper` - HTTP types (Method, Uri, StatusCode, HeaderMap, Version)
- `bytes` - Efficient byte buffer handling
- `futures` - Stream support for streaming responses
- `serde` - Serialization support (with `serde_json` for JSON)
- `reinhardt-exception` - Error handling
- `reinhardt-parsers` - Request body parsing (optional, with `parsers` feature)

## Testing

The crate includes comprehensive unit tests and doctests covering:
- Request construction and builder pattern
- Response helpers and status codes
- Redirect responses
- JSON serialization
- Extensions system
- Query parameter parsing
- Middleware chain control

Run tests with:
```bash
cargo test
cargo test --features parsers  # With parsers support
```

## License

Licensed under the BSD 3-Clause License.