1use crate::domain_types::{ModuleName, SourcePath};
18use std::io;
19use thiserror::Error;
20
21#[derive(Debug, Error)]
23pub enum DissolveError {
24 #[error("IO error: {0}")]
25 Io(#[from] io::Error),
26
27 #[error("Parse error in {file}: {message}")]
28 Parse {
29 file: SourcePath,
30 message: String,
31 #[source]
32 source: Option<Box<dyn std::error::Error + Send + Sync>>,
33 },
34
35 #[error("Type introspection error: {0}")]
36 TypeIntrospection(#[from] TypeIntrospectionError),
37
38 #[error("Migration error in {module}: {message}")]
39 Migration { module: ModuleName, message: String },
40
41 #[error("Configuration error: {0}")]
42 Config(String),
43
44 #[error("Invalid input: {0}")]
45 InvalidInput(String),
46
47 #[error("Feature not implemented: {0}")]
48 NotImplemented(String),
49
50 #[error("Internal error: {0}")]
51 Internal(String),
52}
53
54#[derive(Debug, Error)]
55pub enum TypeIntrospectionError {
56 #[error("Pyright query failed: {0}")]
57 PyrightError(String),
58
59 #[error("Mypy query failed: {0}")]
60 MypyError(String),
61
62 #[error("Failed to determine position in source: {0}")]
63 PositionError(String),
64
65 #[error("No type introspection client available")]
66 NoClientAvailable,
67
68 #[error("LSP communication error: {0}")]
69 LspError(String),
70
71 #[error("Timeout waiting for LSP response after {seconds}s")]
72 Timeout { seconds: u64 },
73}
74
75#[derive(Debug, Error)]
76pub enum CollectorError {
77 #[error("Failed to parse decorator in {file} at line {line}: {message}")]
78 DecoratorParse {
79 file: SourcePath,
80 line: usize,
81 message: String,
82 },
83
84 #[error("Invalid replacement expression: {expression}")]
85 InvalidReplacement { expression: String },
86
87 #[error("Missing required parameter: {parameter}")]
88 MissingParameter { parameter: String },
89}
90
91pub type Result<T, E = DissolveError> = std::result::Result<T, E>;
93
94pub type TypeResult<T> = std::result::Result<T, TypeIntrospectionError>;
96
97pub type CollectorResult<T> = std::result::Result<T, CollectorError>;
99
100impl DissolveError {
101 pub fn parse_error(
103 file: SourcePath,
104 message: impl Into<String>,
105 source: Option<Box<dyn std::error::Error + Send + Sync>>,
106 ) -> Self {
107 Self::Parse {
108 file,
109 message: message.into(),
110 source,
111 }
112 }
113
114 pub fn migration_error(module: ModuleName, message: impl Into<String>) -> Self {
116 Self::Migration {
117 module,
118 message: message.into(),
119 }
120 }
121
122 pub fn config_error(message: impl Into<String>) -> Self {
124 Self::Config(message.into())
125 }
126
127 pub fn invalid_input(message: impl Into<String>) -> Self {
129 Self::InvalidInput(message.into())
130 }
131
132 pub fn not_implemented(feature: impl Into<String>) -> Self {
134 Self::NotImplemented(feature.into())
135 }
136
137 pub fn internal(message: impl Into<String>) -> Self {
139 Self::Internal(message.into())
140 }
141}
142
143impl TypeIntrospectionError {
144 pub fn timeout(seconds: u64) -> Self {
146 Self::Timeout { seconds }
147 }
148
149 pub fn lsp_error(message: impl Into<String>) -> Self {
151 Self::LspError(message.into())
152 }
153
154 pub fn position_error(message: impl Into<String>) -> Self {
156 Self::PositionError(message.into())
157 }
158}
159
160impl CollectorError {
161 pub fn decorator_parse(file: SourcePath, line: usize, message: impl Into<String>) -> Self {
163 Self::DecoratorParse {
164 file,
165 line,
166 message: message.into(),
167 }
168 }
169
170 pub fn invalid_replacement(expression: impl Into<String>) -> Self {
172 Self::InvalidReplacement {
173 expression: expression.into(),
174 }
175 }
176
177 pub fn missing_parameter(parameter: impl Into<String>) -> Self {
179 Self::MissingParameter {
180 parameter: parameter.into(),
181 }
182 }
183}
184
185pub trait AnyhowExt<T> {
187 fn with_context(self, f: impl FnOnce() -> DissolveError) -> Result<T>;
188}
189
190impl<T> AnyhowExt<T> for anyhow::Result<T> {
191 fn with_context(self, f: impl FnOnce() -> DissolveError) -> Result<T> {
192 self.map_err(|_| f())
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_error_creation() {
202 let file = SourcePath::new("test.py");
203 let module = ModuleName::new("test_module");
204
205 let parse_err = DissolveError::parse_error(file.clone(), "syntax error", None);
206 assert!(matches!(parse_err, DissolveError::Parse { .. }));
207
208 let migration_err = DissolveError::migration_error(module, "failed to migrate");
209 assert!(matches!(migration_err, DissolveError::Migration { .. }));
210 }
211
212 #[test]
213 fn test_error_display() {
214 let err = TypeIntrospectionError::timeout(30);
215 assert_eq!(
216 err.to_string(),
217 "Timeout waiting for LSP response after 30s"
218 );
219 }
220}