1#![doc = include_str!("../README.md")]
2
3pub fn id<T>(x: T) -> T {
4 x
5}
6
7pub fn ok<E>() -> Result<(), E> {
8 Ok(())
9}
10
11pub trait Void {
12 fn void(&self);
13}
14
15impl<T> Void for T {
16 fn void(&self) {}
17}
18
19pub fn void<T>(_: T) {}
20
21pub trait Tweak
22where
23 Self: Sized,
24{
25 fn tweak(&mut self, f: impl FnOnce(&Self) -> Self);
26 fn try_tweak<E>(
27 &mut self,
28 f: impl FnOnce(&Self) -> Result<Self, E>,
29 ) -> Result<(), E>;
30}
31
32impl<T> Tweak for T {
33 fn tweak(&mut self, f: impl FnOnce(&T) -> T) {
34 *self = f(self);
35 }
36 fn try_tweak<E>(
37 &mut self,
38 f: impl FnOnce(&T) -> Result<Self, E>,
39 ) -> Result<(), E> {
40 f(self).and_then(|x| {
41 *self = x;
42 ok()
43 })
44 }
45}
46
47pub trait Guard<E>
48where
49 Self: Sized,
50{
51 fn guard(self, e: E) -> Result<(), E> {
52 self.guard_then(|| e)
53 }
54 fn guard_then(
55 self,
56 f: impl FnOnce() -> E,
57 ) -> Result<(), E>;
58}
59
60impl<E> Guard<E> for bool {
61 fn guard_then(
62 self,
63 f: impl FnOnce() -> E,
64 ) -> Result<(), E> {
65 if self { ok() } else { Err(f()) }
66 }
67}
68
69impl<T, E> Guard<E> for Option<T>
70where
71 T: Guard<E>,
72{
73 fn guard_then(
74 self,
75 f: impl FnOnce() -> E,
76 ) -> Result<(), E> {
77 match self {
78 None => Err(f()),
79 Some(x) => x.guard_then(f),
80 }
81 }
82}
83
84impl<T, EOuter, EInner> Guard<EOuter> for Result<T, EInner>
85where
86 T: Guard<EOuter>,
87 EOuter: From<EInner>,
88{
89 fn guard_then(
90 self,
91 f: impl FnOnce() -> EOuter,
92 ) -> Result<(), EOuter> {
93 self?.guard_then(f)
94 }
95}
96
97pub fn guard<T, E>(x: T, e: E) -> Result<(), E>
98where
99 T: Guard<E>,
100{
101 x.guard(e)
102}
103
104pub fn guard_then<T, E>(
105 x: T,
106 f: impl FnOnce() -> E,
107) -> Result<(), E>
108where
109 T: Guard<E>,
110{
111 x.guard_then(f)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use std::convert::Infallible;
118
119 #[test]
120 fn id_function() {
121 assert_eq!(id("Hello"), "Hello");
122 let x = "Hello";
123 assert_eq!(id(x), "Hello");
124 assert_eq!(id(x), "Hello");
125 let x = "Hello".to_string();
126 assert_eq!(id(x), "Hello");
127 }
128
129 #[test]
130 fn ok_function() {
131 assert_eq!(ok::<Infallible>(), Ok(()));
132 }
133
134 #[test]
135 fn void_function() {
136 assert_eq!(void("Hello"), ());
137 let x = "Hello";
138 assert_eq!(void(x), ());
139 assert_eq!(void(x), ());
140 let x = "Hello".to_string();
141 assert_eq!(void(x), ());
142 }
143 #[test]
144 fn void_method() {
145 assert_eq!("Hello".void(), ());
146 let x = "Hello";
147 assert_eq!(x.void(), ());
148 assert_eq!(x.void(), ());
149 let x = "Hello".to_string();
150 assert_eq!(x.void(), ());
151 assert_eq!(x.void(), ());
152 }
153
154 #[test]
155 fn tweak_method() {
156 let mut x = "Hello".to_string();
157 x.tweak(|s| s.to_uppercase());
158 assert_eq!(x, "HELLO");
159 }
160 #[test]
161 fn tweak_nested() {
162 let mut x = ((("hello".to_string(), 3), 2), 1);
163 x.0.0.0.tweak(|x| x.to_uppercase());
164 assert_eq!(x, ((("HELLO".to_string(), 3), 2), 1));
165 }
166
167 #[test]
168 fn try_tweak_method_ok() {
169 let mut x = "41".to_string();
170 assert_eq!(
171 x.try_tweak(|s| {
172 let n: u32 = s.parse()?;
173 Ok::<String, std::num::ParseIntError>(
174 (n + 1).to_string(),
175 )
176 }),
177 Ok(())
178 );
179 assert_eq!(x, "42");
180 }
181 #[test]
182 fn try_tweak_method_err_does_not_mutate() {
183 let mut x = "not a number".to_string();
184 assert!(
185 x.try_tweak(|s| {
186 let n: u32 = s.parse()?;
187 Ok::<String, std::num::ParseIntError>(
188 (n + 1).to_string(),
189 )
190 })
191 .is_err()
192 );
193 assert_eq!(x, "not a number");
194 }
195 #[test]
196 fn try_tweak_nested_ok() {
197 let mut x = ((("41".to_string(), 3), 2), 1);
198 assert_eq!(
199 x.0.0.0.try_tweak(|s| {
200 let n: u32 = s.parse()?;
201 Ok::<String, std::num::ParseIntError>(
202 (n + 1).to_string(),
203 )
204 }),
205 Ok(())
206 );
207 assert_eq!(x, ((("42".to_string(), 3), 2), 1));
208 }
209
210 #[test]
211 fn guard_function() {
212 assert_eq!(guard(true, ()), Ok(()));
213 assert_eq!(guard(false, "error"), Err("error"));
214 }
215 #[test]
216 fn guard_method() {
217 assert_eq!(true.guard(()), Ok(()));
218 assert_eq!(false.guard("error"), Err("error"));
219 }
220 #[test]
221 fn guard_expression() {
222 let f = |x: u32| {
223 guard(x > 0, ())?;
224 Ok(42u32)
225 };
226 assert_eq!(f(1), Ok(42));
227 assert_eq!(f(0), Err(()));
228 }
229 #[test]
230 fn guard_option() {
231 assert_eq!(Some(true).guard(()), Ok(()));
232 assert_eq!(
233 Some(false).guard("error"),
234 Err("error")
235 );
236 assert_eq!(
237 None::<bool>.guard("error"),
238 Err("error")
239 );
240 }
241 #[test]
242 fn guard_result() {
243 assert_eq!(
244 Ok::<_, &str>(true).guard("error"),
245 Ok(())
246 );
247 assert_eq!(
248 Ok::<_, &str>(false).guard("error"),
249 Err("error")
250 );
251 assert_eq!(
252 Err::<bool, _>("orig").guard("error"),
253 Err("orig")
254 );
255 }
256 #[test]
257 fn guard_nested() {
258 assert_eq!(Some(Some(true)).guard(()), Ok(()));
259 assert_eq!(
260 Some(Some(false)).guard("error"),
261 Err("error")
262 );
263 assert_eq!(
264 Some(None::<bool>).guard("error"),
265 Err("error")
266 );
267 assert_eq!(
268 Ok::<_, &str>(Some(true)).guard("error"),
269 Ok(())
270 );
271 assert_eq!(
272 Ok::<_, &str>(Some(false)).guard("error"),
273 Err("error")
274 );
275 assert_eq!(
276 Ok::<_, &str>(None::<bool>).guard("error"),
277 Err("error")
278 );
279 assert_eq!(
280 Err::<Option<bool>, _>("orig").guard("error"),
281 Err("orig")
282 );
283 }
284
285 #[test]
286 fn guard_real_life_scenarios() {
287 struct Request {
288 path: String,
289 token: Option<String>,
290 }
291
292 #[derive(Debug, PartialEq)]
293 enum Error {
294 AccessDenied,
295 RateLimitExceeded,
296 InvalidInput,
297 }
298
299 let check_access = |user_id: u32, role: &str| {
300 guard(user_id != 0, Error::InvalidInput)?;
301 guard(role == "admin", Error::AccessDenied)?;
302 Ok("Success")
303 };
304
305 assert_eq!(check_access(1, "admin"), Ok("Success"));
306 assert_eq!(
307 check_access(0, "admin"),
308 Err(Error::InvalidInput)
309 );
310 assert_eq!(
311 check_access(1, "user"),
312 Err(Error::AccessDenied)
313 );
314
315 let process_data =
316 |data: Option<u32>, limit: u32| {
317 let val =
318 data.ok_or(Error::InvalidInput)?;
319 guard(
320 val < limit,
321 Error::RateLimitExceeded,
322 )?;
323 Ok(val * 2)
324 };
325
326 assert_eq!(process_data(Some(10), 20), Ok(20));
327 assert_eq!(
328 process_data(None, 20),
329 Err(Error::InvalidInput)
330 );
331 assert_eq!(
332 process_data(Some(30), 20),
333 Err(Error::RateLimitExceeded)
334 );
335
336 let handle_request = |req: Request| {
337 guard(
338 req.path.starts_with("/api"),
339 Error::InvalidInput,
340 )?;
341 req.token
342 .is_some()
343 .guard(Error::AccessDenied)?;
344 Ok("Authorized")
345 };
346
347 assert_eq!(
348 handle_request(Request {
349 path: "/api/v1".into(),
350 token: Some("abc".into())
351 }),
352 Ok("Authorized")
353 );
354 assert_eq!(
355 handle_request(Request {
356 path: "/home".into(),
357 token: Some("abc".into())
358 }),
359 Err(Error::InvalidInput)
360 );
361 assert_eq!(
362 handle_request(Request {
363 path: "/api/v1".into(),
364 token: None
365 }),
366 Err(Error::AccessDenied)
367 );
368 }
369}