4.1 KiB
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. Common commands are available through make; run make help for the full list.
Option A: Docker
Install Docker Desktop or an equivalent Docker runtime, then run:
make docker-build
make docker-run
Option B: local Rust
Install Rust with rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup default stable
Then run the service:
make 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:
make cli-health
make cli-pokemon
make cli-translated
make cli-pokemon POKEMON=pikachu BASE_URL=http://localhost:5000
Endpoints
make health
make pokemon
make translated
make metrics
Example response:
{
"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
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-idgeneration and propagation- OpenTelemetry metrics for HTTP requests, HTTP errors, upstream calls, upstream errors and latency histograms
/metricsin Prometheus text format backed by OpenTelemetry instruments- simple global token-bucket rate limiting returning
429
Development checks
make check
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:
make contract-test
Docker multi-arch build
make docker-buildx
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, and a bounded LRU/TTL cache for PokéAPI data. Species content changes rarely, so caching successful responses would avoid unnecessary random upstream hits, reduce latency, and make transient provider failures less visible to users. I would also add stronger health checks that distinguish readiness from liveness, dashboards and alerts from the OTEL metrics, and a dedicated OTEL collector pipeline. I would keep /metrics private, add dependency/container scanning, define SLOs, and add contract tests against recorded upstream fixtures.