Skip to main content

sz_rust_workflow/definition/
strategy.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-2026 SZ-Rust Team
3//
4use serde::{Deserialize, Serialize};
5
6/// 候选人解析策略,对齐 design 2.2.2.6。
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(tag = "type", rename_all = "snake_case")]
9pub enum CandidateStrategy {
10    /// 静态指定用户与角色
11    Static {
12        #[serde(default)]
13        users: Vec<String>,
14        #[serde(default)]
15        roles: Vec<String>,
16    },
17    /// 动态表达式求值(返回 `Vec<String>`)
18    Dynamic { expr: String },
19    /// 调用能力获取候选人
20    Capability {
21        capability_name: String,
22        #[serde(default)]
23        args: serde_json::Value,
24    },
25}
26
27/// 审批策略类型,对齐 spec 6.3.2。
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum ApprovalStrategyType {
31    /// 会签:所有候选人均同意才完成
32    #[default]
33    AndSign,
34    /// 或签:任一候选人同意即完成
35    OrSign,
36}
37
38/// 容错策略,对齐 spec 5.4.1。
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
40#[serde(rename_all = "snake_case")]
41pub enum FaultStrategy {
42    /// 失败终止实例
43    #[default]
44    Fail,
45    /// 跳过节点继续
46    Skip,
47    /// 重试(指数退避)
48    Retry,
49}
50
51/// 状态机迁移定义,对齐 spec 6.2.4-6.2.5。
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct Transition {
54    /// 源状态
55    pub from: String,
56    /// 目标状态
57    pub to: String,
58    /// 触发事件名
59    pub event: String,
60    /// 守卫条件表达式(可选,求值为 true 才迁移)
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub guard: Option<String>,
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn candidate_strategy_static_serde() {
71        let s = CandidateStrategy::Static {
72            users: vec!["u1".into()],
73            roles: vec!["admin".into()],
74        };
75        let json = serde_json::to_string(&s).unwrap();
76        let back: CandidateStrategy = serde_json::from_str(&json).unwrap();
77        assert_eq!(s, back);
78    }
79
80    #[test]
81    fn candidate_strategy_dynamic_serde() {
82        let s = CandidateStrategy::Dynamic {
83            expr: "$.candidates".into(),
84        };
85        let json = serde_json::to_string(&s).unwrap();
86        assert!(json.contains("\"type\":\"dynamic\""));
87        let back: CandidateStrategy = serde_json::from_str(&json).unwrap();
88        assert_eq!(s, back);
89    }
90
91    #[test]
92    fn candidate_strategy_capability_serde() {
93        let s = CandidateStrategy::Capability {
94            capability_name: "hr.get_managers".into(),
95            args: serde_json::json!({"dept": "tech"}),
96        };
97        let json = serde_json::to_string(&s).unwrap();
98        let back: CandidateStrategy = serde_json::from_str(&json).unwrap();
99        assert_eq!(s, back);
100    }
101
102    #[test]
103    fn approval_strategy_type_serde() {
104        let json = serde_json::to_string(&ApprovalStrategyType::AndSign).unwrap();
105        assert_eq!(json, "\"and_sign\"");
106        let json = serde_json::to_string(&ApprovalStrategyType::OrSign).unwrap();
107        assert_eq!(json, "\"or_sign\"");
108    }
109
110    #[test]
111    fn fault_strategy_serde() {
112        assert_eq!(
113            serde_json::to_string(&FaultStrategy::Fail).unwrap(),
114            "\"fail\""
115        );
116        assert_eq!(
117            serde_json::to_string(&FaultStrategy::Skip).unwrap(),
118            "\"skip\""
119        );
120        assert_eq!(
121            serde_json::to_string(&FaultStrategy::Retry).unwrap(),
122            "\"retry\""
123        );
124    }
125
126    #[test]
127    fn transition_serde() {
128        let t = Transition {
129            from: "draft".into(),
130            to: "review".into(),
131            event: "submit".into(),
132            guard: Some("$.amount > 100".into()),
133        };
134        let json = serde_json::to_string(&t).unwrap();
135        let back: Transition = serde_json::from_str(&json).unwrap();
136        assert_eq!(t, back);
137
138        let t2 = Transition {
139            from: "draft".into(),
140            to: "review".into(),
141            event: "submit".into(),
142            guard: None,
143        };
144        let json2 = serde_json::to_string(&t2).unwrap();
145        assert!(!json2.contains("guard"));
146    }
147}