razeri_plugin_contract/
task_renderer.rs1use crate::SqlRenderer;
8
9#[derive(Debug)]
14pub struct TaskRenderConfig<'a> {
15 pub name: &'a str,
17 pub body: &'a str,
19 pub schedule: Option<String>,
21 pub warehouse: Option<&'a str>,
23 pub comment: Option<&'a str>,
25 pub error_integration: Option<&'a str>,
27 pub condition: Option<&'a str>,
29 pub dependencies: Vec<TaskDependencyConfig<'a>>,
31 pub enabled: bool,
35}
36
37#[derive(Debug)]
39pub struct TaskDependencyConfig<'a> {
40 pub predecessor: &'a str,
42 pub condition: &'a str,
44}
45
46#[must_use]
50pub fn render_dependency_comment(deps: &[TaskDependencyConfig<'_>]) -> String {
51 if deps.is_empty() {
52 return String::new();
53 }
54 let strs: Vec<String> =
55 deps.iter().map(|d| format!("{} ({})", d.predecessor, d.condition)).collect();
56 format!("-- Dependencies: {}", strs.join(", "))
57}
58
59pub trait TaskRenderer: SqlRenderer {
63 fn render_schedule_cron(&self, expression: &str, timezone: Option<&str>) -> String {
69 let tz = timezone.unwrap_or("UTC");
70 format!("-- Schedule: cron {expression} {tz}")
71 }
72
73 fn render_schedule_interval(&self, minutes: u32) -> String {
75 format!("-- Schedule: every {minutes} minutes")
76 }
77
78 fn render_schedule_stream(&self, stream_name: &str) -> String {
80 format!("-- Stream trigger: {stream_name}")
81 }
82
83 fn render_schedule_event(&self, source: &str, description: &str) -> String {
85 format!("-- Event-driven: {source} ({description})")
86 }
87
88 fn render_create_task(&self, config: &TaskRenderConfig<'_>) -> Option<String> {
103 let _ = config;
104 None
105 }
106
107 fn render_drop_task(&self, name: &str) -> String {
109 format!("-- Drop task: {name} (platform manages externally)")
110 }
111
112 fn render_resume_task(&self, name: &str) -> String {
114 format!("-- Resume task: {name} (no native suspend/resume)")
115 }
116
117 fn render_suspend_task(&self, name: &str) -> String {
119 format!("-- Suspend task: {name} (no native suspend/resume)")
120 }
121
122 fn render_bulk_load(&self, table: &str, source: &str, format: &str) -> Option<String> {
126 let _ = (table, source, format);
127 None
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::DataType;
135
136 struct TestDialect;
137
138 impl SqlRenderer for TestDialect {
139 fn render_data_type(&self, _dt: &DataType) -> String {
140 "TEXT".to_string()
141 }
142
143 fn quote_identifier(&self, name: &str) -> String {
144 format!("\"{name}\"")
145 }
146
147 fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
148 match schema {
149 Some(s) => format!("\"{s}\".\"{name}\""),
150 None => format!("\"{name}\""),
151 }
152 }
153
154 fn render_current_timestamp(&self) -> String {
155 "CURRENT_TIMESTAMP".to_string()
156 }
157 }
158
159 impl TaskRenderer for TestDialect {}
160
161 #[test]
162 fn task_renderer_defaults_return_comments() {
163 let d = TestDialect;
164
165 assert_eq!(
167 d.render_schedule_cron("0 * * * *", Some("UTC")),
168 "-- Schedule: cron 0 * * * * UTC"
169 );
170 assert_eq!(d.render_schedule_interval(15), "-- Schedule: every 15 minutes");
171 assert_eq!(d.render_schedule_stream("my_stream"), "-- Stream trigger: my_stream");
172 assert_eq!(
173 d.render_schedule_event("sns", "order placed"),
174 "-- Event-driven: sns (order placed)"
175 );
176
177 let config = TaskRenderConfig {
180 name: "hourly",
181 body: "SELECT 1",
182 schedule: Some("-- Schedule: cron 0 * * * * UTC".to_string()),
183 warehouse: None,
184 comment: Some("Hourly check"),
185 error_integration: None,
186 condition: None,
187 dependencies: vec![],
188 enabled: true,
189 };
190 assert_eq!(d.render_create_task(&config), None);
191
192 assert_eq!(
194 d.render_drop_task("hourly"),
195 "-- Drop task: hourly (platform manages externally)"
196 );
197 assert_eq!(
198 d.render_resume_task("hourly"),
199 "-- Resume task: hourly (no native suspend/resume)"
200 );
201 assert_eq!(
202 d.render_suspend_task("hourly"),
203 "-- Suspend task: hourly (no native suspend/resume)"
204 );
205
206 assert_eq!(d.render_bulk_load("raw_orders", "s3://bucket/orders.csv", "csv"), None);
208 }
209
210 #[test]
211 fn render_dependency_comment_formats_correctly() {
212 let deps = vec![
213 TaskDependencyConfig { predecessor: "task_a", condition: "on success" },
214 TaskDependencyConfig { predecessor: "task_b", condition: "on completion" },
215 ];
216 let result = render_dependency_comment(&deps);
217 assert_eq!(result, "-- Dependencies: task_a (on success), task_b (on completion)");
218 }
219
220 #[test]
221 fn render_dependency_comment_empty_returns_empty() {
222 assert_eq!(render_dependency_comment(&[]), "");
223 }
224}