tldr_cli/commands/patterns/
error.rs1use std::path::PathBuf;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum PatternsError {
12 #[error("file not found: {}", path.display())]
14 FileNotFound { path: PathBuf },
15
16 #[error("function '{function}' not found in {}", file.display())]
18 FunctionNotFound { function: String, file: PathBuf },
19
20 #[error("class '{class_name}' not found in {}", file.display())]
22 ClassNotFound { class_name: String, file: PathBuf },
23
24 #[error("parse error in {}: {message}", file.display())]
26 ParseError { file: PathBuf, message: String },
27
28 #[error("file too large: {} ({bytes} bytes, max {max_bytes} bytes)", path.display())]
30 FileTooLarge {
31 path: PathBuf,
32 bytes: u64,
33 max_bytes: u64,
34 },
35
36 #[error("directory scan limit exceeded: {count} files found, max {max_files}")]
38 TooManyFiles { count: u32, max_files: u32 },
39
40 #[error("analysis depth limit exceeded: depth {depth}, max {max_depth}")]
42 DepthLimitExceeded { depth: u32, max_depth: u32 },
43
44 #[error("analysis timed out after {timeout_secs}s")]
46 Timeout { timeout_secs: u64 },
47
48 #[error("invalid parameter: {message}")]
50 InvalidParameter { message: String },
51
52 #[error("path traversal blocked: {} attempts to escape project root", path.display())]
54 PathTraversal { path: PathBuf },
55
56 #[error("path is not a directory: {}", path.display())]
58 NotADirectory { path: PathBuf },
59
60 #[error("unsupported language: {language}")]
62 UnsupportedLanguage { language: String },
63
64 #[error("no constraints found matching criteria")]
66 NoConstraintsFound,
67
68 #[error("resource issues found: {leaks} leaks, {double_closes} double-closes, {use_after_closes} use-after-close")]
70 IssuesFound {
71 leaks: u32,
72 double_closes: u32,
73 use_after_closes: u32,
74 },
75
76 #[error("IO error: {0}")]
78 Io(#[from] std::io::Error),
79
80 #[error("JSON error: {0}")]
82 Json(#[from] serde_json::Error),
83}
84
85pub type PatternsResult<T> = Result<T, PatternsError>;
87
88impl PatternsError {
89 pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
91 Self::FileNotFound { path: path.into() }
92 }
93
94 pub fn function_not_found(function: impl Into<String>, file: impl Into<PathBuf>) -> Self {
96 Self::FunctionNotFound {
97 function: function.into(),
98 file: file.into(),
99 }
100 }
101
102 pub fn class_not_found(class_name: impl Into<String>, file: impl Into<PathBuf>) -> Self {
104 Self::ClassNotFound {
105 class_name: class_name.into(),
106 file: file.into(),
107 }
108 }
109
110 pub fn parse_error(file: impl Into<PathBuf>, message: impl Into<String>) -> Self {
112 Self::ParseError {
113 file: file.into(),
114 message: message.into(),
115 }
116 }
117
118 pub fn invalid_parameter(message: impl Into<String>) -> Self {
120 Self::InvalidParameter {
121 message: message.into(),
122 }
123 }
124
125 pub fn path_traversal(path: impl Into<PathBuf>) -> Self {
127 Self::PathTraversal { path: path.into() }
128 }
129
130 pub fn file_too_large(path: impl Into<PathBuf>, bytes: u64, max_bytes: u64) -> Self {
132 Self::FileTooLarge {
133 path: path.into(),
134 bytes,
135 max_bytes,
136 }
137 }
138
139 pub fn depth_exceeded(depth: u32, max_depth: u32) -> Self {
141 Self::DepthLimitExceeded { depth, max_depth }
142 }
143
144 pub fn too_many_files(count: u32, max_files: u32) -> Self {
146 Self::TooManyFiles { count, max_files }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_error_file_not_found_message() {
156 let err = PatternsError::file_not_found("/path/to/file.py");
157 let msg = err.to_string();
158 assert!(msg.contains("file not found"));
159 assert!(msg.contains("file.py"));
160 }
161
162 #[test]
163 fn test_error_function_not_found_message() {
164 let err = PatternsError::function_not_found("my_func", "/path/to/file.py");
165 let msg = err.to_string();
166 assert!(msg.contains("my_func"));
167 assert!(msg.contains("not found"));
168 }
169
170 #[test]
171 fn test_error_path_traversal_message() {
172 let err = PatternsError::path_traversal("../etc/passwd");
173 let msg = err.to_string();
174 assert!(msg.contains("path traversal"));
175 assert!(msg.contains("etc/passwd"));
176 }
177
178 #[test]
179 fn test_error_file_too_large_message() {
180 let err = PatternsError::file_too_large("/big/file.py", 20_000_000, 10_000_000);
181 let msg = err.to_string();
182 assert!(msg.contains("too large"));
183 assert!(msg.contains("20000000"));
184 }
185
186 #[test]
187 fn test_error_depth_exceeded_message() {
188 let err = PatternsError::depth_exceeded(150, 100);
189 let msg = err.to_string();
190 assert!(msg.contains("depth"));
191 assert!(msg.contains("150"));
192 assert!(msg.contains("100"));
193 }
194
195 #[test]
196 fn test_error_too_many_files_message() {
197 let err = PatternsError::too_many_files(1500, 1000);
198 let msg = err.to_string();
199 assert!(msg.contains("1500"));
200 assert!(msg.contains("1000"));
201 }
202
203 #[test]
204 fn test_error_parse_error_message() {
205 let err = PatternsError::parse_error("/file.py", "unexpected token");
206 let msg = err.to_string();
207 assert!(msg.contains("parse error"));
208 assert!(msg.contains("unexpected token"));
209 }
210
211 #[test]
212 fn test_error_invalid_parameter_message() {
213 let err = PatternsError::invalid_parameter("value must be positive");
214 let msg = err.to_string();
215 assert!(msg.contains("invalid parameter"));
216 assert!(msg.contains("positive"));
217 }
218}