From 3a19387fa19caf9bd5ccdd4a85c632f25b8d81b2 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Sat, 14 Feb 2026 17:56:59 +0530 Subject: [PATCH 1/4] feat: add REST API timeout and improve config docs This change introduces a global 3-second timeout to the REST API server using tower-http's TimeoutLayer. This ensures that hanging requests are terminated early with a 408 Request Timeout status code. Additionally, the documentation for TaskConfig has been expanded to provide clearer explanations of task execution delays, retries, and how the out-of-date setting affects task monitoring and timeouts. --- src/config.rs | 21 ++++++++++++++------- src/rest_api.rs | 12 +++++++----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/config.rs b/src/config.rs index b192ce6..7a56dbe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,17 +13,24 @@ pub enum Delay { #[derive(serde::Deserialize, serde::Serialize, Clone, Copy, Debug)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct TaskConfig { - /// Delay between runs + /// Delay between successful task executions. pub delay: Delay, - /// How many seconds before we should consider the result out of date + /// Number of seconds a task can run before it is considered "out of date". /// - /// This does not include the delay time + /// If a task's execution time exceeds this value, its status will be flagged + /// accordingly. This is useful for monitoring tasks that might be stuck. + /// + /// Setting this also enables a hard timeout on task execution. If a task + /// runs for longer than `MAX_TASK_SECONDS` (180 seconds), it will be + /// cancelled. pub out_of_date: Option, - /// How many times to retry before giving up, overriding the general watcher - /// config + /// Number of times to retry a failing task before giving up. + /// + /// This overrides the global `retries` setting in `WatcherConfig`. pub retries: Option, - /// How many seconds to delay between retries, overriding the general - /// watcher config + /// Delay in seconds between retries of a failing task. + /// + /// This overrides the global `delay_between_retries` setting in `WatcherConfig`. pub delay_between_retries: Option, } diff --git a/src/rest_api.rs b/src/rest_api.rs index 95a96b5..0f859cf 100644 --- a/src/rest_api.rs +++ b/src/rest_api.rs @@ -1,9 +1,9 @@ -use std::{convert::Infallible, sync::Arc}; +use std::{convert::Infallible, sync::Arc, time::Duration}; use anyhow::Result; use axum::{ extract::{Path, State}, - http::{self, HeaderMap, header}, + http::{self, HeaderMap, StatusCode, header}, response::IntoResponse, routing::get, }; @@ -12,6 +12,7 @@ use tower::ServiceBuilder; use tower_http::{ cors::CorsLayer, limit::RequestBodyLimitLayer, + timeout::TimeoutLayer, trace::{self, TraceLayer}, }; use tracing::Level; @@ -40,9 +41,10 @@ pub(crate) async fn start_rest_api Date: Sat, 14 Feb 2026 18:18:58 +0530 Subject: [PATCH 2/4] Extend the router --- src/lib.rs | 6 ++++++ src/rest_api.rs | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 342996e..5240b68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,12 @@ pub trait WatcherAppContext { fn watcher_config(&self) -> WatcherConfig; fn triggers_alert(&self, label: &TaskLabel, selected_label: Option<&TaskLabel>) -> bool; fn show_output(&self, label: &TaskLabel) -> bool; + fn extend_router(&self, router: axum::Router) -> axum::Router + where + S: Clone + Send + Sync + 'static, + { + router + } } #[derive( diff --git a/src/rest_api.rs b/src/rest_api.rs index 0f859cf..235d658 100644 --- a/src/rest_api.rs +++ b/src/rest_api.rs @@ -56,9 +56,16 @@ pub(crate) async fn start_rest_api Date: Mon, 16 Feb 2026 14:29:29 +0530 Subject: [PATCH 3/4] feat: add router extension and export axum Expose the Axum crate and allow router extensions through the WatcherAppContext trait. This allows developers to define custom API endpoints and state while maintaining version compatibility with the underlying web server. Simplify internal configuration logic by removing the defaults module. Define default values for retries and delays directly within the WatcherConfig structure and update the example to demonstrate custom routing. --- examples/leaderboard.rs | 19 +++++++++++++++++++ src/config.rs | 8 ++------ src/defaults.rs | 7 ------- src/lib.rs | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) delete mode 100644 src/defaults.rs diff --git a/examples/leaderboard.rs b/examples/leaderboard.rs index 25795a6..40ae039 100644 --- a/examples/leaderboard.rs +++ b/examples/leaderboard.rs @@ -1,5 +1,6 @@ use anyhow::{Result, bail}; use jiff::Zoned; +use job_watcher::axum::{Router, extract::State, routing::get}; use job_watcher::{ AppBuilder, Heartbeat, TaskLabel, WatchedTask, WatchedTaskOutput, WatcherAppContext, config::{Delay, TaskConfig, WatcherConfig}, @@ -83,6 +84,24 @@ impl WatcherAppContext for DummyApp { fn title(&self) -> String { "Example application Status".to_owned() } + + fn extend_router(&self, router: Router) -> Router + where + S: Clone + Send + Sync + 'static, + { + let custom_router = Router::new() + .route("/", get(hello_handler)) + .with_state(HelloState(self.0.clone())); + + router.nest_service("/hello", custom_router) + } +} + +#[derive(Clone)] +struct HelloState(Zoned); + +async fn hello_handler(State(state): State) -> String { + format!("Hello from custom route! App live since {}", state.0) } impl WatchedTask for LeaderBoard { diff --git a/src/config.rs b/src/config.rs index 7a56dbe..6773e0b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,3 @@ -use crate::defaults; use std::collections::HashMap; #[derive(serde::Deserialize, serde::Serialize, Clone, Copy, Debug)] @@ -38,20 +37,17 @@ pub struct TaskConfig { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct WatcherConfig { /// How many times to retry before giving up - #[serde(default = "defaults::retries")] pub retries: usize, /// How many seconds to delay between retries - #[serde(default = "defaults::delay_between_retries")] pub delay_between_retries: u32, - #[serde(default)] pub tasks: HashMap, } impl Default for WatcherConfig { fn default() -> Self { Self { - retries: defaults::retries(), - delay_between_retries: defaults::delay_between_retries(), + retries: 6, + delay_between_retries: 20, tasks: Default::default(), } } diff --git a/src/defaults.rs b/src/defaults.rs deleted file mode 100644 index 60c1cf3..0000000 --- a/src/defaults.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(super) fn retries() -> usize { - 6 -} - -pub(super) fn delay_between_retries() -> u32 { - 20 -} diff --git a/src/lib.rs b/src/lib.rs index 5240b68..07a6844 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,9 +22,9 @@ mod rest_api; pub mod config; -mod defaults; use anyhow::{Context, Result}; +pub use axum; use axum::{ Json, http::{self, HeaderValue}, From 890de7da7c134c9ec2775a23dbda92769a094be0 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Mon, 16 Feb 2026 14:34:02 +0530 Subject: [PATCH 4/4] cargo fmt --- src/rest_api.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/rest_api.rs b/src/rest_api.rs index 235d658..a2a5827 100644 --- a/src/rest_api.rs +++ b/src/rest_api.rs @@ -60,12 +60,10 @@ pub(crate) async fn start_rest_api