test_better_core/
or_fail.rs1use std::borrow::Cow;
10use std::error::Error;
11
12use crate::context::coerce;
13use crate::error::{ErrorKind, TestError};
14use crate::result::TestResult;
15
16pub trait OrFail<T> {
19 fn or_fail(self) -> TestResult<T>;
21
22 fn or_fail_with(self, message: impl Into<Cow<'static, str>>) -> TestResult<T>;
24}
25
26#[track_caller]
28fn missing_value<T>() -> TestError {
29 TestError::new(ErrorKind::Assertion).with_message(format!(
30 "expected Some({}), got None",
31 std::any::type_name::<T>()
32 ))
33}
34
35impl<T> OrFail<T> for Option<T> {
36 #[track_caller]
37 fn or_fail(self) -> TestResult<T> {
38 match self {
39 Some(value) => Ok(value),
40 None => Err(missing_value::<T>()),
41 }
42 }
43
44 #[track_caller]
45 fn or_fail_with(self, message: impl Into<Cow<'static, str>>) -> TestResult<T> {
46 match self {
47 Some(value) => Ok(value),
48 None => Err(TestError::new(ErrorKind::Assertion).with_message(message)),
49 }
50 }
51}
52
53impl<T, E> OrFail<T> for Result<T, E>
54where
55 E: Error + Send + Sync + 'static,
56{
57 #[track_caller]
58 fn or_fail(self) -> TestResult<T> {
59 self.map_err(coerce)
60 }
61
62 #[track_caller]
65 fn or_fail_with(self, message: impl Into<Cow<'static, str>>) -> TestResult<T> {
66 match self {
67 Ok(value) => Ok(value),
68 Err(error) => {
69 Err(coerce(error).with_context_frame(crate::error::ContextFrame::new(message)))
70 }
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::error::Payload;
79 use crate::{OrFail, TestResult};
80 use test_better_matchers::{check, eq, is_true};
81
82 fn io_error() -> std::io::Error {
83 std::io::Error::new(std::io::ErrorKind::NotFound, "missing file")
84 }
85
86 #[test]
87 fn or_fail_passes_through_some_like_unwrap() -> TestResult {
88 let some: Option<i32> = Some(7);
89 check!(some.or_fail()?).satisfies(eq(7)).or_fail()?;
90 Ok(())
91 }
92
93 #[test]
94 fn or_fail_passes_through_ok_like_unwrap() -> TestResult {
95 let ok: Result<i32, std::io::Error> = Ok(7);
96 check!(ok.or_fail()?).satisfies(eq(7)).or_fail()?;
97 Ok(())
98 }
99
100 #[test]
101 fn or_fail_on_none_names_the_expected_type_and_caller_location() -> TestResult {
102 let missing: Option<i32> = None;
103 let line = line!() + 1;
104 let result = missing.or_fail();
105 let error = result.expect_err("err path");
106 check!(error.kind)
107 .satisfies(eq(ErrorKind::Assertion))
108 .or_fail()?;
109 check!(error.location.line())
110 .satisfies(eq(line))
111 .or_fail()?;
112 let message = error.message.as_deref().or_fail_with("message present")?;
113 check!(message.starts_with("expected Some("))
114 .satisfies(is_true())
115 .or_fail()?;
116 check!(message.ends_with("i32), got None"))
117 .satisfies(is_true())
118 .or_fail()?;
119 Ok(())
120 }
121
122 #[test]
123 fn or_fail_with_on_none_uses_the_supplied_message() -> TestResult {
124 let missing: Option<i32> = None;
125 let error = missing
126 .or_fail_with("the user should have been seeded")
127 .expect_err("err path");
128 check!(error.message.as_deref())
129 .satisfies(eq(Some("the user should have been seeded")))
130 .or_fail()?;
131 Ok(())
132 }
133
134 #[test]
135 fn or_fail_on_err_preserves_the_underlying_error() -> TestResult {
136 let failing: Result<(), std::io::Error> = Err(io_error());
137 let error = failing.or_fail().expect_err("err path");
138 check!(error.kind)
139 .satisfies(eq(ErrorKind::Custom))
140 .or_fail()?;
141 match error.payload.as_deref() {
142 Some(Payload::Other(inner)) => {
143 check!(inner.to_string())
144 .satisfies(eq("missing file".to_string()))
145 .or_fail()?;
146 }
147 other => panic!("expected Other payload, got {other:?}"),
148 }
149 Ok(())
150 }
151
152 #[test]
153 fn or_fail_does_not_double_wrap_a_test_error() -> TestResult {
154 let original = TestError::assertion("values differ");
155 let original_line = original.location.line();
156 let failing: Result<(), TestError> = Err(original);
157 let error = failing.or_fail().expect_err("err path");
158 check!(error.kind)
159 .satisfies(eq(ErrorKind::Assertion))
160 .or_fail()?;
161 check!(error.location.line())
162 .satisfies(eq(original_line))
163 .or_fail()?;
164 check!(error.message.as_deref())
165 .satisfies(eq(Some("values differ")))
166 .or_fail()?;
167 check!(error.payload.is_none())
168 .satisfies(is_true())
169 .or_fail()?;
170 Ok(())
171 }
172
173 #[test]
174 fn or_fail_with_on_err_keeps_the_chain_and_adds_context() -> TestResult {
175 let failing: Result<(), std::io::Error> = Err(io_error());
176 let error = failing
177 .or_fail_with("loading the config file")
178 .expect_err("err path");
179 check!(matches!(error.payload.as_deref(), Some(Payload::Other(_))))
180 .satisfies(is_true())
181 .or_fail()?;
182 check!(error.context.len()).satisfies(eq(1)).or_fail()?;
183 check!(error.context[0].message.as_ref())
184 .satisfies(eq("loading the config file"))
185 .or_fail()?;
186 Ok(())
187 }
188}