Skip to main content

opendev_models/
operation.rs

1//! Operation models for tracking actions and results.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use strum::{Display, EnumString};
7
8/// Type of operation.
9#[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/// Status of operation.
20#[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/// Represents a single operation to be performed.
33#[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    /// File path, command, etc.
40    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    /// Create a new pending operation.
65    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    /// Mark operation as executing.
81    pub fn mark_executing(&mut self) {
82        self.status = OperationStatus::Executing;
83        self.started_at = Some(Utc::now());
84    }
85
86    /// Mark operation as successful.
87    pub fn mark_success(&mut self) {
88        self.status = OperationStatus::Success;
89        self.completed_at = Some(Utc::now());
90    }
91
92    /// Mark operation as failed.
93    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/// Result of a file write operation.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct WriteResult {
103    pub success: bool,
104    pub file_path: String,
105    pub created: bool,
106    /// File size in bytes.
107    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    /// True if operation was interrupted.
113    #[serde(default)]
114    pub interrupted: bool,
115}
116
117/// Result of a file edit operation.
118#[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    /// Diff preview for the edit.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub diff: Option<String>,
133    /// True if operation was interrupted.
134    #[serde(default)]
135    pub interrupted: bool,
136}
137
138/// Result of a bash command execution.
139#[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    /// Duration in seconds.
147    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    /// ID if running as background task.
153    #[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;