Skip to content

feat: axum skeleton with /health endpoint (Phase 1.b.1)#1

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/1.b.1-axum-skeleton
May 29, 2026
Merged

feat: axum skeleton with /health endpoint (Phase 1.b.1)#1
InstaZDLL merged 2 commits into
mainfrom
feat/1.b.1-axum-skeleton

Conversation

@InstaZDLL
Copy link
Copy Markdown
Owner

@InstaZDLL InstaZDLL commented May 29, 2026

Summary

First brick of RFC-001 Phase 1.b. Sets up the axum runtime so the next PRs (1.b.2 Postgres + migrations, 1.b.3 OpenAPI via utoipa, 1.b.4 CRUD modules) land on a stable foundation.

What's wired

  • axum 0.8 + tokio multi-thread. Binds `127.0.0.1:3000` by default — override with `WAVEFLOW_BIND=0.0.0.0:3000` in container / systemd deploys.
  • tracing-subscriber with env-filter + JSON formatter switchable via `WAVEFLOW_LOG_FORMAT=json`. Pretty + debug for the crate's own logs in dev.
  • tower-http middleware stack:
    • per-request UUID injected into `x-request-id` (echoed back, propagated downstream)
    • structured access trace
    • configurable request timeout (default 30 s; the streaming endpoint in 1.e will live on its own router so range requests aren't truncated)
  • Graceful shutdown on SIGINT + SIGTERM (Windows: SIGINT only — SIGTERM doesn't exist there).
  • `Config::from_env` as the single env-reading entry point. No `std::env` reads scattered across modules.

The API surface is just `GET /health` for now — returns `{status, version}` and is the template every future resource module will follow.

Out of scope (intentionally)

  • Postgres + sqlx (1.b.2)
  • `waveflow-core` git dep (1.b.2)
  • Auth middleware / X-User-Id header (1.b.4)
  • utoipa OpenAPI spec (1.b.3)
  • CRUD for profile/library/track/playlist (1.b.4)

Test plan

  • `cargo check --all-targets` ✅
  • `cargo fmt --check` ✅
  • `cargo clippy --all-targets -- -D warnings` ✅
  • Integration tests on CI Linux (`tests/health.rs` boots the real router on a kernel-assigned port and asserts the response + request-id echo path). The tests don't run locally on Windows because Defender quarantines fresh Rust network binaries — CI is the source of truth.

Signed-off-by: InstaZDLL github.105mh@8shield.net

Summary by CodeRabbit

  • New Features

    • Configurable HTTP server with /health endpoint reporting status and version
    • Request tracing and structured logging with automatic request‑ID assignment and echoing
    • Graceful shutdown on interrupt signals and configurable per‑request timeout
  • Tests

    • Added end‑to‑end integration tests for the health endpoint and request‑ID echoing
  • Chores

    • Added .env.example template for local development configuration

Review Change Stack

First brick of RFC-001 Phase 1.b. Sets up the runtime so subsequent
PRs (1.b.2 Postgres + migrations, 1.b.3 utoipa, 1.b.4 CRUD modules)
land on a stable foundation.

What's wired:
- axum 0.8 + tokio multi-thread runtime, binds 127.0.0.1:3000 by
  default (override via WAVEFLOW_BIND).
- tracing-subscriber with env-filter + JSON formatter switchable via
  WAVEFLOW_LOG_FORMAT=json. Defaults are pretty + debug for the crate's
  own logs + tower_http access traces.
- tower-http middleware: per-request UUID (x-request-id, echoed back),
  trace layer, configurable request timeout (default 30s, the
  streaming endpoint in 1.e will live on a separate router).
- Graceful shutdown on SIGINT + SIGTERM (Windows: SIGINT only).
- `Config::from_env` as the single env-reading entry point; no
  `std::env` reads scattered across modules.

API surface today is just `GET /health` → `{status, version}`. Two
integration tests in `tests/health.rs` boot the real router on a
kernel-assigned port and validate the response + that the request-id
echo path works. No DB, no auth, no waveflow-core dependency yet —
those land in 1.b.2.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5129592f-8b06-4419-87e3-4bab517ac5bf

📥 Commits

Reviewing files that changed from the base of the PR and between 49dbd98 and 61656b0.

📒 Files selected for processing (2)
  • src/config.rs
  • src/lib.rs

📝 Walkthrough

Walkthrough

Adds a full Axum server: example environment, dependencies, a Config loader, a /health endpoint, an app(config) assembly with request-id/tracing/timeout middleware, async binary with tracing and graceful shutdown, plus integration tests for health and request-id propagation.

Changes

Axum Server Bootstrap

Layer / File(s) Summary
Dependencies & .env example
.env.example, Cargo.toml
Adds .env template documenting WAVEFLOW_BIND, WAVEFLOW_REQUEST_TIMEOUT_SECS, and RUST_LOG. Declares runtime and dev dependencies: axum, tokio, tower(-http), tracing, tracing-subscriber, dotenvy, serde/serde_json, anyhow, and reqwest for tests.
Configuration & loader
src/config.rs
Config { bind_addr: SocketAddr, request_timeout_secs: u64 } and Config::from_env() parse WAVEFLOW_BIND (default 127.0.0.1:3000) and WAVEFLOW_REQUEST_TIMEOUT_SECS (default 30), returning anyhow errors for invalid vars and validating timeout > 0.
Health Endpoint & API Router
src/api/health.rs, src/api/mod.rs
GET /health returns JSON {"status":"ok","version":CARGO_PKG_VERSION}. Top-level API router exposes and merges the health routes and documents mounting conventions.
App Factory & Middleware Stack
src/lib.rs
pub fn app(config: Config) -> Router composes api::router() and applies middleware: request-id assignment/propagation (x-request-id), HTTP tracing spans including request-id, and a timeout layer returning REQUEST_TIMEOUT on exceed.
Server Startup & Graceful Shutdown
src/main.rs
#[tokio::main] async fn main() -> anyhow::Result<()> loads .env (best-effort), initializes tracing via RUST_LOG/WAVEFLOW_LOG_FORMAT (JSON/compact), builds Config, binds TCP listener, serves the app, and awaits Ctrl+C/SIGTERM for graceful shutdown.
Integration Tests
tests/health.rs
Two end-to-end tests: (1) boot app on an ephemeral port and verify /health returns status: "ok" and non-empty version; (2) send /health with x-request-id and assert the response echoes the same header.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Server as AxumServer
    participant RequestID as RequestIdLayer
    participant Tracing as TraceLayer
    participant Router as APIRouter
    participant Health as HealthHandler

    Client->>Server: GET /health (+ optional x-request-id)
    Server->>RequestID: receive request
    RequestID->>RequestID: set or echo x-request-id
    RequestID->>Tracing: forward request with request-id
    Tracing->>Router: route request
    Router->>Health: dispatch /health
    Health->>Router: JSON {status:"ok", version}
    Router->>Tracing: response
    Tracing->>RequestID: attach tracing info
    RequestID->>Server: add x-request-id header to response
    Server->>Client: 200 JSON + x-request-id
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

A rabbit coded late into the night, 🐇
Bound addresses set and timeouts just right,
Request IDs hop through logs with a cheer,
Health checks reply, steady and clear,
WaveFlow wakes up — the server is bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementation of an axum HTTP server skeleton with a /health endpoint, matching the PR's primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1.b.1-axum-skeleton

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config.rs`:
- Around line 41-47: The parsing currently allows
WAVEFLOW_REQUEST_TIMEOUT_SECS=0 which yields a zero timeout; after extracting
request_timeout_secs (the variable created by the current parse chain in
src/config.rs) validate that the final value is > 0 and return an error if it
equals 0. Concretely, after the existing parse/unwrap_or(30) expression that
assigns request_timeout_secs, add a check (or incorporate into the parsing
chain) that returns an anyhow::anyhow!("invalid WAVEFLOW_REQUEST_TIMEOUT_SECS:
must be > 0") error when request_timeout_secs == 0 so the service fails fast on
this misconfiguration.

In `@src/lib.rs`:
- Around line 45-48: Trace spans currently don't include the x-request-id
because TraceLayer::new_for_http() uses DefaultMakeSpan with
include_headers=false; replace or augment that call to use
TraceLayer::make_span_with so the created span includes the request id: either
use DefaultMakeSpan::new().include_headers(true) inside make_span_with or
implement a custom closure that reads REQUEST_ID_HEADER from the request headers
and adds a request_id field to the span; update the layer setup where
TraceLayer::new_for_http() is called alongside PropagateRequestIdLayer::new(...)
so emitted spans contain the request_id for correlation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bf3cc68e-2f0b-445b-86b5-f2e6f296d93a

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec1771 and 49dbd98.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .env.example
  • Cargo.toml
  • src/api/health.rs
  • src/api/mod.rs
  • src/config.rs
  • src/lib.rs
  • src/main.rs
  • tests/health.rs

Comment thread src/config.rs
Comment thread src/lib.rs Outdated
@InstaZDLL InstaZDLL self-assigned this May 29, 2026
Two valid issues caught on the 1.b.1 review:

1. `Config::from_env` accepted `WAVEFLOW_REQUEST_TIMEOUT_SECS=0`,
   which would make every request 408 before reaching the handler.
   Bail at boot with a clear error so the misconfig fails fast.

2. The TraceLayer used `DefaultMakeSpan`, which drops every header —
   the propagated request id never made it into the access log, so
   correlation between a 5xx and its trace was impossible. Custom
   `make_span_with` extracts just the `x-request-id` header (not
   `include_headers(true)`, which would leak Authorization / Cookie
   into the log sinks).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL InstaZDLL merged commit b06b2dd into main May 29, 2026
4 checks passed
@InstaZDLL InstaZDLL deleted the feat/1.b.1-axum-skeleton branch May 29, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant