use serde::{Deserialize, Serialize};
use std::fmt;
use std::error::Error as StdError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TuskError {
ParseError {
line: usize,
column: usize,
message: String,
context: String,
suggestion: Option<String>,
},
TypeError {
expected: String,
found: String,
context: String,
},
VariableError {
variable: String,
message: String,
available_vars: Vec<String>,
},
FileError {
path: String,
operation: String,
cause: String,
},
ValidationError {
field: String,
value: String,
rule: String,
message: String,
},
SerializationError {
format: String,
message: String,
},
ConfigError {
section: String,
message: String,
details: Option<String>,
},
Generic {
message: String,
context: Option<String>,
code: Option<String>,
},
}
impl TuskError {
pub fn parse_error(line: usize, message: impl Into<String>) -> Self {
Self::ParseError {
line,
column: 0,
message: message.into(),
context: String::new(),
suggestion: None,
}
}
pub fn parse_error_with_context(
line: usize,
column: usize,
message: impl Into<String>,
context: impl Into<String>,
) -> Self {
Self::ParseError {
line,
column,
message: message.into(),
context: context.into(),
suggestion: None,
}
}
pub fn type_error(expected: impl Into<String>, found: impl Into<String>) -> Self {
Self::TypeError {
expected: expected.into(),
found: found.into(),
context: String::new(),
}
}
pub fn variable_error(variable: impl Into<String>, message: impl Into<String>) -> Self {
Self::VariableError {
variable: variable.into(),
message: message.into(),
available_vars: Vec::new(),
}
}
pub fn file_error(path: impl Into<String>, operation: impl Into<String>, cause: impl Into<String>) -> Self {
Self::FileError {
path: path.into(),
operation: operation.into(),
cause: cause.into(),
}
}
pub fn validation_error(
field: impl Into<String>,
value: impl Into<String>,
rule: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::ValidationError {
field: field.into(),
value: value.into(),
rule: rule.into(),
message: message.into(),
}
}
pub fn error_code(&self) -> &str {
match self {
TuskError::ParseError { .. } => "PARSE_ERROR",
TuskError::TypeError { .. } => "TYPE_ERROR",
TuskError::VariableError { .. } => "VARIABLE_ERROR",
TuskError::FileError { .. } => "FILE_ERROR",
TuskError::ValidationError { .. } => "VALIDATION_ERROR",
TuskError::SerializationError { .. } => "SERIALIZATION_ERROR",
TuskError::ConfigError { .. } => "CONFIG_ERROR",
TuskError::Generic { .. } => "GENERIC_ERROR",
}
}
pub fn debug_info(&self) -> String {
match self {
TuskError::ParseError { line, column, message, context, suggestion } => {
let mut info = format!("Parse error at line {}, column {}: {}", line, column, message);
if !context.is_empty() {
info.push_str(&format!("\nContext: {}", context));
}
if let Some(suggestion) = suggestion {
info.push_str(&format!("\nSuggestion: {}", suggestion));
}
info
}
TuskError::TypeError { expected, found, context } => {
let mut info = format!("Type error: expected {}, found {}", expected, found);
if !context.is_empty() {
info.push_str(&format!("\nContext: {}", context));
}
info
}
TuskError::VariableError { variable, message, available_vars } => {
let mut info = format!("Variable error for '{}': {}", variable, message);
if !available_vars.is_empty() {
info.push_str(&format!("\nAvailable variables: {}", available_vars.join(", ")));
}
info
}
TuskError::FileError { path, operation, cause } => {
format!("File error during {} on '{}': {}", operation, path, cause)
}
TuskError::ValidationError { field, value, rule, message } => {
format!("Validation error for field '{}' with value '{}' (rule: {}): {}", field, value, rule, message)
}
TuskError::SerializationError { format, message } => {
format!("Serialization error for format '{}': {}", format, message)
}
TuskError::ConfigError { section, message, details } => {
let mut info = format!("Configuration error in section '{}': {}", section, message);
if let Some(details) = details {
info.push_str(&format!("\nDetails: {}", details));
}
info
}
TuskError::Generic { message, context, code } => {
let mut info = format!("Generic error: {}", message);
if let Some(context) = context {
info.push_str(&format!("\nContext: {}", context));
}
if let Some(code) = code {
info.push_str(&format!("\nCode: {}", code));
}
info
}
}
}
}
impl fmt::Display for TuskError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TuskError::ParseError { line, column, message, .. } => {
write!(f, "Parse error at line {}, column {}: {}", line, column, message)
}
TuskError::TypeError { expected, found, .. } => {
write!(f, "Type error: expected {}, found {}", expected, found)
}
TuskError::VariableError { variable, message, .. } => {
write!(f, "Variable error for '{}': {}", variable, message)
}
TuskError::FileError { path, operation, cause } => {
write!(f, "File error during {} on '{}': {}", operation, path, cause)
}
TuskError::ValidationError { field, message, .. } => {
write!(f, "Validation error for field '{}': {}", field, message)
}
TuskError::SerializationError { format, message } => {
write!(f, "Serialization error for format '{}': {}", format, message)
}
TuskError::ConfigError { section, message, .. } => {
write!(f, "Configuration error in section '{}': {}", section, message)
}
TuskError::Generic { message, .. } => {
write!(f, "Error: {}", message)
}
}
}
}
impl StdError for TuskError {}
impl From<serde_json::Error> for TuskError {
fn from(err: serde_json::Error) -> Self {
TuskError::SerializationError {
format: "JSON".to_string(),
message: err.to_string(),
}
}
}
impl From<serde_yaml::Error> for TuskError {
fn from(err: serde_yaml::Error) -> Self {
TuskError::SerializationError {
format: "YAML".to_string(),
message: err.to_string(),
}
}
}
impl From<std::io::Error> for TuskError {
fn from(err: std::io::Error) -> Self {
TuskError::FileError {
path: "unknown".to_string(),
operation: "io".to_string(),
cause: err.to_string(),
}
}
}
pub type TuskResult<T> = Result<T, TuskError>;
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub file_path: Option<String>,
pub line_number: Option<usize>,
pub column_number: Option<usize>,
pub source_line: Option<String>,
pub stack_trace: Vec<String>,
}
impl ErrorContext {
pub fn new() -> Self {
Self {
file_path: None,
line_number: None,
column_number: None,
source_line: None,
stack_trace: Vec::new(),
}
}
pub fn with_file(mut self, path: impl Into<String>) -> Self {
self.file_path = Some(path.into());
self
}
pub fn with_location(mut self, line: usize, column: usize) -> Self {
self.line_number = Some(line);
self.column_number = Some(column);
self
}
pub fn with_source_line(mut self, line: impl Into<String>) -> Self {
self.source_line = Some(line.into());
self
}
pub fn add_stack_frame(mut self, frame: impl Into<String>) -> Self {
self.stack_trace.push(frame.into());
self
}
}
impl Default for ErrorContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
pub enum TuskTskError {
#[error("Parsing error: {0}")]
Parsing(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Platform integration error: {0}")]
Platform(String),
#[error("Platform not found: {0}")]
PlatformNotFound(String),
#[error("Invalid arguments: {0}")]
InvalidArguments(String),
#[error("Configuration error: {0}")]
Config(String),
}