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