diff options
author | rtkay123 <dev@kanjala.com> | 2025-08-08 08:58:03 +0200 |
---|---|---|
committer | rtkay123 <dev@kanjala.com> | 2025-08-08 08:58:03 +0200 |
commit | 92d853e8c535d21c7e51200ba5ca2b8393fb0656 (patch) | |
tree | 0097009ddd837ec84550808a55fa46fbb73a5b4f | |
parent | b9df4e581cf747c4ccffa3ded49c153e7cbf4cc1 (diff) | |
download | warden-92d853e8c535d21c7e51200ba5ca2b8393fb0656.tar.bz2 warden-92d853e8c535d21c7e51200ba5ca2b8393fb0656.zip |
feat(warden): health route
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | crates/warden/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/warden/src/cnfg.rs | 7 | ||||
-rw-r--r-- | crates/warden/src/error.rs | 26 | ||||
-rw-r--r-- | crates/warden/src/main.rs | 23 | ||||
-rw-r--r-- | crates/warden/src/server.rs | 25 | ||||
-rw-r--r-- | crates/warden/src/server/routes.rs | 36 | ||||
-rw-r--r-- | crates/warden/src/state.rs | 40 | ||||
-rw-r--r-- | crates/warden/src/version.rs | 30 | ||||
-rw-r--r-- | crates/warden/warden.toml | 3 |
11 files changed, 195 insertions, 3 deletions
@@ -1450,8 +1450,10 @@ dependencies = [ "clap", "config", "serde", + "serde_json", "stack-up", "tokio", + "tower", "tracing", "warden-core", ] @@ -15,8 +15,10 @@ clap = "4.5.43" config = { version = "0.15.13", default-features = false } prost = "0.14.1" serde = "1.0.219" +serde_json = "1.0.142" stack-up = { git = "https://github.com/rtkay123/stack-up" } tokio = "1.47.1" tonic = "0.14.0" +tower = "0.5.2" tracing = "0.1.41" warden-core = { path = "lib/warden-core" } diff --git a/crates/warden/Cargo.toml b/crates/warden/Cargo.toml index 7b9ae9e..c50271e 100644 --- a/crates/warden/Cargo.toml +++ b/crates/warden/Cargo.toml @@ -13,10 +13,14 @@ axum = { workspace = true, features = ["macros"] } clap = { workspace = true, features = ["derive"] } config = { workspace = true, features = ["convert-case", "toml"] } serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } tracing.workspace = true warden-core = { workspace = true, features = ["iso20022"] } +[dev-dependencies] +tower = { workspace = true, features = ["util"] } + [dependencies.stack-up] workspace = true features = ["api", "tracing"] diff --git a/crates/warden/src/cnfg.rs b/crates/warden/src/cnfg.rs new file mode 100644 index 0000000..f3fa016 --- /dev/null +++ b/crates/warden/src/cnfg.rs @@ -0,0 +1,7 @@ +use serde::Deserialize; + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct LocalConfig { + pub cache_ttl: u64, +} diff --git a/crates/warden/src/error.rs b/crates/warden/src/error.rs new file mode 100644 index 0000000..730f99a --- /dev/null +++ b/crates/warden/src/error.rs @@ -0,0 +1,26 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; + +#[derive(Debug)] +pub struct AppError(anyhow::Error); + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {}", self.0), + ) + .into_response() + } +} + +impl<E> From<E> for AppError +where + E: Into<anyhow::Error>, +{ + fn from(err: E) -> Self { + Self(err.into()) + } +} diff --git a/crates/warden/src/main.rs b/crates/warden/src/main.rs index 884d2a8..8ebbdf8 100644 --- a/crates/warden/src/main.rs +++ b/crates/warden/src/main.rs @@ -1,8 +1,17 @@ -use anyhow::Result; +mod cnfg; +mod error; +mod server; +mod state; +mod version; + +use std::net::{Ipv6Addr, SocketAddr}; + use clap::{Parser, command}; use stack_up::{Configuration, tracing::Tracing}; use tracing::info; +use crate::state::AppState; + /// warden #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -13,7 +22,7 @@ struct Args { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), error::AppError> { let args = Args::parse(); let config = include_str!("../warden.toml"); @@ -30,6 +39,14 @@ async fn main() -> Result<()> { let _tracing = Tracing::builder().build(&config.monitoring); - info!("Hello, world!"); + let state = AppState::create(&config).await?; + + let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.application.port)); + + let listener = tokio::net::TcpListener::bind(addr).await?; + info!(port = addr.port(), "starting warden"); + + axum::serve(listener, server::router(state)).await?; + Ok(()) } diff --git a/crates/warden/src/server.rs b/crates/warden/src/server.rs new file mode 100644 index 0000000..15f80bd --- /dev/null +++ b/crates/warden/src/server.rs @@ -0,0 +1,25 @@ +mod routes; + +use axum::{routing::get, Router}; + +use crate::state::AppHandle; + +pub fn router(state: AppHandle) -> Router { + Router::new() + .route("/", get(routes::health_check)) +} + + +#[cfg(test)] +pub(crate) fn test_config() -> stack_up::Configuration { + use stack_up::Configuration; + + let config_path = "warden.toml"; + + let config = config::Config::builder() + .add_source(config::File::new(config_path, config::FileFormat::Toml)) + .build() + .unwrap(); + + config.try_deserialize::<Configuration>().unwrap() +} diff --git a/crates/warden/src/server/routes.rs b/crates/warden/src/server/routes.rs new file mode 100644 index 0000000..e8e6705 --- /dev/null +++ b/crates/warden/src/server/routes.rs @@ -0,0 +1,36 @@ +use axum::response::IntoResponse; + +pub async fn health_check() -> impl IntoResponse { + let name = env!("CARGO_PKG_NAME"); + let ver = env!("CARGO_PKG_VERSION"); + + format!("{name} v{ver} is live") +} + +#[cfg(test)] +mod tests { + use axum::{ + body::Body, + http::{Request, StatusCode}, + }; + use tower::ServiceExt; + + use crate::{ + server::{self, test_config}, + state::AppState, + }; + + #[tokio::test] + async fn health_check() { + let state = AppState::create( &test_config()).await.unwrap(); + let app = server::router(state); + + let response = app + .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + } +} + diff --git a/crates/warden/src/state.rs b/crates/warden/src/state.rs new file mode 100644 index 0000000..ce44221 --- /dev/null +++ b/crates/warden/src/state.rs @@ -0,0 +1,40 @@ +use std::{ops::Deref, sync::Arc}; +use stack_up::{Configuration, Environment}; + +use crate::{cnfg::LocalConfig, error::AppError}; + +#[derive(Clone)] +pub struct AppHandle(Arc<AppState>); + +impl Deref for AppHandle { + type Target = Arc<AppState>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone)] +pub struct Services { +} + +pub struct AppState { + pub environment: Environment, +} + +impl AppState { + pub async fn create( + configuration: &Configuration, + ) -> Result<AppHandle, AppError> { + let local_config: LocalConfig = serde_json::from_value(configuration.misc.clone())?; + + + Ok( + + AppHandle(Arc::new(Self { + environment: configuration.application.env, + } + ))) + } +} + diff --git a/crates/warden/src/version.rs b/crates/warden/src/version.rs new file mode 100644 index 0000000..1d4a9ca --- /dev/null +++ b/crates/warden/src/version.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +use axum::{extract::{FromRequestParts, Path}, http::{request::Parts, StatusCode}, response::{IntoResponse, Response}, RequestPartsExt}; + +#[derive(Debug)] +pub enum Version { + V0, +} + + +impl<S> FromRequestParts<S> for Version +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { + let params: Path<HashMap<String, String>> = + parts.extract().await.map_err(IntoResponse::into_response)?; + + let version = params + .get("version") + .ok_or_else(|| (StatusCode::NOT_FOUND, "version param missing").into_response())?; + + match version.as_str() { + "v0" => Ok(Version::V0), + _ => Err((StatusCode::NOT_FOUND, "unknown version").into_response()), + } + } +} diff --git a/crates/warden/warden.toml b/crates/warden/warden.toml index ec10f51..5222260 100644 --- a/crates/warden/warden.toml +++ b/crates/warden/warden.toml @@ -2,6 +2,9 @@ env = "development" port = 2210 +[misc] +cache-ttl = 1000 + [monitoring] log-level = "warden=trace,info" |