1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use strum::{Display, EnumString};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, EnumString)]
10#[serde(rename_all = "snake_case")]
11#[strum(serialize_all = "snake_case")]
12pub enum OperationType {
13 FileWrite,
14 FileEdit,
15 FileDelete,
16 BashExecute,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, EnumString)]
21#[serde(rename_all = "lowercase")]
22#[strum(serialize_all = "lowercase")]
23pub enum OperationStatus {
24 Pending,
25 Approved,
26 Executing,
27 Success,
28 Failed,
29 Cancelled,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct Operation {
35 pub id: String,
36 #[serde(rename = "type")]
37 pub op_type: OperationType,
38 pub status: OperationStatus,
39 pub target: String,
41 #[serde(default)]
42 pub parameters: HashMap<String, serde_json::Value>,
43 #[serde(default = "Utc::now", with = "crate::datetime_compat")]
44 pub created_at: DateTime<Utc>,
45 #[serde(
46 default,
47 skip_serializing_if = "Option::is_none",
48 with = "crate::datetime_compat::option"
49 )]
50 pub started_at: Option<DateTime<Utc>>,
51 #[serde(
52 default,
53 skip_serializing_if = "Option::is_none",
54 with = "crate::datetime_compat::option"
55 )]
56 pub completed_at: Option<DateTime<Utc>>,
57 #[serde(default)]
58 pub approved: bool,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub error: Option<String>,
61}
62
63impl Operation {
64 pub fn new(op_type: OperationType, target: String) -> Self {
66 Self {
67 id: Utc::now().format("%Y%m%d%H%M%S%f").to_string(),
68 op_type,
69 status: OperationStatus::Pending,
70 target,
71 parameters: HashMap::new(),
72 created_at: Utc::now(),
73 started_at: None,
74 completed_at: None,
75 approved: false,
76 error: None,
77 }
78 }
79
80 pub fn mark_executing(&mut self) {
82 self.status = OperationStatus::Executing;
83 self.started_at = Some(Utc::now());
84 }
85
86 pub fn mark_success(&mut self) {
88 self.status = OperationStatus::Success;
89 self.completed_at = Some(Utc::now());
90 }
91
92 pub fn mark_failed(&mut self, error: String) {
94 self.status = OperationStatus::Failed;
95 self.completed_at = Some(Utc::now());
96 self.error = Some(error);
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct WriteResult {
103 pub success: bool,
104 pub file_path: String,
105 pub created: bool,
106 pub size: u64,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub error: Option<String>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub operation_id: Option<String>,
112 #[serde(default)]
114 pub interrupted: bool,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct EditResult {
120 pub success: bool,
121 pub file_path: String,
122 pub lines_added: u64,
123 pub lines_removed: u64,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub backup_path: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub error: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub operation_id: Option<String>,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub diff: Option<String>,
133 #[serde(default)]
135 pub interrupted: bool,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct BashResult {
141 pub success: bool,
142 pub command: String,
143 pub exit_code: i32,
144 pub stdout: String,
145 pub stderr: String,
146 pub duration: f64,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub error: Option<String>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub operation_id: Option<String>,
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub background_task_id: Option<String>,
155}
156
157#[cfg(test)]
158#[path = "operation_tests.rs"]
159mod tests;