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}
32
33#[derive(Debug)]
35pub struct TaskDependencyConfig<'a> {
36 pub predecessor: &'a str,
38 pub condition: &'a str,
40}
41
42#[must_use]
46pub fn render_dependency_comment(deps: &[TaskDependencyConfig<'_>]) -> String {
47 if deps.is_empty() {
48 return String::new();
49 }
50 let strs: Vec<String> =
51 deps.iter().map(|d| format!("{} ({})", d.predecessor, d.condition)).collect();
52 format!("-- Dependencies: {}", strs.join(", "))
53}
54
55pub trait TaskRenderer: SqlRenderer {
59 fn render_schedule_cron(&self, expression: &str, timezone: Option<&str>) -> String {
65 let tz = timezone.unwrap_or("UTC");
66 format!("-- Schedule: cron {expression} {tz}")
67 }
68
69 fn render_schedule_interval(&self, minutes: u32) -> String {
71 format!("-- Schedule: every {minutes} minutes")
72 }
73
74 fn render_schedule_stream(&self, stream_name: &str) -> String {
76 format!("-- Stream trigger: {stream_name}")
77 }
78
79 fn render_schedule_event(&self, source: &str, description: &str) -> String {
81 format!("-- Event-driven: {source} ({description})")
82 }
83
84 fn render_create_task(&self, config: &TaskRenderConfig<'_>) -> String {
90 let mut lines = Vec::new();
91 if let Some(comment) = config.comment {
92 lines.push(format!("-- {comment}"));
93 }
94 if let Some(ref schedule) = config.schedule {
95 lines.push(schedule.clone());
96 }
97 let dep = render_dependency_comment(&config.dependencies);
98 if !dep.is_empty() {
99 lines.push(dep);
100 }
101 lines.push(format!("{};", config.body));
102 lines.join("\n")
103 }
104
105 fn render_drop_task(&self, name: &str) -> String {
107 format!("-- Drop task: {name} (platform manages externally)")
108 }
109
110 fn render_resume_task(&self, name: &str) -> String {
112 format!("-- Resume task: {name} (no native suspend/resume)")
113 }
114
115 fn render_suspend_task(&self, name: &str) -> String {
117 format!("-- Suspend task: {name} (no native suspend/resume)")
118 }
119
120 fn render_bulk_load(&self, _table: &str, _source: &str, _format: &str) -> String {
122 "-- Bulk load not supported on this platform".to_string()
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::DataType;
130
131 struct TestDialect;
132
133 impl SqlRenderer for TestDialect {
134 fn render_data_type(&self, _dt: &DataType) -> String {
135 "TEXT".to_string()
136 }
137
138 fn quote_identifier(&self, name: &str) -> String {
139 format!("\"{name}\"")
140 }
141
142 fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
143 match schema {
144 Some(s) => format!("\"{s}\".\"{name}\""),
145 None => format!("\"{name}\""),
146 }
147 }
148
149 fn render_current_timestamp(&self) -> String {
150 "CURRENT_TIMESTAMP".to_string()
151 }
152 }
153
154 impl TaskRenderer for TestDialect {}
155
156 #[test]
157 fn task_renderer_defaults_return_comments() {
158 let d = TestDialect;
159
160 assert_eq!(
162 d.render_schedule_cron("0 * * * *", Some("UTC")),
163 "-- Schedule: cron 0 * * * * UTC"
164 );
165 assert_eq!(d.render_schedule_interval(15), "-- Schedule: every 15 minutes");
166 assert_eq!(d.render_schedule_stream("my_stream"), "-- Stream trigger: my_stream");
167 assert_eq!(
168 d.render_schedule_event("sns", "order placed"),
169 "-- Event-driven: sns (order placed)"
170 );
171
172 let config = TaskRenderConfig {
174 name: "hourly",
175 body: "SELECT 1",
176 schedule: Some("-- Schedule: cron 0 * * * * UTC".to_string()),
177 warehouse: None,
178 comment: Some("Hourly check"),
179 error_integration: None,
180 condition: None,
181 dependencies: vec![],
182 };
183 let sql = d.render_create_task(&config);
184 assert!(sql.contains("-- Hourly check"));
185 assert!(sql.contains("SELECT 1;"));
186
187 assert_eq!(
189 d.render_drop_task("hourly"),
190 "-- Drop task: hourly (platform manages externally)"
191 );
192 assert_eq!(
193 d.render_resume_task("hourly"),
194 "-- Resume task: hourly (no native suspend/resume)"
195 );
196 assert_eq!(
197 d.render_suspend_task("hourly"),
198 "-- Suspend task: hourly (no native suspend/resume)"
199 );
200
201 assert_eq!(
203 d.render_bulk_load("raw_orders", "s3://bucket/orders.csv", "csv"),
204 "-- Bulk load not supported on this platform"
205 );
206 }
207
208 #[test]
209 fn render_dependency_comment_formats_correctly() {
210 let deps = vec![
211 TaskDependencyConfig { predecessor: "task_a", condition: "on success" },
212 TaskDependencyConfig { predecessor: "task_b", condition: "on completion" },
213 ];
214 let result = render_dependency_comment(&deps);
215 assert_eq!(result, "-- Dependencies: task_a (on success), task_b (on completion)");
216 }
217
218 #[test]
219 fn render_dependency_comment_empty_returns_empty() {
220 assert_eq!(render_dependency_comment(&[]), "");
221 }
222}