
The `http` module provides a typed HTTP client with `Result`-based error handling, a fluent request builder, and a mock transport for deterministic testing.

```vary-snippet
import http

let resp = http.get("https://api.example.com/users").unwrap()
print(resp.status())    # 200
print(resp.body())      # raw response body
```

## Basic requests

| **Function** | **Returns** | **Description** |
|----------|---------|-------------|
| `http.get(url)` | `Result[HttpResponse, HttpError]` | Send a GET request |
| `http.post(url, body)` | `Result[HttpResponse, HttpError]` | Send a POST request |
| `http.put(url, body)` | `Result[HttpResponse, HttpError]` | Send a PUT request |
| `http.del(url)` | `Result[HttpResponse, HttpError]` | Send a DELETE request |
| `http.patch(url, body)` | `Result[HttpResponse, HttpError]` | Send a PATCH request |
| `http.request(method, url)` | `Result[HttpResponse, HttpError]` | Send with any method |

All functions accept optional keyword arguments: `headers` (Dict), `params` (Dict), `timeout_ms` (Int, default 10000). `post`, `put`, and `patch` also accept `body` (Str).

## HttpResponse

| **Method** | **Returns** | **Description** |
|----------|---------|-------------|
| `.status()` | `Int` | HTTP status code |
| `.body()` | `Str` | Response body as text |
| `.headers()` | `Dict[Str, Str]` | Response headers |
| `.header(name)` | `Str?` | Single header value by name |
| `.ok()` | `Bool` | True if status code is 2xx |
| `.url()` | `Str` | The request URL |

## JSON-first API

For JSON APIs, skip the intermediate `HttpResponse` and get a `Result[Json, HttpError]` directly:

```vary-snippet
import http

let data = http.get_json("https://api.example.com/items").unwrap()
let name = data.get_str("name")

let body = Json.empty_object().set_str("name", "alice")
let created = http.post_json("https://api.example.com/users", body).unwrap()
```

| **Function** | **Returns** | **Description** |
|----------|---------|-------------|
| `http.get_json(url)` | `Result[Json, HttpError]` | GET and parse as JSON |
| `http.post_json(url, payload)` | `Result[Json, HttpError]` | POST JSON payload and parse response |

## Request builder

Use `http.new(method, url)` to construct requests with multiple headers or query parameters:

```vary-snippet
import http

let resp = http.new("GET", "https://api.example.com/search")
    .header("Authorization", "Bearer " + token)
    .query("q", "vary language")
    .query("limit", "10")
    .timeout(5000)
    .send()
    .unwrap()
```

| **Method** | **Returns** | **Description** |
|----------|---------|-------------|
| `.header(name, value)` | `HttpRequestBuilder` | Add a request header |
| `.body(text)` | `HttpRequestBuilder` | Set the request body |
| `.query(name, value)` | `HttpRequestBuilder` | Add a query parameter |
| `.json(value)` | `HttpRequestBuilder` | Set JSON request body |
| `.timeout(ms)` | `HttpRequestBuilder` | Set timeout in milliseconds |
| `.send()` | `Result[HttpResponse, HttpError]` | Execute the request |

## Mock transport for testing

Replace the global transport with a mock to test HTTP-dependent code without real network calls:

```vary-snippet
import http

let m = http.mock()
m.addRoute("GET https://api.example.com/ping", 200, "pong", {})
m.addRoute("POST https://api.example.com/users", 201, '{"id": 42}', {})

http.set_transport(m)

let resp = http.get("https://api.example.com/ping").unwrap()
print(resp.body())    # "pong"

http.clear_transport()    # restore real HTTP
```

| **Function** | **Returns** | **Description** |
|----------|---------|-------------|
| `http.mock()` | `HttpMockTransport` | Create a mock transport |
| `http.set_transport(mock)` | `None` | Install mock as global transport |
| `http.clear_transport()` | `None` | Restore real HTTP transport |
| `mock.addRoute(key, status, body, headers)` | `None` | Register a mock response (key is `"METHOD url"`) |
| `mock.call_count()` | `Int` | Number of requests made through the mock |
| `mock.last_call()` | `Str?` | Key of the most recent mock call |
| `mock.all_calls()` | `List[Str]` | All mock call keys in order |
| `mock.reset()` | `None` | Clear all recorded calls |

## HttpError

| **Method** | **Returns** | **Description** |
|----------|---------|-------------|
| `.kind()` | `Str` | Error category (see below) |
| `.message()` | `Str` | Human-readable error detail |

Error kinds:

| **Kind** | **When** |
|----------|---------|
| `"connection"` | Could not connect to the server |
| `"timeout"` | Request exceeded the timeout |
| `"protocol"` | Invalid HTTP response |
| `"decode"` | Response body could not be decoded |
