forge_core/cron/
context.rs1use std::sync::Arc;
2
3use chrono::{DateTime, Utc};
4use uuid::Uuid;
5
6use crate::env::{EnvAccess, EnvProvider, RealEnvProvider};
7use crate::function::AuthContext;
8
9pub struct CronContext {
11 pub run_id: Uuid,
13 pub cron_name: String,
15 pub scheduled_time: DateTime<Utc>,
17 pub execution_time: DateTime<Utc>,
19 pub timezone: String,
21 pub is_catch_up: bool,
23 pub auth: AuthContext,
25 db_pool: sqlx::PgPool,
27 http_client: reqwest::Client,
29 pub log: CronLog,
31 env_provider: Arc<dyn EnvProvider>,
33}
34
35impl CronContext {
36 pub fn new(
38 run_id: Uuid,
39 cron_name: String,
40 scheduled_time: DateTime<Utc>,
41 timezone: String,
42 is_catch_up: bool,
43 db_pool: sqlx::PgPool,
44 http_client: reqwest::Client,
45 ) -> Self {
46 Self {
47 run_id,
48 cron_name: cron_name.clone(),
49 scheduled_time,
50 execution_time: Utc::now(),
51 timezone,
52 is_catch_up,
53 auth: AuthContext::unauthenticated(),
54 db_pool,
55 http_client,
56 log: CronLog::new(cron_name),
57 env_provider: Arc::new(RealEnvProvider::new()),
58 }
59 }
60
61 pub fn with_env_provider(mut self, provider: Arc<dyn EnvProvider>) -> Self {
63 self.env_provider = provider;
64 self
65 }
66
67 pub fn db(&self) -> &sqlx::PgPool {
69 &self.db_pool
70 }
71
72 pub fn http(&self) -> &reqwest::Client {
74 &self.http_client
75 }
76
77 pub fn delay(&self) -> chrono::Duration {
79 self.execution_time - self.scheduled_time
80 }
81
82 pub fn is_late(&self) -> bool {
84 self.delay() > chrono::Duration::minutes(1)
85 }
86
87 pub fn with_auth(mut self, auth: AuthContext) -> Self {
89 self.auth = auth;
90 self
91 }
92}
93
94impl EnvAccess for CronContext {
95 fn env_provider(&self) -> &dyn EnvProvider {
96 self.env_provider.as_ref()
97 }
98}
99
100#[derive(Clone)]
102pub struct CronLog {
103 cron_name: String,
104}
105
106impl CronLog {
107 pub fn new(cron_name: String) -> Self {
109 Self { cron_name }
110 }
111
112 pub fn info(&self, message: &str, data: serde_json::Value) {
114 tracing::info!(
115 cron_name = %self.cron_name,
116 data = %data,
117 "{}",
118 message
119 );
120 }
121
122 pub fn warn(&self, message: &str, data: serde_json::Value) {
124 tracing::warn!(
125 cron_name = %self.cron_name,
126 data = %data,
127 "{}",
128 message
129 );
130 }
131
132 pub fn error(&self, message: &str, data: serde_json::Value) {
134 tracing::error!(
135 cron_name = %self.cron_name,
136 data = %data,
137 "{}",
138 message
139 );
140 }
141
142 pub fn debug(&self, message: &str, data: serde_json::Value) {
144 tracing::debug!(
145 cron_name = %self.cron_name,
146 data = %data,
147 "{}",
148 message
149 );
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[tokio::test]
158 async fn test_cron_context_creation() {
159 let pool = sqlx::postgres::PgPoolOptions::new()
160 .max_connections(1)
161 .connect_lazy("postgres://localhost/nonexistent")
162 .expect("Failed to create mock pool");
163
164 let run_id = Uuid::new_v4();
165 let scheduled = Utc::now() - chrono::Duration::seconds(30);
166
167 let ctx = CronContext::new(
168 run_id,
169 "test_cron".to_string(),
170 scheduled,
171 "UTC".to_string(),
172 false,
173 pool,
174 reqwest::Client::new(),
175 );
176
177 assert_eq!(ctx.run_id, run_id);
178 assert_eq!(ctx.cron_name, "test_cron");
179 assert!(!ctx.is_catch_up);
180 }
181
182 #[tokio::test]
183 async fn test_cron_delay() {
184 let pool = sqlx::postgres::PgPoolOptions::new()
185 .max_connections(1)
186 .connect_lazy("postgres://localhost/nonexistent")
187 .expect("Failed to create mock pool");
188
189 let scheduled = Utc::now() - chrono::Duration::minutes(5);
190
191 let ctx = CronContext::new(
192 Uuid::new_v4(),
193 "test_cron".to_string(),
194 scheduled,
195 "UTC".to_string(),
196 false,
197 pool,
198 reqwest::Client::new(),
199 );
200
201 assert!(ctx.is_late());
202 assert!(ctx.delay() >= chrono::Duration::minutes(5));
203 }
204
205 #[test]
206 fn test_cron_log() {
207 let log = CronLog::new("test_cron".to_string());
208 log.info("Test message", serde_json::json!({"key": "value"}));
209 }
210}