Skip to main content

theater_cli/
error.rs

1use thiserror::Error;
2
3/// Main error type for the Theater CLI
4#[derive(Error, Debug)]
5pub enum CliError {
6    /// Actor-related errors
7    #[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    /// Project and build errors
20    #[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    /// Manifest and configuration errors
33    #[error("Invalid manifest file: {reason}")]
34    InvalidManifest { reason: String },
35
36    #[error("Configuration error: {reason}")]
37    ConfigError { reason: String },
38
39    /// Template errors
40    #[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    /// I/O and filesystem errors
47    #[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    /// Validation errors
59    #[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    /// Server/runtime errors
70    #[error("Server error: {message}")]
71    ServerError { message: String },
72
73    /// Cancellation errors
74    #[error("Operation was cancelled by user")]
75    OperationCancelled,
76
77    /// Generic wrapper for other errors
78    #[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    /// Create an actor not found error
90    pub fn actor_not_found(actor_id: impl Into<String>) -> Self {
91        Self::ActorNotFound {
92            actor_id: actor_id.into(),
93        }
94    }
95
96    /// Create a build failed error
97    pub fn build_failed(output: impl Into<String>) -> Self {
98        Self::BuildFailed {
99            output: output.into(),
100        }
101    }
102
103    /// Create an invalid manifest error
104    pub fn invalid_manifest(reason: impl Into<String>) -> Self {
105        Self::InvalidManifest {
106            reason: reason.into(),
107        }
108    }
109
110    /// Create a template not found error
111    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    /// Create a file operation failed error
119    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    /// Create an invalid input error with helpful suggestions
132    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    /// Create a server/runtime error
145    pub fn server_error(message: impl Into<String>) -> Self {
146        Self::ServerError {
147            message: message.into(),
148        }
149    }
150
151    /// Get a user-friendly error message with potential solutions
152    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 start <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    /// Check if this error suggests the user should retry
207    pub fn is_retryable(&self) -> bool {
208        matches!(self, Self::ServerError { .. })
209    }
210
211    /// Get the error category for metrics/logging
212    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
232/// Result type alias for CLI operations
233pub type CliResult<T> = Result<T, CliError>;