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