chronon_axum/lib.rs
1//! Axum HTTP API for Chronon (`/api/chronon/*`).
2//!
3//! Mounts job, run, and script routes on a host Axum server. Handlers delegate to
4//! [`chronon_runtime::CoordinatorService`] and read script metadata from
5//! [`chronon_executor::ScriptRegistry`].
6//!
7//! # Routes
8//!
9//! - `GET/POST /jobs/*` — list, upsert, pause, resume, run now
10//! - `GET /runs/*` — list and fetch runs
11//! - `GET /scripts` — list registered scripts
12//!
13//! All responses use the [`ApiResponse`] envelope (`success`, `data`, `error`).
14//! [`UpsertJobRequest::script_name`] must exist in the registry or upsert returns 400.
15//!
16//! # Mode 3 (remote clients)
17//!
18//! Mount this router on a Mode 1 or Mode 2 coordinator host, then point
19//! [`chronon_runtime::RemoteCoordinatorClient`] at `{base_url}` (paths under
20//! [`API_PREFIX`]). See the `chronon` facade getting-started Mode 3 section.
21
22mod dto;
23mod handlers;
24mod handlers_common;
25mod state;
26
27use axum::{
28 extract::FromRef,
29 routing::{get, post},
30 Router,
31};
32
33pub use dto::{
34 JobActionRequest, JobResponse, ListJobsQuery, ListRunsQuery, RunResponse, ScheduleKindDto,
35 ScriptResponse, UpsertJobRequest,
36};
37pub use handlers_common::ApiResponse;
38pub use state::ChrononState;
39
40/// API mount prefix for host routers (e.g. `nest(API_PREFIX, chronon_router())`).
41pub const API_PREFIX: &str = "/api/chronon";
42
43/// Create the Chronon API router with job, run, and script routes.
44///
45/// Host state `S` must implement [`FromRef<S>`] for [`ChrononState`]. Nest under
46/// [`API_PREFIX`] (`/api/chronon`) so [`chronon_runtime::RemoteCoordinatorClient`] paths match.
47///
48/// # Examples
49///
50/// ```
51/// use std::sync::Arc;
52/// use axum::body::Body;
53/// use axum::http::{Request, StatusCode};
54/// use axum::extract::FromRef;
55/// use chronon_axum::{chronon_router, ChrononState};
56/// use chronon_backend_mem::InMemorySchedulerStore;
57/// use chronon_core::{Result, ScriptContext};
58/// use chronon_executor::{ScriptDescriptor, ScriptRegistry};
59/// use chronon_runtime::CoordinatorService;
60/// use http_body_util::BodyExt;
61/// use tower::ServiceExt;
62///
63/// fn noop(
64/// _ctx: Box<dyn ScriptContext>,
65/// _params: serde_json::Value,
66/// ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send>> {
67/// Box::pin(async { Ok(()) })
68/// }
69///
70/// #[derive(Clone)]
71/// struct AppState {
72/// chronon: ChrononState,
73/// }
74///
75/// impl FromRef<AppState> for ChrononState {
76/// fn from_ref(state: &AppState) -> Self {
77/// state.chronon.clone()
78/// }
79/// }
80///
81/// # #[tokio::main]
82/// # async fn main() {
83/// let store = Arc::new(InMemorySchedulerStore::new());
84/// let coordinator = Arc::new(CoordinatorService::new(store));
85/// let registry = Arc::new({
86/// let mut r = ScriptRegistry::new();
87/// r.register(ScriptDescriptor::new("demo", noop));
88/// r
89/// });
90/// let app = chronon_router::<AppState>().with_state(AppState {
91/// chronon: ChrononState::new(coordinator, registry),
92/// });
93/// let resp = app
94/// .oneshot(Request::builder().uri("/scripts").body(Body::empty()).unwrap())
95/// .await
96/// .unwrap();
97/// assert_eq!(resp.status(), StatusCode::OK);
98/// # }
99/// ```
100///
101/// Runnable: `cargo run -p uf-chronon --example axum_host --features mem,axum`.
102pub fn chronon_router<S>() -> Router<S>
103where
104 S: Clone + Send + Sync + 'static,
105 ChrononState: FromRef<S>,
106{
107 Router::new()
108 .route("/jobs", get(handlers::list_jobs))
109 .route("/jobs/upsert", post(handlers::upsert_job))
110 .route("/jobs/pause", post(handlers::pause_job))
111 .route("/jobs/resume", post(handlers::resume_job))
112 .route("/jobs/run_now", post(handlers::run_now))
113 .route("/jobs/{id}", get(handlers::get_job))
114 .route("/jobs/{id}/revisions", get(handlers::get_job_revisions))
115 .route("/runs", get(handlers::list_runs))
116 .route("/runs/{id}", get(handlers::get_run))
117 .route("/scripts", get(handlers::list_scripts))
118}