use crate::v1_0::criterion::Criterion;
use crate::validation::{Context, ValidateWithContext};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum SuccessActionType {
#[default]
End,
Goto,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct SuccessAction {
pub name: String,
#[serde(rename = "type")]
pub type_: SuccessActionType,
#[serde(rename = "workflowId", skip_serializing_if = "Option::is_none")]
pub workflow_id: Option<String>,
#[serde(rename = "stepId", skip_serializing_if = "Option::is_none")]
pub step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub criteria: Vec<Criterion>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
impl ValidateWithContext for SuccessAction {
fn validate_with_context(&self, ctx: &mut Context) {
ctx.require_non_empty("name", &self.name);
validate_goto_target(
self.type_ == SuccessActionType::Goto,
self.workflow_id.is_some(),
self.step_id.is_some(),
ctx,
);
for (i, criterion) in self.criteria.iter().enumerate() {
ctx.in_index("criteria", i, |ctx| criterion.validate_with_context(ctx));
}
}
}
pub(crate) fn validate_goto_target(
is_goto: bool,
has_workflow_id: bool,
has_step_id: bool,
ctx: &mut Context,
) {
if is_goto && !(has_workflow_id ^ has_step_id) {
ctx.error("`goto` requires exactly one of `workflowId` or `stepId`");
}
}
#[cfg(test)]
mod tests {
use super::*;
use enumset::EnumSet;
use serde_json::json;
fn validate(a: &SuccessAction) -> Vec<String> {
let mut ctx = Context::with_path(EnumSet::empty(), "#.a");
a.validate_with_context(&mut ctx);
ctx.errors.iter().map(ToString::to_string).collect()
}
#[test]
fn end_action_round_trips() {
let a: SuccessAction =
serde_json::from_value(json!({ "name": "done", "type": "end" })).unwrap();
assert_eq!(a.type_, SuccessActionType::End);
assert!(validate(&a).is_empty());
let v = serde_json::to_value(&a).unwrap();
assert_eq!(v, json!({ "name": "done", "type": "end" }));
}
#[test]
fn goto_requires_exactly_one_target() {
let neither = SuccessAction {
name: "g".into(),
type_: SuccessActionType::Goto,
..Default::default()
};
assert!(validate(&neither).iter().any(|e| e.contains("goto")));
let both = SuccessAction {
workflow_id: Some("w".into()),
step_id: Some("s".into()),
..neither.clone()
};
assert!(validate(&both).iter().any(|e| e.contains("goto")));
let ok = SuccessAction {
workflow_id: Some("w".into()),
..neither
};
assert!(validate(&ok).is_empty());
}
#[test]
fn validate_recurses_into_criteria() {
let a = SuccessAction {
name: "n".into(),
criteria: vec![Criterion::default()],
..Default::default()
};
assert!(validate(&a).iter().any(|e| e.contains("condition")));
}
}