From 0ffb1e8b5f6ed36652b99f4baa8a55d1d9fa3981 Mon Sep 17 00:00:00 2001 From: Alberto Moretti Date: Mon, 15 Jun 2026 12:35:58 +0200 Subject: [PATCH] chore: add make targets --- Makefile | 75 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 37 ++++++++++------------ docs/architecture.md | 2 +- 3 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..34e135f --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +SHELL := /bin/sh + +CARGO ?= cargo +DOCKER ?= docker +IMAGE ?= pokedex-api +TAG ?= local +BASE_URL ?= http://localhost:5000 +POKEMON ?= mewtwo +PLATFORMS ?= linux/amd64,linux/arm64 +GIT_SHA ?= $(shell git rev-parse HEAD 2>/dev/null || printf unknown) + +.PHONY: help run docker-build docker-run cli-health cli-pokemon cli-translated health pokemon translated metrics fmt clippy test check contract-test docker-buildx + +help: + @printf 'Available targets:\n' + @printf ' make run Run the API locally with cargo\n' + @printf ' make docker-build Build the Docker image\n' + @printf ' make docker-run Run the Docker image on port 5000\n' + @printf ' make cli-health Call /health through the CLI helper\n' + @printf ' make cli-pokemon Call /pokemon/$${POKEMON} through the CLI helper\n' + @printf ' make cli-translated Call /pokemon/translated/$${POKEMON} through the CLI helper\n' + @printf ' make health curl /health\n' + @printf ' make pokemon curl /pokemon/$${POKEMON}\n' + @printf ' make translated curl /pokemon/translated/$${POKEMON}\n' + @printf ' make metrics curl /metrics\n' + @printf ' make check Run fmt, clippy, and tests\n' + @printf ' make contract-test Run ignored external contract tests\n' + @printf ' make docker-buildx Build multi-arch Docker image\n' + +run: + $(CARGO) run --bin pokedex-api + +docker-build: + $(DOCKER) build -t $(IMAGE) . + +docker-run: + $(DOCKER) run --rm -p 5000:5000 $(IMAGE) + +cli-health: + $(CARGO) run --bin pokedex-cli -- health + +cli-pokemon: + $(CARGO) run --bin pokedex-cli -- --base-url $(BASE_URL) pokemon $(POKEMON) + +cli-translated: + $(CARGO) run --bin pokedex-cli -- --base-url $(BASE_URL) translated $(POKEMON) + +health: + curl -fsS $(BASE_URL)/health + +pokemon: + curl -fsS $(BASE_URL)/pokemon/$(POKEMON) + +translated: + curl -fsS $(BASE_URL)/pokemon/translated/$(POKEMON) + +metrics: + curl -fsS $(BASE_URL)/metrics + +fmt: + $(CARGO) fmt --all --check + +clippy: + $(CARGO) clippy --locked --all-targets -- -D warnings + +test: + $(CARGO) test --locked + +check: fmt clippy test + +contract-test: + $(CARGO) test --locked --test external_contract -- --ignored --test-threads=1 + +docker-buildx: + $(DOCKER) buildx build --platform $(PLATFORMS) --build-arg GIT_SHA=$(GIT_SHA) -t $(IMAGE):$(TAG) . diff --git a/README.md b/README.md index 930902b..a77b500 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ REST API for the TrueLayer Software Engineering Challenge. It returns basic Pok ## Requirements -You can run it either with Docker or with a local Rust toolchain. +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: ```bash -docker build -t pokedex-api . -docker run --rm -p 5000:5000 pokedex-api +make docker-build +make docker-run ``` ### Option B: local Rust @@ -28,7 +28,7 @@ rustup default stable Then run the service: ```bash -cargo run --bin pokedex-api +make run ``` The API listens on `0.0.0.0:5000` by default. @@ -38,19 +38,19 @@ The API listens on `0.0.0.0:5000` by default. 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 +make cli-health +make cli-pokemon +make cli-translated +make cli-pokemon POKEMON=pikachu BASE_URL=http://localhost:5000 ``` ## 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 +make health +make pokemon +make translated +make metrics ``` Example response: @@ -109,9 +109,7 @@ The service includes: ## Development checks ```bash -cargo fmt --all --check -cargo clippy --locked --all-targets -- -D warnings -cargo test --locked +make check ``` The crate denies unsafe code and enables strict Clippy groups: `all`, `pedantic`, `nursery` and `cargo`, with only dependency-version noise allowed. @@ -121,20 +119,17 @@ The crate denies unsafe code and enables strict Clippy groups: `all`, `pedantic` 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 +make contract-test ``` ## 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 . +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, 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. +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. diff --git a/docs/architecture.md b/docs/architecture.md index a960e1a..f89eaed 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -26,7 +26,7 @@ flowchart LR 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. -PokéAPI species data changes rarely, so production deployments would add a TTL cache for successful species responses. That would reduce upstream calls, improve latency, and make transient provider failures less visible to users. +PokéAPI species data changes rarely, so production deployments would add a bounded LRU cache with TTL for successful species responses. That would avoid unnecessary random upstream hits, reduce latency, and make transient provider failures less visible to users. 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.