返回 CodeWhale
work.rs
1 use serde::{Deserialize, Serialize};
2 use serde_json::Value;
3
4 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5 #[serde(rename_all = "snake_case")]
6 pub enum WorkDomain {
7 /// Durable delegated/background unit.
8 Task,
9 /// Active checklist inside the current turn or lane.
10 Todo,
11 /// Strategy owned through Plan mode.
12 Plan,
13 /// Ordered durable execution.
14 Workflow,
15 }
16
17 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18 #[serde(rename_all = "snake_case")]
19 pub enum WorkLifecycle {
20 Pending,
21 Active,
22 Waiting,
23 Completed,
24 Failed,
25 Canceled,
26 }
27
28 /// Common envelope only. The payload remains owned by its domain so Tasks,
29 /// To-do, Plan, and Workflow cannot collapse into a generic tracker.
30 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31 pub struct WorkEventEnvelope {
32 pub schema_version: u32,
33 pub event_id: String,
34 pub domain: WorkDomain,
35 pub object_id: String,
36 pub lifecycle: WorkLifecycle,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub parent_id: Option<String>,
39 pub payload: Value,
40 }
41
42 impl WorkEventEnvelope {
43 pub fn validate(&self) -> Result<(), String> {
44 if self.schema_version == 0 {
45 return Err("work event schema version cannot be zero".to_string());
46 }
47 if self.event_id.trim().is_empty() || self.object_id.trim().is_empty() {
48 return Err("work event and object IDs cannot be empty".to_string());
49 }
50 if !self.payload.is_object() {
51 return Err("work event payload must remain a typed object".to_string());
52 }
53 Ok(())
54 }
55 }
56
57 #[cfg(test)]
58 mod tests {
59 use super::*;
60
61 #[test]
62 fn domains_share_an_envelope_without_losing_meaning() {
63 let task = WorkEventEnvelope {
64 schema_version: 1,
65 event_id: "event-task".to_string(),
66 domain: WorkDomain::Task,
67 object_id: "task-1".to_string(),
68 lifecycle: WorkLifecycle::Active,
69 parent_id: None,
70 payload: serde_json::json!({"worker_id": "worker-1"}),
71 };
72 let todo = WorkEventEnvelope {
73 domain: WorkDomain::Todo,
74 event_id: "event-todo".to_string(),
75 object_id: "todo-1".to_string(),
76 payload: serde_json::json!({"checked": false}),
77 ..task.clone()
78 };
79 assert_ne!(task.domain, todo.domain);
80 assert!(task.payload.get("worker_id").is_some());
81 assert!(todo.payload.get("checked").is_some());
82 assert!(task.validate().is_ok());
83 assert!(todo.validate().is_ok());
84 }
85 }
86
86 lines RUST