Skip to main content

functora/
lib.rs

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}