systemprompt_provider_contracts/
job.rs1use std::collections::HashMap;
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use serde::{Deserialize, Serialize};
15use systemprompt_identifiers::Actor;
16
17use crate::error::ProviderResult;
18
19#[derive(Debug, Clone)]
20pub struct JobResult {
21 pub success: bool,
22 pub message: Option<String>,
23 pub items_processed: Option<u64>,
24 pub items_failed: Option<u64>,
25 pub duration_ms: u64,
26}
27
28impl JobResult {
29 #[must_use]
30 pub const fn success() -> Self {
31 Self {
32 success: true,
33 message: None,
34 items_processed: None,
35 items_failed: None,
36 duration_ms: 0,
37 }
38 }
39
40 #[must_use]
41 pub fn with_message(mut self, message: impl Into<String>) -> Self {
42 self.message = Some(message.into());
43 self
44 }
45
46 #[must_use]
47 pub const fn with_stats(mut self, processed: u64, failed: u64) -> Self {
48 self.items_processed = Some(processed);
49 self.items_failed = Some(failed);
50 self
51 }
52
53 #[must_use]
54 pub const fn with_duration(mut self, duration_ms: u64) -> Self {
55 self.duration_ms = duration_ms;
56 self
57 }
58
59 #[must_use]
60 pub fn failure(message: impl Into<String>) -> Self {
61 Self {
62 success: false,
63 message: Some(message.into()),
64 items_processed: None,
65 items_failed: None,
66 duration_ms: 0,
67 }
68 }
69}
70
71pub struct JobContext {
72 actor: Actor,
73 db_pool: Arc<dyn std::any::Any + Send + Sync>,
74 app_context: Arc<dyn std::any::Any + Send + Sync>,
75 app_paths: Arc<dyn std::any::Any + Send + Sync>,
76 parameters: HashMap<String, String>,
77 enforce: bool,
78}
79
80impl std::fmt::Debug for JobContext {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("JobContext")
83 .field("actor", &self.actor)
84 .field("db_pool", &"<type-erased>")
85 .field("app_context", &"<type-erased>")
86 .field("app_paths", &"<type-erased>")
87 .field("parameters", &self.parameters)
88 .field("enforce", &self.enforce)
89 .finish()
90 }
91}
92
93impl JobContext {
94 #[must_use]
95 pub fn new(
96 actor: Actor,
97 db_pool: Arc<dyn std::any::Any + Send + Sync>,
98 app_context: Arc<dyn std::any::Any + Send + Sync>,
99 app_paths: Arc<dyn std::any::Any + Send + Sync>,
100 ) -> Self {
101 Self {
102 actor,
103 db_pool,
104 app_context,
105 app_paths,
106 parameters: HashMap::new(),
107 enforce: false,
108 }
109 }
110
111 #[must_use]
112 pub const fn enforce(&self) -> bool {
113 self.enforce
114 }
115
116 #[must_use]
117 pub const fn with_enforce(mut self, enforce: bool) -> Self {
118 self.enforce = enforce;
119 self
120 }
121
122 #[must_use]
123 pub const fn actor(&self) -> &Actor {
124 &self.actor
125 }
126
127 #[must_use]
128 pub fn with_parameters(mut self, parameters: HashMap<String, String>) -> Self {
129 self.parameters = parameters;
130 self
131 }
132
133 #[must_use]
134 pub fn db_pool<T: 'static>(&self) -> Option<&T> {
135 self.db_pool.as_ref().downcast_ref::<T>()
136 }
137
138 #[must_use]
139 pub fn app_context<T: 'static>(&self) -> Option<&T> {
140 self.app_context.as_ref().downcast_ref::<T>()
141 }
142
143 #[must_use]
144 pub fn app_paths<T: 'static>(&self) -> Option<&T> {
145 self.app_paths.as_ref().downcast_ref::<T>()
146 }
147
148 #[must_use]
149 pub fn db_pool_arc(&self) -> Arc<dyn std::any::Any + Send + Sync> {
150 Arc::clone(&self.db_pool)
151 }
152
153 #[must_use]
154 pub fn app_context_arc(&self) -> Arc<dyn std::any::Any + Send + Sync> {
155 Arc::clone(&self.app_context)
156 }
157
158 #[must_use]
159 pub fn app_paths_arc(&self) -> Arc<dyn std::any::Any + Send + Sync> {
160 Arc::clone(&self.app_paths)
161 }
162
163 #[must_use]
164 pub const fn parameters(&self) -> &HashMap<String, String> {
165 &self.parameters
166 }
167
168 #[must_use]
169 pub fn get_parameter(&self, key: &str) -> Option<&String> {
170 self.parameters.get(key)
171 }
172
173 pub fn get_parameter_parsed<T: std::str::FromStr>(
174 &self,
175 key: &str,
176 ) -> Result<Option<T>, crate::ProviderError>
177 where
178 T::Err: std::fmt::Display,
179 {
180 self.parameters
181 .get(key)
182 .map(|value| {
183 value.parse().map_err(|e| {
184 crate::ProviderError::Configuration(format!(
185 "invalid job parameter {key}={value}: {e}"
186 ))
187 })
188 })
189 .transpose()
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
199#[serde(rename_all = "lowercase")]
200pub enum JobScope {
201 #[default]
202 Cluster,
203 Node,
204}
205
206#[async_trait]
207pub trait Job: Send + Sync + 'static {
208 fn name(&self) -> &'static str;
209
210 fn description(&self) -> &'static str {
211 ""
212 }
213
214 fn schedule(&self) -> &'static str;
215
216 fn tags(&self) -> Vec<&'static str> {
217 vec![]
218 }
219
220 async fn execute(&self, ctx: &JobContext) -> ProviderResult<JobResult>;
221
222 fn enabled(&self) -> bool {
223 true
224 }
225
226 fn schedulable(&self) -> bool {
227 true
228 }
229
230 fn scope(&self) -> JobScope {
231 JobScope::Cluster
232 }
233}
234
235inventory::collect!(&'static dyn Job);
236
237#[macro_export]
238macro_rules! submit_job {
239 ($job:expr) => {
240 inventory::submit!($job as &'static dyn $crate::Job);
241 };
242}