summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2025-11-23 12:38:49 +0200
committerrtkay123 <dev@kanjala.com>2025-11-23 12:38:49 +0200
commitae72e4f8d4ccb6d5ed71e17d6b2ffb0ac8876e0a (patch)
tree89e4c44a9e3e0c6cd32976fdc69172d42375fcf2
parent432a5061c0b910821635825021a37f798e0ce26c (diff)
downloadsellershut-ae72e4f8d4ccb6d5ed71e17d6b2ffb0ac8876e0a.tar.bz2
sellershut-ae72e4f8d4ccb6d5ed71e17d6b2ffb0ac8876e0a.zip
feat: apidoc
-rw-r--r--Cargo.lock287
-rw-r--r--crates/sellershut/Cargo.toml15
-rw-r--r--crates/sellershut/src/config.rs (renamed from crates/sellershut/src/config/mod.rs)14
-rw-r--r--crates/sellershut/src/logging.rs19
-rw-r--r--crates/sellershut/src/main.rs42
-rw-r--r--crates/sellershut/src/server.rs42
-rw-r--r--crates/sellershut/src/server/doc.rs11
-rw-r--r--crates/sellershut/src/server/middleware.rs2
-rw-r--r--crates/sellershut/src/server/middleware/request_id.rs11
-rw-r--r--crates/sellershut/src/server/middleware/timeout.rs15
-rw-r--r--crates/sellershut/src/server/routes.rs15
11 files changed, 437 insertions, 36 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 618e037..9af7318 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -53,6 +53,12 @@ dependencies = [
]
[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -127,6 +133,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
name = "async-lock"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -373,6 +388,15 @@ dependencies = [
]
[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -453,6 +477,17 @@ dependencies = [
]
[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -575,6 +610,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
+name = "flate2"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
+dependencies = [
+ "crc32fast",
+ "libz-rs-sys",
+ "miniz_oxide",
+]
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1002,6 +1048,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
+ "serde",
+ "serde_core",
]
[[package]]
@@ -1073,6 +1121,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
+name = "libz-rs-sys"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
+dependencies = [
+ "zlib-rs",
+]
+
+[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1127,6 +1184,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
name = "mio"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1255,6 +1332,12 @@ dependencies = [
]
[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1593,6 +1676,40 @@ dependencies = [
]
[[package]]
+name = "rust-embed"
+version = "8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.111",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
+[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1655,6 +1772,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1669,9 +1795,16 @@ dependencies = [
"axum",
"clap",
"tokio",
+ "tower",
"tower-http",
"tracing",
"tracing-subscriber",
+ "utoipa",
+ "utoipa-axum",
+ "utoipa-rapidoc",
+ "utoipa-redoc",
+ "utoipa-scalar",
+ "utoipa-swagger-ui",
]
[[package]]
@@ -1793,6 +1926,12 @@ dependencies = [
]
[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2054,6 +2193,7 @@ dependencies = [
"tower-layer",
"tower-service",
"tracing",
+ "uuid",
]
[[package]]
@@ -2143,6 +2283,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
+name = "unicase"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+
+[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2179,6 +2325,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "utoipa"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_json",
+ "utoipa-gen",
+]
+
+[[package]]
+name = "utoipa-axum"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0"
+dependencies = [
+ "axum",
+ "paste",
+ "tower-layer",
+ "tower-service",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-gen"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "utoipa-rapidoc"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5f8f5abd341cce16bb4f09a8bafc087d4884a004f25fb980e538d51d6501dab"
+dependencies = [
+ "axum",
+ "serde",
+ "serde_json",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-redoc"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6427547f6db7ec006cbbef95f7565952a16f362e298b416d2d497d9706fef72d"
+dependencies = [
+ "axum",
+ "serde",
+ "serde_json",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-scalar"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
+dependencies = [
+ "axum",
+ "serde",
+ "serde_json",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-swagger-ui"
+version = "9.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
+dependencies = [
+ "axum",
+ "base64",
+ "mime_guess",
+ "regex",
+ "rust-embed",
+ "serde",
+ "serde_json",
+ "url",
+ "utoipa",
+ "zip",
+]
+
+[[package]]
name = "uuid"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2202,6 +2438,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2326,6 +2572,15 @@ dependencies = [
]
[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2654,3 +2909,35 @@ dependencies = [
"quote",
"syn 2.0.111",
]
+
+[[package]]
+name = "zip"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "flate2",
+ "indexmap",
+ "memchr",
+ "zopfli",
+]
+
+[[package]]
+name = "zlib-rs"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
+
+[[package]]
+name = "zopfli"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "log",
+ "simd-adler32",
+]
diff --git a/crates/sellershut/Cargo.toml b/crates/sellershut/Cargo.toml
index 9fcf378..aee606e 100644
--- a/crates/sellershut/Cargo.toml
+++ b/crates/sellershut/Cargo.toml
@@ -13,6 +13,19 @@ anyhow = "1.0.100"
axum = { version = "0.8.7", features = ["macros"] }
clap = { version = "4.5.53", features = ["derive", "env"] }
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "signal"] }
-tower-http = { version = "0.6.6", features = ["trace", "timeout"] }
+tower = "0.5.2"
+tower-http = { version = "0.6.6", features = ["propagate-header", "request-id", "trace", "timeout"] }
tracing.workspace = true
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
+utoipa = "5.4.0"
+utoipa-axum = "0.2.0"
+utoipa-rapidoc = { version = "6.0.0", features = ["axum"], optional = true }
+utoipa-redoc = { version = "6.0.0", optional = true, features = ["axum"] }
+utoipa-scalar = { version = "0.3.0", features = ["axum"], optional = true }
+utoipa-swagger-ui = { version = "9.0.2", features = ["axum"], optional = true }
+
+[features]
+redoc = ["dep:utoipa-redoc"]
+rapidoc = ["dep:utoipa-rapidoc"]
+scalar = ["dep:utoipa-scalar"]
+swagger-ui = ["dep:utoipa-swagger-ui"]
diff --git a/crates/sellershut/src/config/mod.rs b/crates/sellershut/src/config.rs
index 9ce7b2d..65383a6 100644
--- a/crates/sellershut/src/config/mod.rs
+++ b/crates/sellershut/src/config.rs
@@ -5,20 +5,26 @@ use std::path::PathBuf;
use clap::Parser;
use logging::LogLevel;
+pub const LOG_LEVEL: &str = "LOG_LEVEL";
+
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Cli {
/// Sets the port the server listens on
- #[arg(default_value_t = 2210, env = "PORT")]
- port: u16,
+ #[arg(short, long, default_value_t = 2210, env = "PORT")]
+ pub port: u16,
- /// Sets the port the server listens on
- #[arg(short, long, value_enum, env = "LOG_LEVEL", default_value_t = LogLevel::Debug)]
+ /// Sets the application log level
+ #[arg(short, long, value_enum, env = LOG_LEVEL, default_value_t = LogLevel::Debug)]
log_level: LogLevel,
/// Sets a custom config file
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
+
+ /// Request timeout duration (in seconds)
+ #[arg(short, long, default_value_t = 10, env = "TIMEOUT_DURATION")]
+ pub timeout_duration: u64,
}
impl Cli {
diff --git a/crates/sellershut/src/logging.rs b/crates/sellershut/src/logging.rs
new file mode 100644
index 0000000..89b8686
--- /dev/null
+++ b/crates/sellershut/src/logging.rs
@@ -0,0 +1,19 @@
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
+
+use crate::config::{Cli, LOG_LEVEL};
+
+pub fn initialise_logging(config: &Cli) {
+ tracing_subscriber::registry()
+ .with(
+ tracing_subscriber::EnvFilter::try_from_env(LOG_LEVEL).unwrap_or_else(|_| {
+ format!(
+ "{}={},tower_http=debug,axum=trace",
+ env!("CARGO_CRATE_NAME"),
+ config.log_level()
+ )
+ .into()
+ }),
+ )
+ .with(tracing_subscriber::fmt::layer())
+ .init();
+}
diff --git a/crates/sellershut/src/main.rs b/crates/sellershut/src/main.rs
index 9736986..07e6193 100644
--- a/crates/sellershut/src/main.rs
+++ b/crates/sellershut/src/main.rs
@@ -1,45 +1,25 @@
mod config;
+mod logging;
+mod server;
-use std::time::Duration;
+use std::net::{Ipv6Addr, SocketAddr};
-use axum::{Router, routing::get};
use clap::Parser;
use tokio::{net::TcpListener, signal};
-use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
-use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
+use tracing::info;
+
+use crate::logging::initialise_logging;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = config::Cli::parse();
- dbg!(&config);
-
- tracing_subscriber::registry()
- .with(
- tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
- format!(
- "{}={},tower_http=debug,axum=trace",
- env!("CARGO_CRATE_NAME"),
- config.log_level()
- )
- .into()
- }),
- )
- .with(tracing_subscriber::fmt::layer().without_time())
- .init();
+ initialise_logging(&config);
- // Create a regular axum app.
- let app = Router::new()
- .route("/slow", get(|| tokio::time::sleep(Duration::from_secs(5))))
- .route("/forever", get(std::future::pending::<()>))
- .layer((
- TraceLayer::new_for_http(),
- // Graceful shutdown will wait for outstanding requests to complete. Add a timeout so
- // requests don't hang forever.
- TimeoutLayer::new(Duration::from_secs(10)),
- ));
+ let app = server::router(&config);
- // Create a `TcpListener` using tokio.
- let listener = TcpListener::bind("0.0.0.0:3000").await?;
+ let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.port));
+ info!(port = addr.port(), "starting server");
+ let listener = TcpListener::bind(addr).await?;
// Run the server with graceful shutdown
axum::serve(listener, app)
diff --git a/crates/sellershut/src/server.rs b/crates/sellershut/src/server.rs
new file mode 100644
index 0000000..cee6146
--- /dev/null
+++ b/crates/sellershut/src/server.rs
@@ -0,0 +1,42 @@
+mod doc;
+mod middleware;
+mod routes;
+
+use axum::Router;
+use utoipa::OpenApi as _;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+#[cfg(feature = "redoc")]
+use utoipa_redoc::Servable as _;
+#[cfg(feature = "scalar")]
+use utoipa_scalar::Servable as _;
+
+use crate::{config::Cli, server::doc::ApiDoc};
+
+pub fn router(config: &Cli) -> Router {
+ let (router, _api) = OpenApiRouter::with_openapi(ApiDoc::openapi())
+ .routes(routes!(routes::health_check))
+ .split_for_parts();
+
+ #[cfg(feature = "swagger-ui")]
+ let router = router.merge(
+ utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
+ .url("/api-docs/swaggerdoc.json", _api.clone()),
+ );
+
+ #[cfg(feature = "redoc")]
+ let router = router.merge(utoipa_redoc::Redoc::with_url("/redoc", _api.clone()));
+
+ #[cfg(feature = "rapidoc")]
+ let router = router.merge(
+ utoipa_rapidoc::RapiDoc::with_openapi("/api-docs/rapidoc.json", _api.clone())
+ .path("/rapidoc"),
+ );
+
+ #[cfg(feature = "scalar")]
+ let router = router.merge(utoipa_scalar::Scalar::with_url("/scalar", _api));
+
+ let router = middleware::timeout::apply(router, config.timeout_duration);
+
+ middleware::request_id::apply(router)
+}
diff --git a/crates/sellershut/src/server/doc.rs b/crates/sellershut/src/server/doc.rs
new file mode 100644
index 0000000..11b561e
--- /dev/null
+++ b/crates/sellershut/src/server/doc.rs
@@ -0,0 +1,11 @@
+use utoipa::OpenApi;
+
+pub(super) const HEALTH: &str = "HEALTH";
+
+#[derive(OpenApi)]
+#[openapi(
+ tags(
+ (name = HEALTH, description = "Check API health"),
+ )
+)]
+pub struct ApiDoc;
diff --git a/crates/sellershut/src/server/middleware.rs b/crates/sellershut/src/server/middleware.rs
new file mode 100644
index 0000000..0c44376
--- /dev/null
+++ b/crates/sellershut/src/server/middleware.rs
@@ -0,0 +1,2 @@
+pub(super) mod request_id;
+pub(super) mod timeout;
diff --git a/crates/sellershut/src/server/middleware/request_id.rs b/crates/sellershut/src/server/middleware/request_id.rs
new file mode 100644
index 0000000..cce6898
--- /dev/null
+++ b/crates/sellershut/src/server/middleware/request_id.rs
@@ -0,0 +1,11 @@
+use axum::{Router, http::HeaderName};
+use tower_http::propagate_header::PropagateHeaderLayer;
+use tracing::trace;
+
+pub fn apply(router: Router) -> Router {
+ trace!("applying x-request-id middleware");
+
+ router.layer(PropagateHeaderLayer::new(HeaderName::from_static(
+ "x-request-id",
+ )))
+}
diff --git a/crates/sellershut/src/server/middleware/timeout.rs b/crates/sellershut/src/server/middleware/timeout.rs
new file mode 100644
index 0000000..ff39c6a
--- /dev/null
+++ b/crates/sellershut/src/server/middleware/timeout.rs
@@ -0,0 +1,15 @@
+use std::time::Duration;
+
+use axum::Router;
+use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
+use tracing::trace;
+
+pub fn apply(router: Router, duration: u64) -> Router {
+ trace!(seconds = duration, "applying timeout middleware");
+ router.layer((
+ TraceLayer::new_for_http(),
+ // Graceful shutdown will wait for outstanding requests to complete. Add a timeout so
+ // requests don't hang forever.
+ TimeoutLayer::new(Duration::from_secs(duration)),
+ ))
+}
diff --git a/crates/sellershut/src/server/routes.rs b/crates/sellershut/src/server/routes.rs
new file mode 100644
index 0000000..e5d1031
--- /dev/null
+++ b/crates/sellershut/src/server/routes.rs
@@ -0,0 +1,15 @@
+/// Get health of the API.
+#[utoipa::path(
+ method(get),
+ path = "/",
+ tag = super::doc::HEALTH,
+ responses(
+ (status = OK, description = "Success", body = str, content_type = "text/plain")
+ )
+)]
+pub async fn health_check() -> impl axum::response::IntoResponse {
+ let name = env!("CARGO_PKG_NAME");
+ let ver = env!("CARGO_PKG_VERSION");
+
+ format!("{name} v{ver} is live")
+}