use crate::domain_types::{ModuleName, SourcePath};
use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DissolveError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Parse error in {file}: {message}")]
Parse {
file: SourcePath,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Type introspection error: {0}")]
TypeIntrospection(#[from] TypeIntrospectionError),
#[error("Migration error in {module}: {message}")]
Migration { module: ModuleName, message: String },
#[error("Configuration error: {0}")]
Config(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Feature not implemented: {0}")]
NotImplemented(String),
#[error("Internal error: {0}")]
Internal(String),
}
#[derive(Debug, Error)]
pub enum TypeIntrospectionError {
#[error("Pyright query failed: {0}")]
PyrightError(String),
#[error("Mypy query failed: {0}")]
MypyError(String),
#[error("Failed to determine position in source: {0}")]
PositionError(String),
#[error("No type introspection client available")]
NoClientAvailable,
#[error("LSP communication error: {0}")]
LspError(String),
#[error("Timeout waiting for LSP response after {seconds}s")]
Timeout { seconds: u64 },
}
#[derive(Debug, Error)]
pub enum CollectorError {
#[error("Failed to parse decorator in {file} at line {line}: {message}")]
DecoratorParse {
file: SourcePath,
line: usize,
message: String,
},
#[error("Invalid replacement expression: {expression}")]
InvalidReplacement { expression: String },
#[error("Missing required parameter: {parameter}")]
MissingParameter { parameter: String },
}
pub type Result<T, E = DissolveError> = std::result::Result<T, E>;
pub type TypeResult<T> = std::result::Result<T, TypeIntrospectionError>;
pub type CollectorResult<T> = std::result::Result<T, CollectorError>;
impl DissolveError {
pub fn parse_error(
file: SourcePath,
message: impl Into<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Parse {
file,
message: message.into(),
source,
}
}
pub fn migration_error(module: ModuleName, message: impl Into<String>) -> Self {
Self::Migration {
module,
message: message.into(),
}
}
pub fn config_error(message: impl Into<String>) -> Self {
Self::Config(message.into())
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self::InvalidInput(message.into())
}
pub fn not_implemented(feature: impl Into<String>) -> Self {
Self::NotImplemented(feature.into())
}
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal(message.into())
}
}
impl TypeIntrospectionError {
pub fn timeout(seconds: u64) -> Self {
Self::Timeout { seconds }
}
pub fn lsp_error(message: impl Into<String>) -> Self {
Self::LspError(message.into())
}
pub fn position_error(message: impl Into<String>) -> Self {
Self::PositionError(message.into())
}
}
impl CollectorError {
pub fn decorator_parse(file: SourcePath, line: usize, message: impl Into<String>) -> Self {
Self::DecoratorParse {
file,
line,
message: message.into(),
}
}
pub fn invalid_replacement(expression: impl Into<String>) -> Self {
Self::InvalidReplacement {
expression: expression.into(),
}
}
pub fn missing_parameter(parameter: impl Into<String>) -> Self {
Self::MissingParameter {
parameter: parameter.into(),
}
}
}
pub trait AnyhowExt<T> {
fn with_context(self, f: impl FnOnce() -> DissolveError) -> Result<T>;
}
impl<T> AnyhowExt<T> for anyhow::Result<T> {
fn with_context(self, f: impl FnOnce() -> DissolveError) -> Result<T> {
self.map_err(|_| f())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let file = SourcePath::new("test.py");
let module = ModuleName::new("test_module");
let parse_err = DissolveError::parse_error(file.clone(), "syntax error", None);
assert!(matches!(parse_err, DissolveError::Parse { .. }));
let migration_err = DissolveError::migration_error(module, "failed to migrate");
assert!(matches!(migration_err, DissolveError::Migration { .. }));
}
#[test]
fn test_error_display() {
let err = TypeIntrospectionError::timeout(30);
assert_eq!(
err.to_string(),
"Timeout waiting for LSP response after 30s"
);
}
}