1use std::sync::Arc;
4
5use boson_core::{
6 BosonError, IdempotencyMode, Job, JobEnqueueDisposition, JobStatus, QueueBackend, Result, Run,
7 TaskConfig, TaskRunStats,
8};
9use chrono::{DateTime, Utc};
10
11use crate::registry::TaskRegistry;
12use crate::worker::WorkerSettings;
13
14#[derive(Clone)]
16pub struct Boson {
17 pub(crate) backend: Arc<dyn QueueBackend>,
18 pub(crate) registry: Arc<TaskRegistry>,
19 worker: WorkerSettings,
20 idempotency_mode: IdempotencyMode,
22}
23
24impl Boson {
25 pub fn from_parts(
27 backend: Arc<dyn QueueBackend>,
28 registry: Arc<TaskRegistry>,
29 worker: WorkerSettings,
30 ) -> Self {
31 Self::from_parts_with_idempotency(backend, registry, worker, IdempotencyMode::Lwt)
32 }
33
34 pub fn from_parts_with_idempotency(
36 backend: Arc<dyn QueueBackend>,
37 registry: Arc<TaskRegistry>,
38 worker: WorkerSettings,
39 idempotency_mode: IdempotencyMode,
40 ) -> Self {
41 Self {
42 backend,
43 registry,
44 worker,
45 idempotency_mode,
46 }
47 }
48
49 #[must_use]
51 pub const fn idempotency_mode(&self) -> IdempotencyMode {
52 self.idempotency_mode
53 }
54
55 #[must_use]
57 pub const fn worker_settings(&self) -> &WorkerSettings {
58 &self.worker
59 }
60
61 #[must_use]
63 pub fn runtime_label(&self) -> &str {
64 &self.worker.runtime_label
65 }
66
67 #[must_use]
69 pub fn queue_backend(&self) -> Arc<dyn QueueBackend> {
70 Arc::clone(&self.backend)
71 }
72
73 #[must_use]
75 pub fn registry(&self) -> &TaskRegistry {
76 &self.registry
77 }
78
79 pub async fn resolve_task_config(&self, task_name: &str) -> Result<TaskConfig> {
88 let config = if let Some(c) = self.backend.get_task_config(task_name).await? {
89 c
90 } else {
91 self.registry.get_or_err(task_name)?.to_task_config()
92 };
93 Ok(config.with_runtime_idempotency_fallback(self.idempotency_mode()))
94 }
95
96 pub async fn resolve_priority_pool(&self, task_name: &str) -> Result<(i32, String)> {
102 let config = self.resolve_task_config(task_name).await?;
103 Ok((config.priority, config.pool))
104 }
105
106 pub(crate) async fn enqueue_internal(
108 &self,
109 task_name: &str,
110 actor_json: serde_json::Value,
111 params_json: serde_json::Value,
112 idempotency_key: Option<String>,
113 ) -> Result<String> {
114 let descriptor = self.registry.get_or_err(task_name)?;
115 let task_config = self.resolve_task_config(task_name).await?;
116 let priority = task_config.priority;
117 let pool = task_config.pool.clone();
118 let job = Job::new(
119 task_name,
120 actor_json,
121 params_json,
122 priority,
123 &pool,
124 descriptor.signature_hash,
125 idempotency_key,
126 );
127 let (job_id, disposition) = self
128 .backend
129 .enqueue_with_policies(job, &task_config)
130 .await
131 .map_err(|e| {
132 if matches!(e, BosonError::RateLimited(_)) {
133 crate::telemetry::record_task_failed(
134 task_name,
135 "",
136 "",
137 &e.to_string(),
138 false,
139 );
140 }
141 e
142 })?;
143 if disposition == JobEnqueueDisposition::InsertedNew {
144 crate::telemetry::record_task_enqueued(task_name, self.runtime_label());
145 }
146 Ok(job_id)
147 }
148
149 pub async fn enqueue(
160 &self,
161 task_name: &str,
162 actor_json: serde_json::Value,
163 params_json: serde_json::Value,
164 idempotency_key: Option<String>,
165 ) -> Result<String> {
166 self.enqueue_internal(task_name, actor_json, params_json, idempotency_key)
167 .await
168 }
169
170 pub async fn get_job(&self, job_id: &str) -> Result<Option<Job>> {
176 self.backend.get_job(job_id).await
177 }
178
179 pub async fn list_jobs(
185 &self,
186 status_filter: Option<JobStatus>,
187 offset: usize,
188 limit: usize,
189 ) -> Result<Vec<Job>> {
190 self.backend.list_jobs(status_filter, offset, limit).await
191 }
192
193 pub async fn cancel_job(&self, job_id: &str) -> Result<()> {
199 self.backend.cancel_job_if_active(job_id).await
200 }
201
202 pub async fn get_task_config(&self, task_name: &str) -> Result<TaskConfig> {
208 self.resolve_task_config(task_name).await
209 }
210
211 pub async fn upsert_task_config(&self, config: TaskConfig) -> Result<()> {
217 self.backend.upsert_task_config(&config).await
218 }
219
220 pub async fn list_runs(
226 &self,
227 job_id_filter: Option<&str>,
228 offset: usize,
229 limit: usize,
230 ) -> Result<Vec<Run>> {
231 self.backend.list_runs(job_id_filter, offset, limit).await
232 }
233
234 pub async fn get_run(&self, run_id: &str) -> Result<Option<Run>> {
240 self.backend.get_run(run_id).await
241 }
242
243 pub async fn count_jobs(&self, status_filter: Option<JobStatus>) -> Result<u64> {
249 self.backend.count_jobs(status_filter).await
250 }
251
252 pub async fn count_runs(&self, job_id_filter: Option<&str>) -> Result<u64> {
258 self.backend.count_runs(job_id_filter).await
259 }
260
261 pub async fn count_runs_since(&self, since: DateTime<Utc>) -> Result<u64> {
267 self.backend.count_runs_since(since).await
268 }
269
270 pub async fn count_jobs_for_task(
276 &self,
277 task_name: &str,
278 status: Option<JobStatus>,
279 ) -> Result<u64> {
280 self.backend.count_jobs_for_task(task_name, status).await
281 }
282
283 pub async fn task_run_stats(&self, task_name: &str) -> Result<TaskRunStats> {
289 self.backend.task_run_stats(task_name).await
290 }
291}