1use crate::error::Result;
9use async_trait::async_trait;
10use serde::{Deserialize, Serialize};
11use std::fmt::Debug;
12
13#[async_trait]
51#[allow(dead_code)]
52pub trait CommandService: Send + Sync {
53 type Output: Serialize + Debug + Send;
57
58 async fn execute(&self) -> Result<Self::Output>;
63
64 fn to_json(&self, output: &Self::Output) -> Result<String> {
68 serde_json::to_string_pretty(output).map_err(|e| {
69 crate::error::CliError::Serialization(format!("Failed to serialize output: {}", e))
70 })
71 }
72
73 async fn execute_with_json(&self, json: bool) -> Result<String> {
77 let output = self.execute().await?;
78
79 if json {
80 self.to_json(&output)
81 } else {
82 Ok(format!("{:?}", output))
84 }
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
92#[allow(dead_code)]
93pub struct CommandMetadata {
94 pub command: String,
96 pub timestamp: String,
98 pub success: bool,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub error: Option<String>,
103}
104
105#[allow(dead_code)]
106impl CommandMetadata {
107 pub fn new(command: impl Into<String>, success: bool) -> Self {
109 Self {
110 command: command.into(),
111 timestamp: chrono::Utc::now().to_rfc3339(),
112 success,
113 error: None,
114 }
115 }
116
117 pub fn with_error(command: impl Into<String>, error: impl Into<String>) -> Self {
119 Self {
120 command: command.into(),
121 timestamp: chrono::Utc::now().to_rfc3339(),
122 success: false,
123 error: Some(error.into()),
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[derive(Debug, Serialize)]
133 struct TestOutput {
134 message: String,
135 }
136
137 struct TestCommand;
138
139 #[async_trait]
140 impl CommandService for TestCommand {
141 type Output = TestOutput;
142
143 async fn execute(&self) -> Result<Self::Output> {
144 Ok(TestOutput {
145 message: "test".to_string(),
146 })
147 }
148 }
149
150 #[tokio::test]
151 async fn test_command_service_execute() {
152 let cmd = TestCommand;
153 let output = cmd.execute().await.unwrap();
154 assert_eq!(output.message, "test");
155 }
156
157 #[tokio::test]
158 async fn test_command_service_to_json() {
159 let cmd = TestCommand;
160 let output = cmd.execute().await.unwrap();
161 let json = cmd.to_json(&output).unwrap();
162 assert!(json.contains("\"message\""));
163 assert!(json.contains("\"test\""));
164 }
165
166 #[tokio::test]
167 async fn test_command_service_execute_with_json() {
168 let cmd = TestCommand;
169 let json_output = cmd.execute_with_json(true).await.unwrap();
170 assert!(json_output.contains("\"message\""));
171 }
172
173 #[test]
174 fn test_command_metadata_creation() {
175 let metadata = CommandMetadata::new("test", true);
176 assert_eq!(metadata.command, "test");
177 assert!(metadata.success);
178 assert!(metadata.error.is_none());
179 }
180
181 #[test]
182 fn test_command_metadata_with_error() {
183 let metadata = CommandMetadata::with_error("test", "test error");
184 assert_eq!(metadata.command, "test");
185 assert!(!metadata.success);
186 assert_eq!(metadata.error, Some("test error".to_string()));
187 }
188}