gnu_sort/
error.rs

1//! Error handling for the sort utility
2
3use std::io;
4use thiserror::Error;
5
6/// Custom error type for sort operations
7#[derive(Error, Debug)]
8pub enum SortError {
9    #[error("I/O error: {0}")]
10    Io(#[from] io::Error),
11
12    #[error("Permission denied: {file}")]
13    PermissionDenied { file: String },
14
15    #[error("No such file or directory: {file}")]
16    FileNotFound { file: String },
17
18    #[error("Is a directory: {file}")]
19    IsDirectory { file: String },
20
21    #[error("Invalid key specification: {spec}")]
22    InvalidKeySpec { spec: String },
23
24    #[error("Invalid field separator: {sep}")]
25    InvalidFieldSeparator { sep: String },
26
27    #[error("Invalid buffer size: {size}")]
28    InvalidBufferSize { size: String },
29
30    #[error("Conflicting sort options: {message}")]
31    ConflictingOptions { message: String },
32
33    #[error("Memory allocation failed")]
34    OutOfMemory,
35
36    #[error("Input is not sorted at line {line}")]
37    NotSorted { line: usize },
38
39    #[error("Merge operation failed: {message}")]
40    MergeFailed { message: String },
41
42    #[error("Thread pool error: {message}")]
43    ThreadPoolError { message: String },
44
45    #[error("UTF-8 encoding error: {0}")]
46    Utf8Error(#[from] std::string::FromUtf8Error),
47
48    #[error("Parse error: {message}")]
49    ParseError { message: String },
50
51    #[error("Internal error: {message}")]
52    Internal { message: String },
53}
54
55impl SortError {
56    /// Returns the appropriate exit code for this error
57    pub fn exit_code(&self) -> i32 {
58        match self {
59            SortError::PermissionDenied { .. }
60            | SortError::FileNotFound { .. }
61            | SortError::IsDirectory { .. }
62            | SortError::Io(_) => crate::SORT_FAILURE,
63
64            SortError::NotSorted { .. } => crate::EXIT_FAILURE,
65
66            _ => crate::EXIT_FAILURE,
67        }
68    }
69
70    /// Create a permission denied error
71    pub fn permission_denied(file: &str) -> Self {
72        SortError::PermissionDenied {
73            file: file.to_string(),
74        }
75    }
76
77    /// Create a file not found error
78    pub fn file_not_found(file: &str) -> Self {
79        SortError::FileNotFound {
80            file: file.to_string(),
81        }
82    }
83
84    /// Create an is directory error
85    pub fn is_directory(file: &str) -> Self {
86        SortError::IsDirectory {
87            file: file.to_string(),
88        }
89    }
90
91    /// Create an invalid key spec error
92    pub fn invalid_key_spec(spec: &str) -> Self {
93        SortError::InvalidKeySpec {
94            spec: spec.to_string(),
95        }
96    }
97
98    /// Create an invalid field separator error
99    pub fn invalid_field_separator(sep: &str) -> Self {
100        SortError::InvalidFieldSeparator {
101            sep: sep.to_string(),
102        }
103    }
104
105    /// Create an invalid buffer size error
106    pub fn invalid_buffer_size(size: &str) -> Self {
107        SortError::InvalidBufferSize {
108            size: size.to_string(),
109        }
110    }
111
112    /// Create a conflicting options error
113    pub fn conflicting_options(message: &str) -> Self {
114        SortError::ConflictingOptions {
115            message: message.to_string(),
116        }
117    }
118
119    /// Create a not sorted error
120    pub fn not_sorted(line: usize) -> Self {
121        SortError::NotSorted { line }
122    }
123
124    /// Create a merge failed error
125    pub fn merge_failed(message: &str) -> Self {
126        SortError::MergeFailed {
127            message: message.to_string(),
128        }
129    }
130
131    /// Create a thread pool error
132    pub fn thread_pool_error(message: &str) -> Self {
133        SortError::ThreadPoolError {
134            message: message.to_string(),
135        }
136    }
137
138    /// Create a parse error
139    pub fn parse_error(message: &str) -> Self {
140        SortError::ParseError {
141            message: message.to_string(),
142        }
143    }
144
145    /// Create an internal error
146    pub fn internal(message: &str) -> Self {
147        SortError::Internal {
148            message: message.to_string(),
149        }
150    }
151}
152
153/// Convert io::Error to SortError with context (removed to avoid conflict with thiserror derive)
154/// Result type for sort operations
155pub type SortResult<T> = Result<T, SortError>;
156
157/// Context trait for adding context to errors
158pub trait SortContext<T> {
159    fn with_context<F>(self, f: F) -> SortResult<T>
160    where
161        F: FnOnce() -> String;
162
163    fn with_file_context(self, filename: &str) -> SortResult<T>;
164}
165
166impl<T> SortContext<T> for SortResult<T> {
167    fn with_context<F>(self, f: F) -> SortResult<T>
168    where
169        F: FnOnce() -> String,
170    {
171        self.map_err(|err| match err {
172            SortError::Io(io_err) => SortError::Io(io::Error::new(
173                io_err.kind(),
174                format!("{}: {}", f(), io_err),
175            )),
176            other => other,
177        })
178    }
179
180    fn with_file_context(self, filename: &str) -> SortResult<T> {
181        self.map_err(|err| match err {
182            SortError::Io(io_err) => match io_err.kind() {
183                io::ErrorKind::PermissionDenied => SortError::permission_denied(filename),
184                io::ErrorKind::NotFound => SortError::file_not_found(filename),
185                _ => SortError::Io(io::Error::new(
186                    io_err.kind(),
187                    format!("{filename}: {io_err}"),
188                )),
189            },
190            other => other,
191        })
192    }
193}
194
195impl<T> SortContext<T> for Result<T, io::Error> {
196    fn with_context<F>(self, f: F) -> SortResult<T>
197    where
198        F: FnOnce() -> String,
199    {
200        self.map_err(|io_err| {
201            SortError::Io(io::Error::new(io_err.kind(), format!("{}: {io_err}", f())))
202        })
203    }
204
205    fn with_file_context(self, filename: &str) -> SortResult<T> {
206        self.map_err(|io_err| match io_err.kind() {
207            io::ErrorKind::PermissionDenied => SortError::permission_denied(filename),
208            io::ErrorKind::NotFound => SortError::file_not_found(filename),
209            _ => SortError::Io(io::Error::new(
210                io_err.kind(),
211                format!("{filename}: {io_err}"),
212            )),
213        })
214    }
215}