# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Test Commands

```bash
cargo build                                      # Build library
cargo test                                       # Run all offline tests (72 tests)
cargo test --test client                         # Run only wiremock integration tests
cargo test --test models                         # Run only serde deserialization tests
cargo test --test live -- --ignored              # Run live tests against real SEC API
cargo test --test field_coverage -- --ignored    # Run field coverage tests against real SEC API
cargo test -- --ignored                          # Run all live/ignored tests
cargo test <test_name>                           # Run a single test by name
cargo clippy -- -D warnings                      # Lint
cargo fmt --check                                # Check formatting
cargo doc --no-deps                              # Build documentation
```

## Architecture

Async HTTP client library for the SEC EDGAR API. All public types are re-exported from `lib.rs`.

**`src/client.rs`** — `ClientBuilder` (builder pattern, user-agent required) and `Client` with 7 async API methods. Rate limiting via `governor` crate (10 req/s default). Two internal helpers: `get_json<T>()` and `get_bytes()` handle rate-limit wait → request → status check. Three base URLs (`sec.gov`, `data.sec.gov`, `efts.sec.gov`) are overridable via `#[doc(hidden)]` builder methods for test mocking.

**`src/models.rs`** — All response structs with serde derives. The `nullable` helper function deserializes JSON `null` as the type's default value (e.g., `""` for String), matching Go's zero-value behavior. EFTS search types are `pub` but re-exported as `#[doc(hidden)]` — they exist for field coverage tests, not user code.

**`src/error.rs`** — `Error` enum with 5 variants: `Request`, `Api`, `Decode`, `DecodeBody`, `Config`. Helper methods `.is_rate_limited()` (HTTP 429) and `.is_not_found()` (HTTP 404).

**`src/cik.rs`** — `Cik(u64)` newtype. `to_padded_string()` zero-pads to 10 digits for URL construction. `#[serde(transparent)]` for plain integer serialization.

## Testing Patterns

**Wiremock tests** (`tests/client.rs`): Create `MockServer`, pass its URI to all three base URL overrides on `ClientBuilder`, set `rate_limit(100)` to avoid throttling. All three base URLs must be overridden for full mocking.

**Field coverage tests** (`tests/field_coverage.rs`): Roundtrip approach — deserialize raw API JSON into typed struct, serialize back, compare keys. Detects fields the SEC API returns that our structs silently drop.

**Live tests** (`tests/live.rs`): `#[ignore]` by default. Use Apple Inc. (CIK 320193) as test subject.

## Key Conventions

- Accession numbers have dashes stripped for URL paths (e.g., `"0000320193-24-000123"` → `"000032019324000123"`)
- `get_tickers()` re-keys the API response HashMap by CIK string instead of array index
- `search()` skips sending `from` param when value is 0
- All error variants include the endpoint URL for debugging
- `#[serde(rename = "core_type")]` on `FilingSet` overrides `rename_all = "camelCase"` because the API uses snake_case for that field
- EFTS source fields use `Vec<serde_json::Value>` because the SEC API returns mixed types in those arrays
