Skip to main content

axum_api_kit/
lib.rs

1//! Shared response types for Axum JSON APIs.
2//!
3//! Provides building blocks that every Axum CRUD service needs but always
4//! re-defines from scratch:
5//!
6//! - [`ApiError`] - a machine-readable JSON error body with `code`, `message`, and optional
7//!   `details`, plus factory helpers that return `(StatusCode, Json<ApiError>)` tuples ready
8//!   for use with Axum's [`IntoResponse`](axum::response::IntoResponse). Supports `From`
9//!   conversions for common error types. With the optional `validator` feature enabled,
10//!   also supports converting `validator::ValidationErrors` into structured field errors.
11//!   With the optional `sqlx` feature enabled, also supports converting `sqlx::Error` into
12//!   semantically correct HTTP status codes (404, 409, 422, 503, 500).
13//! - [`ListResponse<T>`] - a generic offset/limit paginated collection response with `data`,
14//!   `total`, `limit`, and `offset` fields.
15//! - [`CursorResponse<T>`] - a generic cursor-based paginated collection response for large
16//!   datasets or feeds, with `data`, `next_cursor`, and `has_more` fields.
17//! - [`HealthResponse`] - a health-check response with `status` field supporting `ok`,
18//!   `degraded`, and `unhealthy` states.
19//! - [`Created<T>`], [`Accepted<T>`], and [`NoContent`] - success-side responses for the
20//!   rest of the CRUD lifecycle: `201 Created` (with an optional `Location` header),
21//!   `202 Accepted`, and `204 No Content`.
22//!
23//! With optional feature flags enabled, the kit also provides request extractors that
24//! reject with an [`ApiError`] body on failure:
25//!
26//! - `ValidatedJson<T>` (feature `validator`) - deserializes a JSON body and runs
27//!   `validator` validation before the handler runs.
28//! - `Pagination` and `CursorPagination` (feature `extract`) - parse `limit`/`offset` and
29//!   `cursor`/`limit` query parameters into typed values, with `list_response` /
30//!   `cursor_response` helpers that build the matching response type.
31//!
32//! It also ships observability middleware:
33//!
34//! - `propagate_request_id` and `trace_requests` (feature `trace`) - assign an
35//!   `x-request-id` correlation id (extractable via `RequestId`) and emit a structured
36//!   `tracing` event with method, path, status, and latency for each request.
37//!
38//! And service-wiring helpers:
39//!
40//! - `health_routes` and `liveness` (feature `router`) - a `Router` exposing `/healthz` and
41//!   `/readyz` probes backed by `HealthResponse`.
42//! - `cors_allowing` and `cors_permissive` (feature `cors`) - build a `tower_http`
43//!   `CorsLayer` with sensible defaults.
44//!
45//! With the `openapi` feature, all four response types derive `utoipa::ToSchema` so they
46//! can be referenced from a `utoipa` `OpenApi` document and appear in generated specs.
47//!
48//! # Quick Start
49//!
50//! ```rust,no_run
51//! use axum::{Json, http::StatusCode, response::IntoResponse};
52//! use axum_api_kit::{ApiError, ListResponse, CursorResponse, HealthResponse};
53//! use serde::Serialize;
54//!
55//! #[derive(Serialize)]
56//! struct Item { id: String }
57//!
58//! async fn list_items() -> impl IntoResponse {
59//!     let items = vec![Item { id: "1".into() }];
60//!     Json(ListResponse { data: items, total: 1, limit: 50, offset: 0 })
61//! }
62//!
63//! async fn feed_items(cursor: Option<String>) -> impl IntoResponse {
64//!     let items = vec![Item { id: "1".into() }];
65//!     CursorResponse { data: items, next_cursor: Some("abc".into()), has_more: true }
66//! }
67//!
68//! async fn get_item() -> impl IntoResponse {
69//!     ApiError::not_found("item not found")
70//! }
71//!
72//! async fn health() -> impl IntoResponse {
73//!     HealthResponse::ok()
74//! }
75//! ```
76
77#[cfg(feature = "cors")]
78mod cors;
79mod cursor;
80mod error;
81mod health;
82mod list;
83#[cfg(feature = "extract")]
84mod pagination;
85#[cfg(feature = "router")]
86mod router;
87mod success;
88#[cfg(feature = "trace")]
89mod trace;
90#[cfg(feature = "validator")]
91mod validated;
92
93#[cfg(feature = "cors")]
94pub use cors::{cors_allowing, cors_permissive};
95pub use cursor::CursorResponse;
96pub use error::ApiError;
97pub use health::HealthResponse;
98pub use list::ListResponse;
99#[cfg(feature = "extract")]
100pub use pagination::{CursorPagination, Pagination};
101#[cfg(feature = "router")]
102pub use router::{health_routes, liveness};
103pub use success::{Accepted, Created, NoContent};
104#[cfg(feature = "trace")]
105pub use trace::{propagate_request_id, trace_requests, RequestId, REQUEST_ID_HEADER};
106#[cfg(feature = "validator")]
107pub use validated::ValidatedJson;