1#[cfg(feature = "syn")]
7use std::any::Any;
8use std::fmt::{Debug, Display};
9
10pub struct TestResult<T, E> {
12 inner: Result<T, E>,
13 context: Option<String>,
14 source: Option<String>,
15}
16
17impl<T: Debug, E: Display + Debug + 'static> TestResult<T, E> {
18 pub fn new(result: Result<T, E>) -> Self {
19 Self {
20 inner: result,
21 context: None,
22 source: None,
23 }
24 }
25
26 pub fn with_context(mut self, context: &str) -> Self {
27 self.context = Some(context.to_string());
28 self
29 }
30
31 pub fn with_source(mut self, source: &str) -> Self {
32 self.source = Some(source.to_string());
33 self
34 }
35
36 fn format_context(&self) -> String {
37 self.context
38 .as_ref()
39 .map(|c| format!("\nContext: {}", c))
40 .unwrap_or_default()
41 }
42
43 fn format_err(&self, err: &E) -> String {
44 format_error_impl(err, self.source.as_deref())
45 }
46
47 pub fn inspect(self) -> Self {
50 let ctx = self.format_context();
51 match &self.inner {
52 Ok(val) => {
53 println!("\n🔎 INSPECT SUCCESS: {}\nValue: {:?}\n", ctx, val);
54 }
55 Err(e) => {
56 let msg = self.format_err(e);
57 println!(
58 "\n🔎 INSPECT FAILURE: {}\nMessage: {}\nDebug: {:?}\n",
59 ctx, msg, e
60 );
61 }
62 }
63 self
64 }
65
66 pub fn assert_success(self) -> T {
68 let ctx = self.format_context();
69 match self.inner {
70 Ok(val) => {
71 println!("ℹ️ Asserting success.\n Actual: {:?}", val);
72 val
73 }
74 Err(ref e) => {
75 let msg = self.format_err(e);
76 panic!(
77 "\n🔴 TEST FAILED (Expected Success, but got Error):{}\nMessage: {}\nError Debug: {:?}\n",
78 ctx, msg, e
79 );
80 }
81 }
82 }
83
84 pub fn assert_success_is<Exp>(self, expected: Exp) -> T
87 where
88 T: PartialEq<Exp>,
89 Exp: Debug,
90 {
91 let ctx = self.format_context();
92 let val = self.assert_success();
94
95 println!("ℹ️ Checking equality.\n Expected: {:?}", expected);
96
97 if val != expected {
98 panic!(
99 "\n🔴 TEST FAILED (Value Mismatch):{}\nExpected: {:?}\nGot: {:?}\n",
100 ctx, expected, val
101 );
102 }
103 val
104 }
105
106 pub fn assert_success_with<F>(self, f: F) -> T
109 where
110 F: FnOnce(&T),
111 {
112 let val = self.assert_success();
114 println!("ℹ️ Asserting success with closure.");
115 f(&val);
116 val
117 }
118
119 pub fn assert_success_debug(self, expected_debug: &str) -> T {
122 let ctx = self.format_context();
123 let val = self.assert_success();
125 let actual_debug = format!("{:?}", val);
126
127 println!(
128 "ℹ️ Checking debug representation.\n Expected: {:?}",
129 expected_debug
130 );
131
132 if actual_debug != expected_debug {
133 panic!(
134 "\n🔴 TEST FAILED (Debug Mismatch):{}\nExpected: {:?}\nGot: {:?}\n",
135 ctx, expected_debug, actual_debug
136 );
137 }
138 val
139 }
140
141 pub fn assert_failure(self) -> E {
143 let ctx = self.format_context();
144 match self.inner {
145 Ok(val) => {
146 panic!(
147 "\n🔴 TEST FAILED (Expected Failure, but got Success):{}\nParsed Value: {:?}\n",
148 ctx, val
149 );
150 }
151 Err(e) => {
152 println!("ℹ️ Asserting failure.\n Error: {:?}", e);
153 e
154 }
155 }
156 }
157
158 pub fn assert_failure_contains(self, expected_msg_part: &str) {
160 let ctx = self.format_context();
161 let source = self.source.clone();
162
163 let err = self.assert_failure();
165 let actual_msg = err.to_string();
166
167 println!(
168 "ℹ️ Checking error message contains {:?}.\n Actual message: {:?}",
169 expected_msg_part, actual_msg
170 );
171
172 if !actual_msg.contains(expected_msg_part) {
173 let formatted = format_error_impl(&err, source.as_deref());
174 panic!(
175 "\n🔴 TEST FAILED (Error Message Mismatch):{}\nExpected part: {:?}\nActual msg: {:?}\nError Debug: {:?}\nFormatted: \n{}\n",
176 ctx, expected_msg_part, actual_msg, err, formatted
177 );
178 }
179 }
180
181 pub fn assert_success_contains(self, expected_part: &str) -> T
183 where
184 T: Display,
185 {
186 let ctx = self.format_context();
187 let val = self.assert_success();
189 let val_str = val.to_string();
190
191 println!(
192 "ℹ️ Checking success string contains {:?}.\n Actual string: {:?}",
193 expected_part, val_str
194 );
195
196 if !val_str.contains(expected_part) {
197 panic!(
198 "\n🔴 TEST FAILED (Content Mismatch):{}\nExpected to contain: {:?}\nGot: {:?}\n",
199 ctx, expected_part, val_str
200 );
201 }
202 val
203 }
204
205 pub fn assert_failure_not_contains(self, unexpected_part: &str) {
207 let ctx = self.format_context();
208 let source = self.source.clone();
209
210 let err = self.assert_failure();
212 let actual_msg = err.to_string();
213
214 println!(
215 "ℹ️ Checking error message NOT contains {:?}.\n Actual message: {:?}",
216 unexpected_part, actual_msg
217 );
218
219 if actual_msg.contains(unexpected_part) {
220 let formatted = format_error_impl(&err, source.as_deref());
221 panic!(
222 "\n🔴 TEST FAILED (Unexpected Error Message Content):{}\nUnexpected part: {:?}\nActual msg: {:?}\nError Debug: {:?}\nFormatted:\n{}\n",
223 ctx, unexpected_part, actual_msg, err, formatted
224 );
225 }
226 }
227}
228
229pub trait Testable<T, E> {
230 fn test(self) -> TestResult<T, E>;
231}
232
233#[cfg(feature = "syn")]
234impl<T: Debug> Testable<T, syn::Error> for syn::Result<T> {
235 fn test(self) -> TestResult<T, syn::Error> {
236 TestResult::new(self)
237 }
238}
239
240fn format_error_impl<E: Display + Debug + 'static>(err: &E, source: Option<&str>) -> String {
241 #[cfg(feature = "syn")]
242 if let Some(src) = source {
243 if let Some(syn_err) = (err as &dyn Any).downcast_ref::<syn::Error>() {
244 return pretty_print_syn_error(syn_err, src);
245 }
246 }
247 format!("{}", err)
248}
249
250#[cfg(feature = "syn")]
251fn pretty_print_syn_error(err: &syn::Error, source: &str) -> String {
252 let start = err.span().start();
253 let end = err.span().end();
254
255 if start.line == 0 {
256 return err.to_string();
257 }
258
259 let line_idx = start.line - 1;
260 let lines: Vec<&str> = source.lines().collect();
261
262 if line_idx >= lines.len() {
263 return err.to_string();
264 }
265
266 let line = lines[line_idx];
267 let col = start.column;
268
269 let width = if start.line == end.line {
273 end.column.saturating_sub(col).max(1)
274 } else {
275 1
276 };
277
278 format!(
279 "{}\n --> line {}:{}\n |\n {} | {}\n | {}{}",
280 err,
281 start.line,
282 col,
283 start.line,
284 line,
285 " ".repeat(col),
286 "^".repeat(width)
287 )
288}