use super::Rollout;
use crate::base::{AuthoredPath, StepKind};
use chrono::{DateTime, Utc};
use serde_json::{Map, Value};
pub mod defaults {
pub const PRIORITY: u32 = 0;
pub const VERSION: u32 = 1;
pub const CHANNEL: &str = "default";
pub const INIT: i64 = 0;
pub const INCREMENT: i64 = 1;
pub const MAX_CONCURRENCY: usize = 1;
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct Logic(pub Value);
impl Logic {
pub fn new(value: Value) -> Self {
Self(value)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct NodeHeader {
pub(crate) name: Option<String>,
pub(crate) description: Option<String>,
pub(crate) ext: Map<String, Value>,
}
impl NodeHeader {
#[must_use]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
#[must_use]
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
#[must_use]
pub fn ext(&self) -> &Map<String, Value> {
&self.ext
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct WorkflowDef {
pub(crate) id: String,
pub(crate) header: NodeHeader,
pub(crate) priority: u32,
pub(crate) condition: Option<Logic>,
pub(crate) tasks: Vec<StepDef>,
pub(crate) on_failure: WorkflowFailurePolicy,
pub(crate) channel: String,
pub(crate) status: Status,
pub(crate) version: u32,
pub(crate) rollout: Option<Rollout>,
pub(crate) r#loop: Option<LoopDef>,
pub(crate) tags: Vec<String>,
pub(crate) created_at: Option<DateTime<Utc>>,
pub(crate) updated_at: Option<DateTime<Utc>>,
}
impl WorkflowDef {
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn header(&self) -> &NodeHeader {
&self.header
}
#[must_use]
pub fn priority(&self) -> u32 {
self.priority
}
#[must_use]
pub fn condition(&self) -> Option<&Logic> {
self.condition.as_ref()
}
#[must_use]
pub fn tasks(&self) -> &[StepDef] {
&self.tasks
}
#[must_use]
pub fn on_failure(&self) -> WorkflowFailurePolicy {
self.on_failure
}
#[must_use]
pub fn channel(&self) -> &str {
&self.channel
}
#[must_use]
pub fn status(&self) -> Status {
self.status
}
#[must_use]
pub fn version(&self) -> u32 {
self.version
}
#[must_use]
pub fn rollout(&self) -> Option<&Rollout> {
self.rollout.as_ref()
}
#[must_use]
pub fn r#loop(&self) -> Option<&LoopDef> {
self.r#loop.as_ref()
}
#[must_use]
pub fn tags(&self) -> &[String] {
&self.tags
}
#[must_use]
pub fn created_at(&self) -> Option<DateTime<Utc>> {
self.created_at
}
#[must_use]
pub fn updated_at(&self) -> Option<DateTime<Utc>> {
self.updated_at
}
pub fn walk(&self) -> impl Iterator<Item = (AuthoredPath, StepKind, &StepDef)> {
let mut out = Vec::new();
if let Some(config) = &self.r#loop {
let mut at = AuthoredPath::root().join("loop").join("setup");
collect(&config.setup, &mut at, true, &mut out);
}
let mut at = AuthoredPath::root().join("tasks");
collect(&self.tasks, &mut at, false, &mut out);
out.into_iter()
}
}
fn collect<'d>(
steps: &'d [StepDef],
at: &mut AuthoredPath,
setup: bool,
out: &mut Vec<(AuthoredPath, StepKind, &'d StepDef)>,
) {
for (i, step) in steps.iter().enumerate() {
at.push(i);
out.push((at.clone(), step.kind(setup), step));
if let StepDef::Group(group) = step {
at.push("tasks");
collect(&group.tasks, at, setup, out);
at.pop();
}
at.pop();
}
}
#[derive(Clone, Debug, PartialEq)]
#[allow(
clippy::large_enum_variant,
reason = "a definition is parse-time data, never touched per message; a task \
is larger than a group because its JSON values are insertion-ordered \
maps (serde_json's `preserve_order`), and boxing it would cost every \
host that builds or matches a step a `Box` for no runtime gain"
)]
pub enum StepDef {
Task(TaskDef),
Group(GroupDef),
}
impl StepDef {
pub fn id(&self) -> &str {
match self {
Self::Task(task) => &task.id,
Self::Group(group) => &group.id,
}
}
pub(crate) fn kind(&self, setup: bool) -> StepKind {
match self {
_ if setup => StepKind::Setup,
Self::Task(_) => StepKind::Task,
Self::Group(_) => StepKind::Group,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct TaskDef {
pub(crate) id: String,
pub(crate) header: NodeHeader,
pub(crate) condition: Option<Logic>,
pub(crate) function: FunctionRef,
pub(crate) on_failure: FailurePolicy,
pub(crate) on_rejection: RejectionPolicy,
pub(crate) terminal: bool,
pub(crate) for_each: Option<ForEachDef>,
}
impl TaskDef {
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn header(&self) -> &NodeHeader {
&self.header
}
#[must_use]
pub fn condition(&self) -> Option<&Logic> {
self.condition.as_ref()
}
#[must_use]
pub fn function(&self) -> &FunctionRef {
&self.function
}
#[must_use]
pub fn on_failure(&self) -> FailurePolicy {
self.on_failure
}
#[must_use]
pub fn on_rejection(&self) -> RejectionPolicy {
self.on_rejection
}
#[must_use]
pub fn terminal(&self) -> bool {
self.terminal
}
#[must_use]
pub fn for_each(&self) -> Option<&ForEachDef> {
self.for_each.as_ref()
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct GroupDef {
pub(crate) id: String,
pub(crate) header: NodeHeader,
pub(crate) condition: Option<Logic>,
pub(crate) terminal: bool,
pub(crate) tasks: Vec<StepDef>,
}
impl GroupDef {
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn header(&self) -> &NodeHeader {
&self.header
}
#[must_use]
pub fn condition(&self) -> Option<&Logic> {
self.condition.as_ref()
}
#[must_use]
pub fn terminal(&self) -> bool {
self.terminal
}
#[must_use]
pub fn tasks(&self) -> &[StepDef] {
&self.tasks
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct LoopDef {
pub(crate) counter: Option<String>,
pub(crate) init: i64,
pub(crate) increment: i64,
pub(crate) max: i64,
pub(crate) setup: Vec<StepDef>,
pub(crate) over: Option<Logic>,
pub(crate) r#as: Option<String>,
pub(crate) scratch: Option<String>,
}
impl LoopDef {
#[must_use]
pub fn counter(&self) -> Option<&str> {
self.counter.as_deref()
}
#[must_use]
pub fn init(&self) -> i64 {
self.init
}
#[must_use]
pub fn increment(&self) -> i64 {
self.increment
}
#[must_use]
pub fn max(&self) -> i64 {
self.max
}
#[must_use]
pub fn setup(&self) -> &[StepDef] {
&self.setup
}
#[must_use]
pub fn over(&self) -> Option<&Logic> {
self.over.as_ref()
}
#[must_use]
pub fn r#as(&self) -> Option<&str> {
self.r#as.as_deref()
}
#[must_use]
pub fn scratch(&self) -> Option<&str> {
self.scratch.as_deref()
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct ForEachDef {
pub(crate) over: Logic,
pub(crate) r#as: String,
pub(crate) max_concurrency: usize,
pub(crate) collect: Option<String>,
pub(crate) into: Option<String>,
}
impl ForEachDef {
#[must_use]
pub fn over(&self) -> &Logic {
&self.over
}
#[must_use]
pub fn r#as(&self) -> &str {
&self.r#as
}
#[must_use]
pub fn max_concurrency(&self) -> usize {
self.max_concurrency
}
#[must_use]
pub fn collect(&self) -> Option<&str> {
self.collect.as_deref()
}
#[must_use]
pub fn into(&self) -> Option<&str> {
self.into.as_deref()
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct FunctionRef {
pub(crate) name: String,
pub(crate) input: Value,
}
impl FunctionRef {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn input(&self) -> &Value {
&self.input
}
}
pub(super) trait Spelling: Copy + Default + 'static {
const ALL: &'static [Self];
fn spelling(self) -> &'static str;
fn from_spelling(text: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|v| v.spelling() == text)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Status {
#[default]
Active,
Paused,
Archived,
}
impl Status {
pub fn as_str(self) -> &'static str {
match self {
Self::Active => "active",
Self::Paused => "paused",
Self::Archived => "archived",
}
}
}
impl Spelling for Status {
const ALL: &'static [Self] = &[Self::Active, Self::Paused, Self::Archived];
fn spelling(self) -> &'static str {
self.as_str()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FailurePolicy {
#[default]
Fail,
Halt,
Continue,
}
impl FailurePolicy {
pub fn as_str(self) -> &'static str {
match self {
Self::Fail => "fail",
Self::Halt => "halt",
Self::Continue => "continue",
}
}
}
impl Spelling for FailurePolicy {
const ALL: &'static [Self] = &[Self::Fail, Self::Halt, Self::Continue];
fn spelling(self) -> &'static str {
self.as_str()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RejectionPolicy {
#[default]
Continue,
Halt,
}
impl RejectionPolicy {
pub fn as_str(self) -> &'static str {
match self {
Self::Continue => "continue",
Self::Halt => "halt",
}
}
}
impl Spelling for RejectionPolicy {
const ALL: &'static [Self] = &[Self::Continue, Self::Halt];
fn spelling(self) -> &'static str {
self.as_str()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WorkflowFailurePolicy {
#[default]
Fail,
Continue,
}
impl WorkflowFailurePolicy {
pub fn as_str(self) -> &'static str {
match self {
Self::Fail => "fail",
Self::Continue => "continue",
}
}
}
impl Spelling for WorkflowFailurePolicy {
const ALL: &'static [Self] = &[Self::Fail, Self::Continue];
fn spelling(self) -> &'static str {
self.as_str()
}
}