reinhardt-http 0.4.0-alpha.2

HTTP primitives, request and response handling for Reinhardt
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# 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 lazy 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_static_body(data)` - Set a static byte body without copying
  - `.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 lazily initialized `Arc<Mutex<TypeMap>>`
  - Common use cases: authentication context, request ID, user data

#### Error Integration

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

#### Handler Traits

- `Handler` - Async request handler trait for routes that await I/O
- `SyncHandler` - Synchronous fast path for routes that only inspect the request and build a response
- `SyncHandlerAdapter` - Compatibility adapter used when synchronous handlers pass through async middleware APIs

## Installation

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

<!-- reinhardt-version-sync:3 -->
```toml
[dependencies]
reinhardt = "0.4.0-alpha.2"

# Or use a preset with parsers support:
# reinhardt = { version = "0.4.0-alpha.2", features = ["standard"] }  # Recommended
# reinhardt = { version = "0.4.0-alpha.2", 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"));
```

### 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("name"));
assert_eq!(request.query_params.get("order"), Some("asc"));

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

### 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);
```

### Synchronous Handlers

```rust
use reinhardt::http::{Request, Response, Result, SyncHandler};

struct HealthHandler;

impl SyncHandler for HealthHandler {
    fn handle_sync(&self, _request: Request) -> Result<Response> {
        Ok(Response::ok().with_static_body(b"ok"))
    }
}
```

### 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: PathParams` - Path parameters from URL routing
- `query_params: QueryParams` - Lazily parsed 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>`
- `.read_body()` - Read the body with consumption tracking
- `.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-core` crate (parsers module)

## 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-core` - Core types, error handling, and optional request body parsing (parsers module enabled via the `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.