summaryrefslogtreecommitdiffstats
path: root/crates/sellershut/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/sellershut/src')
-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
9 files changed, 136 insertions, 35 deletions
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")
+}