nika_core/ast/raw/
task.rs1use indexmap::IndexMap;
4
5use super::action::RawTaskAction;
6use crate::ast::decompose::DecomposeSpec;
7use crate::ast::structured::StructuredOutputSpec;
8use crate::source::{Span, Spanned};
9
10#[derive(Debug, Clone, Default)]
16pub struct RawTask {
17 pub id: Spanned<String>,
19
20 pub description: Option<Spanned<String>>,
22
23 pub action: Option<RawTaskAction>,
25
26 pub provider: Option<Spanned<String>>,
28
29 pub model: Option<Spanned<String>>,
31
32 pub with_refs: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
41
42 pub depends_on: Option<Spanned<Vec<Spanned<String>>>>,
47
48 pub output: Option<Spanned<RawOutputConfig>>,
50
51 pub for_each: Option<Spanned<RawForEach>>,
53
54 pub retry: Option<Spanned<RawRetryConfig>>,
56
57 pub decompose: Option<Spanned<DecomposeSpec>>,
59
60 pub concurrency: Option<Spanned<u32>>,
62
63 pub fail_fast: Option<Spanned<bool>>,
65
66 pub structured: Option<StructuredOutputSpec>,
68
69 pub artifact: Option<Spanned<serde_json::Value>>,
71
72 pub log: Option<Spanned<serde_json::Value>>,
74
75 pub span: Span,
77}
78
79#[derive(Debug, Clone, Default)]
81pub struct RawOutputConfig {
82 pub format: Option<Spanned<String>>,
84 pub schema: Option<Spanned<serde_json::Value>>,
86 pub schema_ref: Option<Spanned<String>>,
88 pub max_retries: Option<Spanned<u32>>,
90}
91
92#[derive(Debug, Clone, Default)]
94pub struct RawForEach {
95 pub items: Spanned<String>,
97 pub as_var: Option<Spanned<String>>,
99 pub concurrency: Option<Spanned<u32>>,
101 pub fail_fast: Option<Spanned<bool>>,
103}
104
105#[derive(Debug, Clone, Default)]
107pub struct RawRetryConfig {
108 pub max_attempts: Option<Spanned<u32>>,
110 pub delay_ms: Option<Spanned<u64>>,
112 pub backoff: Option<Spanned<f64>>,
114}
115
116impl RawTask {
117 pub fn new(id: impl Into<String>) -> Self {
119 Self {
120 id: Spanned::dummy(id.into()),
121 ..Default::default()
122 }
123 }
124
125 pub fn has_dependencies(&self) -> bool {
127 self.with_refs
128 .as_ref()
129 .map(|w| !w.value.is_empty())
130 .unwrap_or(false)
131 || self
132 .depends_on
133 .as_ref()
134 .map(|d| !d.value.is_empty())
135 .unwrap_or(false)
136 }
137
138 pub fn depends_on_ids(&self) -> Vec<&str> {
140 match &self.depends_on {
141 Some(deps) => deps.value.iter().map(|s| s.value.as_str()).collect(),
142 None => vec![],
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::source::FileId;
151
152 fn make_span(start: u32, end: u32) -> Span {
153 Span::new(FileId(0), start, end)
154 }
155
156 #[test]
157 fn test_raw_task_new() {
158 let task = RawTask::new("my-task");
159 assert_eq!(task.id.value, "my-task");
160 assert!(!task.has_dependencies());
161 }
162
163 #[test]
164 fn test_task_has_dependencies_with() {
165 let mut task = RawTask::new("consumer");
166
167 let mut with_refs = IndexMap::new();
168 with_refs.insert(
169 Spanned::new("data".to_string(), make_span(0, 4)),
170 Spanned::new("producer".to_string(), make_span(6, 14)),
171 );
172 task.with_refs = Some(Spanned::new(with_refs, make_span(0, 20)));
173
174 assert!(task.has_dependencies());
175 }
176
177 #[test]
178 fn test_task_has_dependencies_depends_on() {
179 let mut task = RawTask::new("consumer");
180
181 task.depends_on = Some(Spanned::new(
182 vec![
183 Spanned::new("setup".to_string(), make_span(0, 5)),
184 Spanned::new("init".to_string(), make_span(7, 11)),
185 ],
186 make_span(0, 15),
187 ));
188
189 assert!(task.has_dependencies());
190 assert_eq!(task.depends_on_ids(), vec!["setup", "init"]);
191 }
192
193 #[test]
194 fn test_task_depends_on_empty() {
195 let task = RawTask::new("standalone");
196 assert!(task.depends_on_ids().is_empty());
197 assert!(!task.has_dependencies());
198 }
199}