use std::io;
use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, ScribeError>;
#[derive(Error, Debug)]
pub enum ScribeError {
#[error("I/O error: {message}")]
Io {
message: String,
#[source]
source: io::Error,
},
#[error("Path error: {message} (path: {path:?})")]
Path {
message: String,
path: PathBuf,
#[source]
source: Option<io::Error>,
},
#[error("Git error: {message}")]
Git {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Configuration error: {message}")]
Config {
message: String,
field: Option<String>,
},
#[error("Analysis error: {message} (file: {file:?})")]
Analysis {
message: String,
file: PathBuf,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Scoring error: {message}")]
Scoring {
message: String,
context: Option<String>,
},
#[error("Graph error: {message}")]
Graph {
message: String,
details: Option<String>,
},
#[error("Pattern error: {message} (pattern: {pattern})")]
Pattern {
message: String,
pattern: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Serialization error: {message}")]
Serialization {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Concurrency error: {message}")]
Concurrency {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Resource limit exceeded: {message} (limit: {limit}, actual: {actual})")]
ResourceLimit {
message: String,
limit: u64,
actual: u64,
},
#[error("Invalid operation: {message}")]
InvalidOperation { message: String, operation: String },
#[error("Parse error: {message} (file: {file:?})")]
Parse {
message: String,
file: Option<PathBuf>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Tokenization error: {message}")]
Tokenization {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Internal error: {message}")]
Internal {
message: String,
location: Option<String>,
},
}
impl ScribeError {
pub fn io<S: Into<String>>(message: S, source: io::Error) -> Self {
Self::Io {
message: message.into(),
source,
}
}
pub fn path<S: Into<String>, P: Into<PathBuf>>(message: S, path: P) -> Self {
Self::Path {
message: message.into(),
path: path.into(),
source: None,
}
}
pub fn path_with_source<S: Into<String>, P: Into<PathBuf>>(
message: S,
path: P,
source: io::Error,
) -> Self {
Self::Path {
message: message.into(),
path: path.into(),
source: Some(source),
}
}
pub fn git<S: Into<String>>(message: S) -> Self {
Self::Git {
message: message.into(),
source: None,
}
}
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Config {
message: message.into(),
field: None,
}
}
pub fn config_field<S: Into<String>, F: Into<String>>(message: S, field: F) -> Self {
Self::Config {
message: message.into(),
field: Some(field.into()),
}
}
pub fn analysis<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
Self::Analysis {
message: message.into(),
file: file.into(),
source: None,
}
}
pub fn scoring<S: Into<String>>(message: S) -> Self {
Self::Scoring {
message: message.into(),
context: None,
}
}
pub fn scoring_with_context<S: Into<String>, C: Into<String>>(message: S, context: C) -> Self {
Self::Scoring {
message: message.into(),
context: Some(context.into()),
}
}
pub fn graph<S: Into<String>>(message: S) -> Self {
Self::Graph {
message: message.into(),
details: None,
}
}
pub fn pattern<S: Into<String>, P: Into<String>>(message: S, pattern: P) -> Self {
Self::Pattern {
message: message.into(),
pattern: pattern.into(),
source: None,
}
}
pub fn resource_limit<S: Into<String>>(message: S, limit: u64, actual: u64) -> Self {
Self::ResourceLimit {
message: message.into(),
limit,
actual,
}
}
pub fn invalid_operation<S: Into<String>, O: Into<String>>(message: S, operation: O) -> Self {
Self::InvalidOperation {
message: message.into(),
operation: operation.into(),
}
}
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal {
message: message.into(),
location: None,
}
}
pub fn parse<S: Into<String>>(message: S) -> Self {
Self::Parse {
message: message.into(),
file: None,
source: None,
}
}
pub fn parse_file<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
Self::Parse {
message: message.into(),
file: Some(file.into()),
source: None,
}
}
pub fn parse_with_source<S: Into<String>>(
message: S,
source: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
Self::Parse {
message: message.into(),
file: None,
source: Some(source),
}
}
pub fn tokenization<S: Into<String>>(message: S) -> Self {
Self::Tokenization {
message: message.into(),
source: None,
}
}
pub fn tokenization_with_source<S: Into<String>>(
message: S,
source: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
Self::Tokenization {
message: message.into(),
source: Some(source),
}
}
pub fn internal_with_location<S: Into<String>, L: Into<String>>(
message: S,
location: L,
) -> Self {
Self::Internal {
message: message.into(),
location: Some(location.into()),
}
}
}
impl From<io::Error> for ScribeError {
fn from(error: io::Error) -> Self {
Self::io("I/O operation failed", error)
}
}
impl From<serde_json::Error> for ScribeError {
fn from(error: serde_json::Error) -> Self {
Self::Serialization {
message: "JSON serialization failed".to_string(),
source: Some(Box::new(error)),
}
}
}
impl From<globset::Error> for ScribeError {
fn from(error: globset::Error) -> Self {
Self::Pattern {
message: "Glob pattern compilation failed".to_string(),
pattern: "unknown".to_string(),
source: Some(Box::new(error)),
}
}
}
impl From<ignore::Error> for ScribeError {
fn from(error: ignore::Error) -> Self {
Self::Pattern {
message: "Ignore pattern error".to_string(),
pattern: "unknown".to_string(),
source: Some(Box::new(error)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_error_creation() {
let err = ScribeError::path("Test path error", Path::new("/test/path"));
assert!(err.to_string().contains("Test path error"));
assert!(err.to_string().contains("/test/path"));
}
#[test]
fn test_io_error_conversion() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
let scribe_err = ScribeError::from(io_err);
match scribe_err {
ScribeError::Io { message, .. } => {
assert_eq!(message, "I/O operation failed");
}
_ => panic!("Expected Io error variant"),
}
}
#[test]
fn test_resource_limit_error() {
let err = ScribeError::resource_limit("File too large", 1000, 2000);
let msg = err.to_string();
assert!(msg.contains("limit: 1000"));
assert!(msg.contains("actual: 2000"));
}
}