1use thiserror::Error;
2
3#[derive(Error, Debug)]
5pub enum CliError {
6 #[error("Actor '{actor_id}' not found")]
8 ActorNotFound { actor_id: String },
9
10 #[error("Actor '{actor_id}' failed to start: {reason}")]
11 ActorStartFailed { actor_id: String, reason: String },
12
13 #[error("Actor '{actor_id}' is not running")]
14 ActorNotRunning { actor_id: String },
15
16 #[error("Actor '{actor_id}' had an error: {reason}")]
17 ActorError { actor_id: String, reason: String },
18
19 #[error("Invalid project directory: {path}")]
21 InvalidProjectDirectory { path: String },
22
23 #[error("Build failed: {output}")]
24 BuildFailed { output: String },
25
26 #[error("Missing required tool: {tool}. Please install it with: {install_command}")]
27 MissingTool {
28 tool: String,
29 install_command: String,
30 },
31
32 #[error("Invalid manifest file: {reason}")]
34 InvalidManifest { reason: String },
35
36 #[error("Configuration error: {reason}")]
37 ConfigError { reason: String },
38
39 #[error("Template '{template}' not found. Available templates: {available}")]
41 TemplateNotFound { template: String, available: String },
42
43 #[error("Template error: {reason}")]
44 TemplateError { reason: String },
45
46 #[error("File operation failed: {operation} on {path}: {source}")]
48 FileOperationFailed {
49 operation: String,
50 path: String,
51 #[source]
52 source: std::io::Error,
53 },
54
55 #[error("Permission denied: {operation} on {path}")]
56 PermissionDenied { operation: String, path: String },
57
58 #[error("Invalid input: {field} = '{value}'. {suggestion}")]
60 InvalidInput {
61 field: String,
62 value: String,
63 suggestion: String,
64 },
65
66 #[error("Validation failed: {reason}")]
67 ValidationError { reason: String },
68
69 #[error("Server error: {message}")]
71 ServerError { message: String },
72
73 #[error("Operation was cancelled by user")]
75 OperationCancelled,
76
77 #[error("Internal error: {0}")]
79 Internal(#[from] anyhow::Error),
80
81 #[error("Serialization error: {0}")]
82 Serialization(#[from] serde_json::Error),
83
84 #[error("I/O error: {0}")]
85 Io(#[from] std::io::Error),
86}
87
88impl CliError {
89 pub fn actor_not_found(actor_id: impl Into<String>) -> Self {
91 Self::ActorNotFound {
92 actor_id: actor_id.into(),
93 }
94 }
95
96 pub fn build_failed(output: impl Into<String>) -> Self {
98 Self::BuildFailed {
99 output: output.into(),
100 }
101 }
102
103 pub fn invalid_manifest(reason: impl Into<String>) -> Self {
105 Self::InvalidManifest {
106 reason: reason.into(),
107 }
108 }
109
110 pub fn template_not_found(template: impl Into<String>, available: Vec<String>) -> Self {
112 Self::TemplateNotFound {
113 template: template.into(),
114 available: available.join(", "),
115 }
116 }
117
118 pub fn file_operation_failed(
120 operation: impl Into<String>,
121 path: impl Into<String>,
122 source: std::io::Error,
123 ) -> Self {
124 Self::FileOperationFailed {
125 operation: operation.into(),
126 path: path.into(),
127 source,
128 }
129 }
130
131 pub fn invalid_input(
133 field: impl Into<String>,
134 value: impl Into<String>,
135 suggestion: impl Into<String>,
136 ) -> Self {
137 Self::InvalidInput {
138 field: field.into(),
139 value: value.into(),
140 suggestion: suggestion.into(),
141 }
142 }
143
144 pub fn server_error(message: impl Into<String>) -> Self {
146 Self::ServerError {
147 message: message.into(),
148 }
149 }
150
151 pub fn user_message(&self) -> String {
153 match self {
154 Self::ActorNotFound { actor_id } => {
155 format!(
156 "Actor '{}' was not found.\n\n\
157 Possible solutions:\n\
158 • Check the actor ID is correct\n\
159 • Start the actor with: theater spawn <manifest>",
160 actor_id
161 )
162 }
163 Self::BuildFailed { output } => {
164 format!(
165 "Build failed.\n\n\
166 Build output:\n{}\n\n\
167 Possible solutions:\n\
168 • Check for compilation errors in your Rust code\n\
169 • Ensure all dependencies are available\n\
170 • Try a clean build with: theater build --clean",
171 output
172 )
173 }
174 Self::MissingTool {
175 tool,
176 install_command,
177 } => {
178 format!(
179 "Required tool '{}' is not installed.\n\n\
180 Install it with:\n {}",
181 tool, install_command
182 )
183 }
184 Self::TemplateNotFound {
185 template,
186 available,
187 } => {
188 format!(
189 "Template '{}' was not found.\n\n\
190 Available templates: {}\n\n\
191 Use one of the available templates or check your template configuration.",
192 template, available
193 )
194 }
195 Self::InvalidInput {
196 field,
197 value,
198 suggestion,
199 } => {
200 format!("Invalid {}: '{}'\n\n{}", field, value, suggestion)
201 }
202 _ => self.to_string(),
203 }
204 }
205
206 pub fn is_retryable(&self) -> bool {
208 matches!(self, Self::ServerError { .. })
209 }
210
211 pub fn category(&self) -> &'static str {
213 match self {
214 Self::ActorNotFound { .. }
215 | Self::ActorStartFailed { .. }
216 | Self::ActorNotRunning { .. }
217 | Self::ActorError { .. } => "actor",
218 Self::InvalidProjectDirectory { .. }
219 | Self::BuildFailed { .. }
220 | Self::MissingTool { .. } => "build",
221 Self::InvalidManifest { .. } | Self::ConfigError { .. } => "config",
222 Self::TemplateNotFound { .. } | Self::TemplateError { .. } => "template",
223 Self::FileOperationFailed { .. } | Self::PermissionDenied { .. } => "filesystem",
224 Self::InvalidInput { .. } | Self::ValidationError { .. } => "validation",
225 Self::ServerError { .. } => "server",
226 Self::OperationCancelled => "cancellation",
227 Self::Internal(_) | Self::Serialization(_) | Self::Io(_) => "internal",
228 }
229 }
230}
231
232pub type CliResult<T> = Result<T, CliError>;