use af_context::{InstanceId, WorkflowDefinitionId};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "resource", rename_all = "snake_case", deny_unknown_fields)]
pub enum WorkflowTarget {
Definition {
definition_id: WorkflowDefinitionId,
},
Instance {
instance_id: InstanceId,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowMetadataChange {
pub target: WorkflowTarget,
pub expected_version: u64,
pub name: Option<String>,
pub archived: Option<bool>,
pub idempotency_key: String,
}
impl WorkflowMetadataChange {
pub fn validate(&self) -> Result<(), String> {
if self.expected_version == 0 || self.expected_version >= i64::MAX as u64 {
return Err("workflow metadata version must be within 1..i64::MAX".into());
}
if self.idempotency_key.trim().is_empty() || self.idempotency_key.len() > 256 {
return Err("workflow metadata idempotency key requires 1..256 bytes".into());
}
if self.name.is_none() && self.archived.is_none() {
return Err("workflow metadata patch is empty".into());
}
if self
.name
.as_ref()
.is_some_and(|name| name.trim().is_empty() || name.chars().count() > 256)
{
return Err("workflow display name requires 1..256 characters".into());
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkflowMetadata {
pub target: WorkflowTarget,
pub version: u64,
pub name: String,
pub archived: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_input_is_bounded_and_has_no_identity_override() {
let valid = WorkflowMetadataChange {
target: WorkflowTarget::Definition {
definition_id: "definition".parse().unwrap(),
},
expected_version: 1,
name: Some("名称".into()),
archived: None,
idempotency_key: "key".into(),
};
assert!(valid.validate().is_ok());
let mut changed = valid.clone();
changed.name = None;
assert!(changed.validate().is_err());
changed.archived = Some(false);
assert!(changed.validate().is_ok());
for name in [" ".into(), "字".repeat(257)] {
changed.name = Some(name);
assert!(changed.validate().is_err());
}
for version in [0, i64::MAX as u64, u64::MAX] {
let mut changed = valid.clone();
changed.expected_version = version;
assert!(changed.validate().is_err());
}
for key in [" ".into(), "k".repeat(257)] {
let mut changed = valid.clone();
changed.idempotency_key = key;
assert!(changed.validate().is_err());
}
let mut value = serde_json::to_value(&valid).unwrap();
value["subject_id"] = "other".into();
assert!(serde_json::from_value::<WorkflowMetadataChange>(value).is_err());
let instance = WorkflowTarget::Instance {
instance_id: "instance".parse().unwrap(),
};
assert_eq!(
serde_json::from_value::<WorkflowTarget>(serde_json::to_value(&instance).unwrap())
.unwrap(),
instance
);
}
}