use crate::engine::functions::FunctionConfig;
use datalogic_rs::Logic;
use serde::Deserialize;
use serde_json::Value;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct TaskGroup {
pub id: String,
pub name: Option<String>,
pub description: Option<String>,
pub condition: Value,
#[doc(hidden)]
pub compiled_condition: Option<Arc<Logic>>,
pub terminal: bool,
#[doc(hidden)]
pub end: usize,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Task {
pub id: String,
#[doc(hidden)]
#[serde(skip)]
pub id_arc: Arc<str>,
pub name: String,
pub description: Option<String>,
#[serde(default = "crate::engine::utils::default_condition")]
pub condition: Value,
#[doc(hidden)]
#[serde(skip)]
pub compiled_condition: Option<Arc<Logic>>,
pub function: FunctionConfig,
#[serde(default)]
pub continue_on_error: bool,
#[serde(default)]
pub terminal: bool,
#[doc(hidden)]
#[serde(skip)]
pub group_starts: Vec<TaskGroup>,
}
impl Task {
pub fn action(id: &str, name: &str, function: FunctionConfig) -> Self {
Task {
id: id.to_string(),
id_arc: Arc::from(id),
name: name.to_string(),
description: None,
condition: Value::Bool(true),
compiled_condition: None,
function,
continue_on_error: false,
terminal: false,
group_starts: Vec::new(),
}
}
}
pub(crate) mod steps {
use super::{Task, TaskGroup};
use serde::Deserialize;
use serde::de::{Deserializer, Error as DeError};
use serde_json::Value;
const MAX_GROUP_DEPTH: usize = 8;
#[derive(Deserialize)]
struct GroupHeader {
id: String,
#[serde(default)]
name: Option<String>,
#[serde(default)]
description: Option<String>,
#[serde(default = "crate::engine::utils::default_condition")]
condition: Value,
#[serde(default)]
terminal: bool,
tasks: Vec<Value>,
}
pub(crate) fn flatten<'de, D>(deserializer: D) -> Result<Vec<Task>, D::Error>
where
D: Deserializer<'de>,
{
let steps = Vec::<Value>::deserialize(deserializer)?;
let mut tasks = Vec::with_capacity(steps.len());
walk(&steps, 0, &mut tasks).map_err(D::Error::custom)?;
Ok(tasks)
}
fn walk(steps: &[Value], depth: usize, out: &mut Vec<Task>) -> Result<(), String> {
for step in steps {
let is_group = step.get("tasks").is_some();
if !is_group {
let task: Task = serde_json::from_value(step.clone())
.map_err(|e| format!("invalid task in workflow tasks: {e}"))?;
out.push(task);
continue;
}
if depth >= MAX_GROUP_DEPTH {
return Err(format!(
"task groups nested deeper than {MAX_GROUP_DEPTH} levels"
));
}
let header: GroupHeader = serde_json::from_value(step.clone())
.map_err(|e| format!("invalid task group in workflow tasks: {e}"))?;
let start = out.len();
walk(&header.tasks, depth + 1, out)?;
let end = out.len();
if end == start {
return Err(format!(
"task group '{}' contains no tasks — an empty group can only be a mistake",
header.id
));
}
out[start].group_starts.insert(
0,
TaskGroup {
id: header.id,
name: header.name,
description: header.description,
condition: header.condition,
compiled_condition: None,
terminal: header.terminal,
end,
},
);
}
Ok(())
}
}