1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
6pub enum SearchError {
7 #[error("No translation files found containing '{text}'.\n\nSearched in: {searched_paths}\n\nTip: Check your project structure or verify translation files exist")]
9 NoTranslationFiles {
10 text: String,
11 searched_paths: String,
12 },
13
14 #[error(
16 "Failed to parse YAML file {file}:\n{reason}\n\nTip: Verify the YAML syntax is correct"
17 )]
18 YamlParseError { file: PathBuf, reason: String },
19
20 #[error(
22 "Failed to parse JSON file {file}:\n{reason}\n\nTip: Verify the JSON syntax is correct"
23 )]
24 JsonParseError { file: PathBuf, reason: String },
25
26 #[error("Translation key '{key}' found in {file} but no code references detected.\n\nTip: Check if the key is actually used in the codebase")]
28 NoCodeReferences { key: String, file: PathBuf },
29
30 #[error("IO error: {0}")]
32 Io(#[from] std::io::Error),
33
34 #[error("Failed to execute ripgrep command: {0}")]
36 RipgrepExecutionFailed(String),
37
38 #[error("ripgrep output is not valid UTF-8: {0}")]
40 InvalidUtf8(#[from] std::string::FromUtf8Error),
41
42 #[error("Failed to parse file path: {0}")]
44 InvalidPath(String),
45
46 #[error("{0}")]
48 Generic(String),
49}
50
51impl SearchError {
52 pub fn no_translation_files(text: impl Into<String>) -> Self {
54 Self::NoTranslationFiles {
55 text: text.into(),
56 searched_paths: "config/locales, src/i18n, locales, i18n".to_string(),
57 }
58 }
59
60 pub fn no_translation_files_with_paths(
62 text: impl Into<String>,
63 paths: impl Into<String>,
64 ) -> Self {
65 Self::NoTranslationFiles {
66 text: text.into(),
67 searched_paths: paths.into(),
68 }
69 }
70
71 pub fn yaml_parse_error(file: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
73 Self::YamlParseError {
74 file: file.into(),
75 reason: reason.into(),
76 }
77 }
78
79 pub fn json_parse_error(file: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
81 Self::JsonParseError {
82 file: file.into(),
83 reason: reason.into(),
84 }
85 }
86
87 pub fn no_code_references(key: impl Into<String>, file: impl Into<PathBuf>) -> Self {
89 Self::NoCodeReferences {
90 key: key.into(),
91 file: file.into(),
92 }
93 }
94}
95
96pub type Result<T> = std::result::Result<T, SearchError>;
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_no_translation_files_error() {
105 let err = SearchError::no_translation_files("add new");
106 let msg = err.to_string();
107 assert!(msg.contains("add new"));
108 assert!(msg.contains("config/locales"));
109 assert!(msg.contains("Tip:"));
110 }
111
112 #[test]
113 fn test_no_translation_files_with_custom_paths() {
114 let err =
115 SearchError::no_translation_files_with_paths("test", "custom/path1, custom/path2");
116 let msg = err.to_string();
117 assert!(msg.contains("test"));
118 assert!(msg.contains("custom/path1"));
119 assert!(msg.contains("custom/path2"));
120 }
121
122 #[test]
123 fn test_yaml_parse_error() {
124 let err = SearchError::yaml_parse_error("config/en.yml", "unexpected character");
125 let msg = err.to_string();
126 assert!(msg.contains("config/en.yml"));
127 assert!(msg.contains("unexpected character"));
128 assert!(msg.contains("YAML syntax"));
129 }
130
131 #[test]
132 fn test_no_code_references_error() {
133 let err = SearchError::no_code_references("invoice.labels.add_new", "config/en.yml");
134 let msg = err.to_string();
135 assert!(msg.contains("invoice.labels.add_new"));
136 assert!(msg.contains("config/en.yml"));
137 assert!(msg.contains("Tip:"));
138 }
139
140 #[test]
141 fn test_io_error_conversion() {
142 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
143 let search_err: SearchError = io_err.into();
144 let msg = search_err.to_string();
145 assert!(msg.contains("IO error"));
146 assert!(msg.contains("file not found"));
147 }
148
149 #[test]
150 fn test_ripgrep_execution_failed() {
151 let err = SearchError::RipgrepExecutionFailed("command failed".to_string());
152 let msg = err.to_string();
153 assert!(msg.contains("Failed to execute ripgrep"));
154 assert!(msg.contains("command failed"));
155 }
156}