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:

docker build -t pokedex-api .
docker run --rm -p 5000:5000 pokedex-api

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:

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:

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

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:

{
  "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.

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

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:

cargo test --locked --test external_contract -- --ignored --test-threads=1

Docker multi-arch build

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.

Description
Truelayer interview
Readme 258 KiB
Languages
Rust 92.7%
Dockerfile 4.7%
Makefile 2.6%