Files
truelayer-interview/README.md
Alberto Moretti 17dc59699e
All checks were successful
Docker / build (push) Successful in 22s
Rust / check (push) Successful in 32s
Rust / test (push) Successful in 33s
docs: improve docs
2026-06-16 14:11:25 +02:00

6.2 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.

File challenge

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

This is a small Rust service, so the structure is intentionally a lightweight hexagonal/onion layout rather than a framework-heavy one.

flowchart LR
  CLI[CLI helper] --> HTTP
  HTTP[HTTP layer: axum routes, handlers, middleware] --> Application
  Application[Application service: use cases and translation rules] --> Domain
  Application --> Clients
  Clients[External clients: PokéAPI and FunTranslations]
  HTTP --> Telemetry
  Application --> Telemetry

Layers

  • domain: core data and business concepts (PokemonInfo, TranslationKind).
  • application: orchestration and business rules, including Yoda vs Shakespeare selection and fallback behavior.
  • clients: adapters for external providers (PokéAPI, FunTranslations).
  • http: API transport concerns, routing, handlers, request IDs, metrics middleware, and rate limiting.
  • telemetry: OpenTelemetry metrics and tracing setup.
  • bin: executable entrypoints for the API and the local CLI helper.

Deliberate trade-offs

The application service currently depends on concrete clients instead of trait-based ports. For this challenge that keeps the code easier to read and avoids abstractions that do not buy much yet. In a larger enterprise codebase, those clients would likely become traits owned by the application layer, with HTTP clients as adapters, so providers could be swapped or mocked without depending on concrete implementations.

Rate limiting is intentionally simple and in-memory. In production, this should usually live in an API gateway or shared rate-limiting service so limits are consistent across instances and can be keyed by API key, user, or client IP. PokéAPI species data also changes rarely, so production deployments would add a bounded LRU cache with TTL for successful species responses to avoid unnecessary random upstream hits, reduce latency, and make transient provider failures less visible to users.

External contract tests are separated from the normal deterministic test suite. Unit and integration tests use mocks; the nightly workflow calls real providers to detect contract drift early.

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

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 retries with bounded exponential backoff, circuit breakers for upstream failures, 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.