use indexmap::IndexMap;
use super::action::RawTaskAction;
use crate::ast::decompose::DecomposeSpec;
use crate::ast::structured::StructuredOutputSpec;
use crate::source::{Span, Spanned};
#[derive(Debug, Clone, Default)]
pub struct RawTask {
pub id: Spanned<String>,
pub description: Option<Spanned<String>>,
pub action: Option<RawTaskAction>,
pub provider: Option<Spanned<String>>,
pub model: Option<Spanned<String>>,
pub with_refs: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
pub depends_on: Option<Spanned<Vec<Spanned<String>>>>,
pub output: Option<Spanned<RawOutputConfig>>,
pub for_each: Option<Spanned<RawForEach>>,
pub retry: Option<Spanned<RawRetryConfig>>,
pub decompose: Option<Spanned<DecomposeSpec>>,
pub concurrency: Option<Spanned<u32>>,
pub fail_fast: Option<Spanned<bool>>,
pub structured: Option<StructuredOutputSpec>,
pub artifact: Option<Spanned<serde_json::Value>>,
pub log: Option<Spanned<serde_json::Value>>,
pub span: Span,
}
#[derive(Debug, Clone, Default)]
pub struct RawOutputConfig {
pub format: Option<Spanned<String>>,
pub schema: Option<Spanned<serde_json::Value>>,
pub schema_ref: Option<Spanned<String>>,
pub max_retries: Option<Spanned<u32>>,
}
#[derive(Debug, Clone, Default)]
pub struct RawForEach {
pub items: Spanned<String>,
pub as_var: Option<Spanned<String>>,
pub parallel: Option<Spanned<u32>>,
pub fail_fast: Option<Spanned<bool>>,
}
#[derive(Debug, Clone, Default)]
pub struct RawRetryConfig {
pub max_attempts: Option<Spanned<u32>>,
pub delay_ms: Option<Spanned<u64>>,
pub backoff: Option<Spanned<f64>>,
}
impl RawTask {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: Spanned::dummy(id.into()),
..Default::default()
}
}
pub fn has_dependencies(&self) -> bool {
self.with_refs
.as_ref()
.map(|w| !w.value.is_empty())
.unwrap_or(false)
|| self
.depends_on
.as_ref()
.map(|d| !d.value.is_empty())
.unwrap_or(false)
}
pub fn depends_on_ids(&self) -> Vec<&str> {
match &self.depends_on {
Some(deps) => deps.value.iter().map(|s| s.value.as_str()).collect(),
None => vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source::FileId;
fn make_span(start: u32, end: u32) -> Span {
Span::new(FileId(0), start, end)
}
#[test]
fn test_raw_task_new() {
let task = RawTask::new("my-task");
assert_eq!(task.id.value, "my-task");
assert!(!task.has_dependencies());
}
#[test]
fn test_task_has_dependencies_with() {
let mut task = RawTask::new("consumer");
let mut with_refs = IndexMap::new();
with_refs.insert(
Spanned::new("data".to_string(), make_span(0, 4)),
Spanned::new("producer".to_string(), make_span(6, 14)),
);
task.with_refs = Some(Spanned::new(with_refs, make_span(0, 20)));
assert!(task.has_dependencies());
}
#[test]
fn test_task_has_dependencies_depends_on() {
let mut task = RawTask::new("consumer");
task.depends_on = Some(Spanned::new(
vec![
Spanned::new("setup".to_string(), make_span(0, 5)),
Spanned::new("init".to_string(), make_span(7, 11)),
],
make_span(0, 15),
));
assert!(task.has_dependencies());
assert_eq!(task.depends_on_ids(), vec!["setup", "init"]);
}
#[test]
fn test_task_depends_on_empty() {
let task = RawTask::new("standalone");
assert!(task.depends_on_ids().is_empty());
assert!(!task.has_dependencies());
}
}