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