1use af_context::{InstanceId, WorkflowDefinitionId};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(tag = "resource", rename_all = "snake_case", deny_unknown_fields)]
8pub enum WorkflowTarget {
9 Definition {
11 definition_id: WorkflowDefinitionId,
13 },
14 Instance {
16 instance_id: InstanceId,
18 },
19}
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct WorkflowMetadataChange {
24 pub target: WorkflowTarget,
26 pub expected_version: u64,
28 pub name: Option<String>,
30 pub archived: Option<bool>,
32 pub idempotency_key: String,
34}
35impl WorkflowMetadataChange {
36 pub fn validate(&self) -> Result<(), String> {
38 if self.expected_version == 0 || self.expected_version >= i64::MAX as u64 {
39 return Err("workflow metadata version must be within 1..i64::MAX".into());
40 }
41 if self.idempotency_key.trim().is_empty() || self.idempotency_key.len() > 256 {
42 return Err("workflow metadata idempotency key requires 1..256 bytes".into());
43 }
44 if self.name.is_none() && self.archived.is_none() {
45 return Err("workflow metadata patch is empty".into());
46 }
47 if self
48 .name
49 .as_ref()
50 .is_some_and(|name| name.trim().is_empty() || name.chars().count() > 256)
51 {
52 return Err("workflow display name requires 1..256 characters".into());
53 }
54 Ok(())
55 }
56}
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct WorkflowMetadata {
60 pub target: WorkflowTarget,
62 pub version: u64,
64 pub name: String,
66 pub archived: bool,
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 #[test]
74 fn metadata_input_is_bounded_and_has_no_identity_override() {
75 let valid = WorkflowMetadataChange {
76 target: WorkflowTarget::Definition {
77 definition_id: "definition".parse().unwrap(),
78 },
79 expected_version: 1,
80 name: Some("名称".into()),
81 archived: None,
82 idempotency_key: "key".into(),
83 };
84 assert!(valid.validate().is_ok());
85 let mut changed = valid.clone();
86 changed.name = None;
87 assert!(changed.validate().is_err());
88 changed.archived = Some(false);
89 assert!(changed.validate().is_ok());
90 for name in [" ".into(), "字".repeat(257)] {
91 changed.name = Some(name);
92 assert!(changed.validate().is_err());
93 }
94 for version in [0, i64::MAX as u64, u64::MAX] {
95 let mut changed = valid.clone();
96 changed.expected_version = version;
97 assert!(changed.validate().is_err());
98 }
99 for key in [" ".into(), "k".repeat(257)] {
100 let mut changed = valid.clone();
101 changed.idempotency_key = key;
102 assert!(changed.validate().is_err());
103 }
104 let mut value = serde_json::to_value(&valid).unwrap();
105 value["subject_id"] = "other".into();
106 assert!(serde_json::from_value::<WorkflowMetadataChange>(value).is_err());
107 let instance = WorkflowTarget::Instance {
108 instance_id: "instance".parse().unwrap(),
109 };
110 assert_eq!(
111 serde_json::from_value::<WorkflowTarget>(serde_json::to_value(&instance).unwrap())
112 .unwrap(),
113 instance
114 );
115 }
116}