Files
truelayer-interview/README.md

141 lines
4.3 KiB
Markdown

# Pokedex API
REST API for the TrueLayer Software Engineering Challenge. It returns basic Pokémon information from PokéAPI and, on request, a fun translated description from FunTranslations.
## Requirements
You can run it either with Docker or with a local Rust toolchain.
### Option A: Docker
Install Docker Desktop or an equivalent Docker runtime, then run:
```bash
docker build -t pokedex-api .
docker run --rm -p 5000:5000 pokedex-api
```
### Option B: local Rust
Install Rust with rustup:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup default stable
```
Then run the service:
```bash
cargo run
```
The API listens on `0.0.0.0:5000` by default.
## CLI helper
With the API running in another terminal, call it through the small helper binary:
```bash
cargo run --bin pokedex-cli -- health
cargo run --bin pokedex-cli -- pokemon mewtwo
cargo run --bin pokedex-cli -- translated mewtwo
cargo run --bin pokedex-cli -- --base-url http://localhost:5000 pokemon pikachu
```
## Endpoints
```bash
curl http://localhost:5000/health
curl http://localhost:5000/pokemon/mewtwo
curl http://localhost:5000/pokemon/translated/mewtwo
curl http://localhost:5000/metrics
```
Example response:
```json
{
"name": "mewtwo",
"description": "It was created by a scientist after years of horrific gene splicing and DNA engineering experiments.",
"habitat": "rare",
"isLegendary": true
}
```
## Translation rules
`/pokemon/translated/{name}` applies Yoda when the Pokémon is legendary or its habitat is `cave`; otherwise it applies Shakespeare. If translation fails or returns an empty result, the API falls back to the standard PokéAPI description.
## Architecture
```mermaid
flowchart LR
CLI[CLI helper] --> HTTP[HTTP layer]
HTTP --> APP[Application service]
APP --> DOMAIN[Domain]
APP --> CLIENTS[External clients]
HTTP --> OTEL[Telemetry]
APP --> OTEL
```
See `docs/architecture.md` for the design notes.
## Configuration
| Variable | Default | Description |
| --- | --- | --- |
| `BIND_ADDR` | `0.0.0.0:5000` | HTTP bind address |
| `POKEAPI_BASE_URL` | `https://pokeapi.co/api/v2` | PokéAPI base URL |
| `FUN_TRANSLATIONS_BASE_URL` | `https://api.funtranslations.mercxry.me/v1` | FunTranslations base URL |
| `REQUEST_TIMEOUT_SECONDS` | `5` | Upstream HTTP timeout |
| `RATE_LIMIT_PER_SECOND` | `20` | Global token refill rate |
| `RATE_LIMIT_BURST` | `40` | Global burst capacity |
| `OTEL_SERVICE_NAME` | `pokedex-api` | OpenTelemetry service name |
| `RUST_LOG` | `pokedex_api=info,tower_http=info` | JSON tracing log filter |
## Instrumentation
The service includes:
- structured JSON logging via `tracing`
- request spans via `tower-http`
- `x-request-id` generation and propagation
- OpenTelemetry metrics for HTTP requests, HTTP errors, upstream calls, upstream errors and latency histograms
- `/metrics` in Prometheus text format backed by OpenTelemetry instruments
- simple global token-bucket rate limiting returning `429`
## Development checks
```bash
cargo fmt --all --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
```
The crate denies unsafe code and enables strict Clippy groups: `all`, `pedantic`, `nursery` and `cargo`, with only dependency-version noise allowed.
## External contract checks
Normal tests mock third-party APIs. A scheduled GitHub Actions workflow runs ignored contract tests nightly against the real PokéAPI and FunTranslations APIs to catch provider contract drift early:
```bash
cargo test --locked --test external_contract -- --ignored --test-threads=1
```
## Docker multi-arch build
```bash
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg GIT_SHA="$(git rev-parse HEAD)" \
-t pokedex-api:local .
```
The Dockerfile uses `cargo-chef` and BuildKit cache mounts for dependency and target directory caching.
## Production notes
For a production API I would add per-client or per-token rate limiting instead of one global bucket, retries with bounded exponential backoff, circuit breakers for upstream failures, response caching for PokéAPI data, stronger health checks that distinguish readiness from liveness, dashboards and alerts from the OTEL metrics, and a dedicated OTEL collector pipeline. I would also keep `/metrics` private, add dependency/container scanning, define SLOs, and add contract tests against recorded upstream fixtures.