1#![deny(missing_docs)]
2#![deny(rustdoc::broken_intra_doc_links)]
3mod auth;
12mod error;
13pub mod handlers;
14mod pagination;
15mod state;
16pub mod types;
17
18use crate::state::OpsState;
19use axum::{middleware, Router};
20use klieo_a2a::task_store::A2aTaskStore;
21use klieo_auth_common::Authenticator;
22use klieo_core::resume::KvResumeBuffer;
23use klieo_runlog::RunLogStore;
24use std::sync::Arc;
25
26#[derive(Debug, thiserror::Error)]
28#[non_exhaustive]
29pub enum OpsBuilderError {
30 #[error("authenticator not configured — call with_authenticator() before build()")]
32 MissingAuthenticator,
33}
34
35pub struct OpsRouterBuilder {
37 authenticator: Option<Arc<dyn Authenticator>>,
38 run_log_store: Option<Arc<dyn RunLogStore>>,
39 task_store: Option<Arc<A2aTaskStore>>,
40 resume_buffer: Option<Arc<KvResumeBuffer>>,
41}
42
43impl OpsRouterBuilder {
44 pub fn new() -> Self {
48 Self {
49 authenticator: None,
50 run_log_store: None,
51 task_store: None,
52 resume_buffer: None,
53 }
54 }
55
56 pub fn with_authenticator(mut self, auth: Arc<dyn Authenticator>) -> Self {
58 self.authenticator = Some(auth);
59 self
60 }
61
62 #[cfg(feature = "dev-auth")]
73 pub fn with_dev_auth(self) -> Self {
74 self.with_authenticator(Arc::new(klieo_auth_common::AllowAnonymous))
75 }
76
77 pub fn with_run_log_store(mut self, store: Arc<dyn RunLogStore>) -> Self {
79 self.run_log_store = Some(store);
80 self
81 }
82
83 pub fn with_task_store(mut self, store: Arc<A2aTaskStore>) -> Self {
85 self.task_store = Some(store);
86 self
87 }
88
89 pub fn with_resume_buffer(mut self, buf: Arc<KvResumeBuffer>) -> Self {
91 self.resume_buffer = Some(buf);
92 self
93 }
94
95 pub fn build(self) -> Result<Router, OpsBuilderError> {
99 let authenticator = self
100 .authenticator
101 .ok_or(OpsBuilderError::MissingAuthenticator)?;
102
103 let state = Arc::new(OpsState {
104 authenticator,
105 run_log_store: self.run_log_store,
106 task_store: self.task_store,
107 resume_buffer: self.resume_buffer,
108 });
109
110 use crate::handlers::{runs, streams, tasks};
111 use axum::routing::get;
112
113 Ok(Router::new()
114 .route("/runs", get(runs::list_runs))
115 .route("/runs/:run_id", get(runs::get_run))
116 .route("/agents", get(runs::list_agents))
117 .route("/tasks", get(tasks::list_tasks))
118 .route("/streams", get(streams::list_streams))
119 .layer(middleware::from_fn_with_state(
120 Arc::clone(&state),
121 crate::auth::require_auth,
122 ))
123 .with_state(state))
124 }
125}
126
127impl Default for OpsRouterBuilder {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn build_without_authenticator_returns_missing_authenticator() {
139 let err = OpsRouterBuilder::new().build().unwrap_err();
140 assert!(matches!(err, OpsBuilderError::MissingAuthenticator));
141 }
142
143 #[cfg(feature = "dev-auth")]
144 #[test]
145 fn with_dev_auth_satisfies_authenticator_requirement() {
146 let result = OpsRouterBuilder::new().with_dev_auth().build();
147 assert!(
148 result.is_ok(),
149 "with_dev_auth must produce a buildable router"
150 );
151 }
152}