function_compose/
lib.rs

1//uncomment below lines to debug macros
2
3/*#![feature(trace_macros)]
4trace_macros!(true);*/
5//! Crate `function-compose` provides utilities for composing functions and way to inject arguments to functions
6//! 
7//! ## Composing functions
8//! 
9//! ### step 1
10//!
11//!  Mark a function as composeable as below. Note that the functions must always return Result type
12//!
13//! ```rust
14//! use function_compose::composeable;
15//! #[composeable()]
16//! pub fn add_10(a: i32) -> Result<i32, String> {
17//!     Ok(a + 10)
18//! }
19//! 
20//! #[composeable()]
21//! pub fn add_100(a: i32) -> Result<i32, String> {
22//!     Ok(a + 100)
23//! }
24//! 
25//! ```
26//! ### step 2
27//! 
28//! use compose! macro to compose the above two functions.
29//! 
30//! ```ignore
31//! let result = compose!(add_10 -> add_100 -> with_args(10));
32//! assert_eq!(220, result.unwrap());
33//! ```
34//! Argument 10(from with_args(10)). is passed to add_10 function and result of add_10 is passed to add_100
35//! 
36//! ## composing Async functions
37//! It is also possible to compose sync and asycn function.
38//! ##### <font color="#FFBF00"> __For async function,  return type should be BoxedFuture(futures crate)__</font>
39//! 
40//! ```ignore
41//! use function_compose::composeable;
42//! use futures::{future::BoxFuture, FutureExt};
43//! #[composeable()]
44//! pub fn add_async(a: i32, b: i32) -> BoxFuture<'static, Result<i32, String>> {
45//!     async move {
46//!         let r = a + b;
47//!         Ok(r)
48//!     }.boxed()
49//! }
50//! ```
51//! 
52//! ### Composing async and sync functions usage
53//!
54//!```ignore
55//! use function_compose::compose;
56//! use fn_macros::composeable;
57//! use futures::{future::BoxFuture, FutureExt};
58//! #[composeable()]
59//! pub fn add_10_async(a: i32) -> BoxFuture<'static, Result<i32, String>> {
60//!     async move {
61//!         let r = a + 10;
62//!         Ok(r)
63//!     }.boxed()
64//! }
65//! #[composeable()]
66//! pub fn add_10(a: i32) -> Result<i32, String> {
67//!     Ok(a + 10)
68//! }
69//! async fn test(){
70//!    let result = compose!(add_async.add_10_async -> add_10 -> with_args(10)).await;
71//!    assert_eq!(30, result.unwrap());
72//! }
73//! 
74//! ```
75//! 
76//! ## Injecting dependencies in multi-args function
77//! For function with multiple arguments(say 2), One of the argument can be injected during composition itself.
78//! 
79//! #### Function argument injection usage
80//!```ignore
81//! use function_compose::composeable;
82//! use futures::{future::BoxFuture, FutureExt};
83//! #[composeable()]
84//! pub fn add_3_arg_async(a: i32,b: i32, c:i32) -> BoxFuture<'static, Result<i32, String>>{
85//!     async move{
86//!         let  r =   a + b + c;
87//!         Ok(r)
88//!     }.boxed()
89//! }
90//! use crate::compose;
91//! let result = compose!(add_3_arg_async.provide(100).provide(200) -> add_10 -> with_args(10)).await;
92//! assert_eq!(320, result.unwrap());
93//!```
94//! In the above example function add_3_arg_async, out of three arguments, 2 are injected during composing the function itself (using provide(100)) .
95//! This feature could be used for injecting connection pool or a repository instance(see the example project).
96//! 
97//! ## Retry in Fn Composer
98
99//!Composeable macro supports retrying a function at specified interval in case of Error returned by the function.
100//!This could be useful when trying make a database call or connect to network endpoint.
101//!Make sure to add https://docs.rs/retry/latest/retry/ to your project before proceeding with retry feature.
102//!
103//!Retry mechanism is implemented as part of composeable procedureal macro.
104//!Below is example of  add_10  function configured to be retried 2 times after initial failure.
105//!
106//!```ignore
107//!use retry::delay::*;
108//!#[composeable(retry = Fixed::from_millis(100).take(2))]
109//!pub fn add_10(a: i32) -> Result<i32, String> {
110//!    Ok(a + 10)
111//!}
112//!
113//!```
114
115//!Retry can be applied to both sync and async functions.
116//!
117//!for async functions, <font color="#FFBF00"> __all arguments to the function must be either shared reference or exclusive reference.__ </font>
118//!
119//!Below is example of  async function with retry.
120//!
121//!```ignore
122//!#[composeable(retry = Fixed::from_millis(100))]
123//!pub fn add_3_arg_ref__non_copy_async<'a>(
124//!    a: &'a mut Vec<String>,
125//!    b: &'a mut Vec<String>,
126//!    c: &'a Vec<String>,
127//!) -> BoxFuture<'a, Result<i32, String>> {
128//!    async move {
129//!        let r = a.len() + b.len() + c.len();
130//!        Ok(r as i32)
131//!    }
132//!    .boxed()
133//!}
134//!```
135//!
136//!Apart from fixed duration retries, it is possible to configure with exponential delay.
137//!Refer to retry documentation for all available delay options https://docs.rs/retry/latest/retry/all.html
138
139
140use futures::{future::BoxFuture, FutureExt};
141
142fn to_fn_error<E1, E2>(error:E1) -> E2 where E2:From<E1>{
143    From::from(error)    
144}
145
146
147pub use function_compose_proc_macros::*;
148pub use paste::*;
149pub use concat_idents::concat_idents;
150
151macro_rules! composer_generator {
152    ($arg1:ident, $return_type1:ident, $return_type2:ident, $error_type1:ident, $error_type2:ident) => {
153        paste!{
154            #[doc = concat!("Then implementation for composing sync function (BoxedFn1) with another sync function(BoxedFn1) ")]
155            impl<'a, $arg1: 'a + Send, $return_type1: 'a + Send, $return_type2: 'a, $error_type1: Send + 'a, $error_type2: Send + 'a>
156                Then<'a, $arg1, $return_type1, $return_type2, BoxedFn1<'a, $return_type1, $return_type2, $error_type2>, BoxedFn1<'a, $arg1, $return_type2, $error_type2>> for BoxedFn1<'a, $arg1, $return_type1, $error_type1> where E2:From<E1>{
157
158                fn then(self, f: BoxedFn1<'a, $return_type1, $return_type2, $error_type2>) -> BoxedFn1<'a, $arg1, $return_type2, $error_type2> {
159                    let r1 = move |x: $arg1| {
160                        let g_result = self(x);
161                        match g_result{
162                                Ok(inner_result) => f(inner_result),
163                                Err(error) =>   Err(to_fn_error(error)),
164                            }
165                    };
166                    Box::new(r1)
167                }
168            }
169
170            #[doc = concat!("Then implementation for composing sync function(BoxedFn1) with another async function(BoxedAsyncFn1) ")]
171            impl<'a, $arg1: 'a + Send, $return_type1: 'a + Send, $return_type2: 'a, $error_type1: Send + 'a, $error_type2: Send + 'a>
172                Then<'a, $arg1, $return_type1, $return_type2, BoxedAsyncFn1<'a, $return_type1, $return_type2, $error_type2>, BoxedAsyncFn1<'a, $arg1, $return_type2, $error_type2>> for BoxedFn1<'a, $arg1, $return_type1, $error_type1> where E2:From<E1>{
173
174                fn then(self, f: BoxedAsyncFn1<'a, $return_type1, $return_type2, $error_type2>) -> BoxedAsyncFn1<'a, $arg1, $return_type2, $error_type2> {
175                    let r1 =  |x: $arg1| {
176                        async move{
177                            let g_result = self(x);
178                            match g_result{
179                                Ok(inner_result) => f(inner_result).await,
180                                Err(error) =>   Err(to_fn_error(error)),
181                            }
182                            //f(b).await
183                        }.boxed()
184                    };
185                    Box::new(r1)
186                }
187            }
188
189            #[doc = concat!("Then implementation for composing async function(BoxedAsyncFn1) with another sync function(BoxedFn1) ")]
190
191            impl<'a, $arg1: 'a + Send, $return_type1: 'a + Send, $return_type2: 'a, $error_type1:Send +  'a, $error_type2:Send +  'a>
192                Then<'a, $arg1, $return_type1, $return_type2, BoxedFn1<'a, $return_type1, $return_type2, $error_type2>, BoxedAsyncFn1<'a, $arg1, $return_type2, $error_type2>> for BoxedAsyncFn1<'a, $arg1, $return_type1, $error_type1> where E2:From<E1>{
193
194                fn then(self, f: BoxedFn1<'a, $return_type1, $return_type2, $error_type2>) -> BoxedAsyncFn1<'a, $arg1, $return_type2, $error_type2> {
195                    let r1 = |a: $arg1| {
196                        async move {
197                            let g_result = self(a).await;
198                            match g_result{
199                                Ok(inner_result) => f(inner_result),
200                                Err(error) =>   Err(to_fn_error(error)),
201                            }
202                        }.boxed()
203                    };
204                    let r: BoxedAsyncFn1<'a,$arg1, $return_type2, $error_type2> = Box::new(r1);
205                    r
206                }
207            }
208
209
210            #[doc = concat!("Then implementation for composing async function(BoxedAsyncFn1) with another async function(BoxedAsyncFn1) ")]
211            impl<'a, $arg1: 'a + Send, $return_type1: 'a + Send, $return_type2: 'a, $error_type1:Send +  'a, $error_type2:Send + 'a>
212                Then<'a, $arg1, $return_type1, $return_type2, BoxedAsyncFn1<'a, $return_type1, $return_type2, $error_type2>, BoxedAsyncFn1<'a, $arg1, $return_type2, $error_type2>> for BoxedAsyncFn1<'a, $arg1, $return_type1, $error_type1> where E2:From<E1>{
213
214                fn then(self, f: BoxedAsyncFn1<'a, $return_type1, $return_type2, $error_type2>) -> BoxedAsyncFn1<'a, $arg1, $return_type2, $error_type2> {
215                    let r1 = |a: $arg1| {
216                        async move {
217                            let g_result = self(a).await;
218                            match g_result{
219                                Ok(inner_result) => f(inner_result).await,
220                                Err(error) =>   Err(to_fn_error(error)),
221                            }
222                        }.boxed()
223                    };
224                    let r: BoxedAsyncFn1<'a,$arg1, $return_type2, $error_type2> = Box::new(r1);
225                    r
226                }
227            }
228        }
229    }
230}
231macro_rules! impl_injector {
232    ([$($args:ident),*], $provided:ident, $return_type:ident, $error_type:ident, $arg_size:literal, $return_fn_arg_size:literal) => {
233
234        paste!  {
235            #[doc = concat!("dependency injection function provide_f", stringify!($arg_size), " for injecting the last argument of a given sync function")]
236            pub fn [<provider_f $arg_size>]<'a, $($args),*, $provided, $return_type, $error_type>(fn1: [<BoxedFn $arg_size>]<'a, $($args),*, $provided, $return_type, $error_type>,provided_data: $provided,) -> [<BoxedFn $return_fn_arg_size>]<'a, $($args),* , $return_type, $error_type> where $( $args: 'a ),*, $provided: Send + Sync + 'a, $return_type: 'a, $error_type: 'a{
237                    Box::new(move |$( [<$args:lower>]:$args ),*| fn1($( [<$args:lower>]),*,  provided_data))
238            }
239
240            #[doc = concat!("dependency injection function provider_async_f", stringify!($arg_size), " for injecting the last argument of a given async function")]
241            pub fn [<provider_async_f $arg_size>]<'a, $($args),*, $provided, $return_type, $error_type>(fn1: [<BoxedAsyncFn $arg_size>]<'a, $($args),*, $provided, $return_type, $error_type>,provided_data: $provided,) -> [<BoxedAsyncFn $return_fn_arg_size>]<'a, $($args),* , $return_type, $error_type> where $( $args: 'a ),*, $provided: Send + Sync + 'a, $return_type: 'a, $error_type: 'a{
242                    Box::new(move |$( [<$args:lower>]:$args ),*| fn1($( [<$args:lower>]),*,  provided_data))
243            }
244
245        }
246        paste!{
247
248            #[doc = concat!("Injector implementation for a given sync function that accepts " , stringify!($return_fn_arg_size+1), " arguments and returns a function with ", stringify!($return_fn_arg_size), " arguments")]
249            impl<'a, $($args),*, $provided, $return_type, $error_type> Injector<$provided, [<BoxedFn $return_fn_arg_size>]<'a, $($args),*, $return_type, $error_type>> for [<BoxedFn $arg_size>] <'a, $($args),*, $provided, $return_type, $error_type>
250            where $( $args: 'a ),*, $provided: Send + Sync +'a, $return_type: 'a, $error_type: 'a
251            {
252                fn provide(self, a: $provided) -> [<BoxedFn $return_fn_arg_size>]<'a, $($args),*, $return_type, $error_type> {
253                    let r = [<provider_f $arg_size>](self, a);
254                    r
255                }
256            }
257
258            #[doc = concat!("Injector implementation for a given async function that accepts " , stringify!($return_fn_arg_size+1), " arguments  and returns a function with ", stringify!($return_fn_arg_size), " arguments")]
259            impl<'a, $($args),*, $provided, $return_type, $error_type> Injector<$provided, [<BoxedAsyncFn $return_fn_arg_size>]<'a, $($args),*, $return_type, $error_type>> for [<BoxedAsyncFn $arg_size>] <'a, $($args),*, $provided, $return_type, $error_type>
260            where $( $args: 'a ),*, $provided: Send + Sync +'a, $return_type: 'a, $error_type: 'a
261            {
262                fn provide(self, a: $provided) -> [<BoxedAsyncFn $return_fn_arg_size>]<'a, $($args),*, $return_type, $error_type> {
263                    let r = [<provider_async_f $arg_size>](self, a);
264                    r
265                }
266            }
267        }
268    };
269}
270
271macro_rules! generate_boxed_fn {
272    ( [$($args:ident),*], $return_type:ident,$error_type:ident, $arg_size:expr ) => {
273
274            //let x = count!($($args),*);
275            crate::concat_idents!(boxed_fn_name = BoxedFn,$arg_size  {
276                #[doc = concat!("Type alias  BoxedFn", stringify!($arg_size), "  for Boxed FnOnce sync function with ", stringify!($arg_size), " arguments")]
277                pub type boxed_fn_name<'a, $($args),*, $return_type, $error_type> = Box<dyn FnOnce($($args),*) -> Result<$return_type, $error_type> + Send + Sync + 'a>;
278            });
279
280            crate::concat_idents!(boxed_fn_name = BoxedAsyncFn,$arg_size  {
281                #[doc = concat!("Type alias  BoxedAsyncFn", stringify!($arg_size), "  for Boxed FnOnce async function" , stringify!($arg_size), " arguments")]
282                    pub type boxed_fn_name<'a, $($args),*, $return_type,$error_type> = Box<dyn FnOnce($($args),*) -> BoxFuture<'a, Result<$return_type, $error_type>> + Send + Sync + 'a>;
283                });
284
285            paste!{
286                #[doc = concat!("Function to box FnOnce sync function with ", stringify!($arg_size), " aguments and coerce it to BoxedFn",stringify!($arg_size))]
287                pub fn [<lift_sync_fn $arg_size>]<'a, $($args),*, $return_type, $error_type, F: FnOnce($($args),*) -> Result<$return_type, $error_type> + Send + Sync + 'a>(f: F,) -> [<BoxedFn $arg_size>]<'a, $($args),*, $return_type, $error_type> {
288                    Box::new(f)
289                }
290
291                #[doc = concat!("Function to box  FnOnce sync function with ", stringify!($arg_size), " aguments and coerce it to BoxedAsyncFn",stringify!($arg_size))]
292                pub fn [<lift_async_fn $arg_size>]<'a, $($args),*, $return_type, $error_type, F: FnOnce($($args),*) -> BoxFuture<'a,Result<$return_type, $error_type>> + Send + Sync + 'a>(f: F,) -> [<BoxedAsyncFn $arg_size>]<'a, $($args),*, $return_type, $error_type> {
293                    Box::new(f)
294                }
295            }
296    }
297}
298
299generate_boxed_fn! {[T1], T2, E1, 1}
300
301generate_boxed_fn!([T1, T2], T3, E1,  2);
302impl_injector! {[T1],T2, T3, E1, 2, 1}
303
304generate_boxed_fn!([T1, T2, T3], T4, E1, 3);
305impl_injector!([T1, T2], T3, T4, E1, 3, 2);
306
307generate_boxed_fn!([T1, T2, T3, T4], T5, E1, 4);
308impl_injector!([T1, T2, T3], T4, T5, E1, 4, 3);
309
310generate_boxed_fn!([T1, T2, T3, T4, T5], T6, E1,  5);
311impl_injector!([T1, T2, T3, T4], T5, T6, E1, 5, 4);
312
313generate_boxed_fn!([T1, T2, T3, T4, T5, T6], T7, E1, 6);
314impl_injector!([T1, T2, T3, T4, T5], T6, T7, E1, 6, 5);
315
316generate_boxed_fn!([T1, T2, T3, T4, T5, T6, T7], T8, E1, 7);
317impl_injector!([T1, T2, T3, T4, T5, T6], T7, T8,E1, 7, 6);
318
319generate_boxed_fn!([T1, T2, T3, T4, T5, T6, T7, T8], T9, E1, 8);
320impl_injector!([T1, T2, T3, T4, T5, T6, T7], T8, T9, E1, 8, 7);
321
322//Generates a function composition for BoxedFn1 as below. The below is example of composing sync with sync function.
323//Similar code is generated for composing sync with async function, async with sync function and async with async function.
324// impl<'a, T1: 'a + Send, T2: 'a + Send, T3: 'a>
325// Then<'a, T1, T2, T3, BoxedFn1<'a, T2, T3>, BoxedFn1<'a, T1, T3>> for BoxedFn1<'a, T1, T2> {
326//     fn then(self, f: BoxedFn1<'a, T2, T3>) -> BoxedFn1<'a, T1, T3> {
327//         let r1 = move |x: T1| {
328//             let b = self(x)?;
329//             let r = f(b)?;
330//             Ok(r)
331//         };
332//         Box::new(r1)
333//     }
334// }
335composer_generator!(T1, T2, T3, E1, E2);
336
337
338
339
340
341pub trait Injector<I, O> {
342    fn provide(self, a: I) -> O;
343}
344
345///trait Then allows you to compose functions.
346/// Type param A represents the function arg of Self
347///
348/// Type param B represents the return type of Self
349///
350///Type B also acts as the input arg of function f
351///
352/// Type C represents the return type of  function f
353
354pub trait Then<'a, A, B, C, F, R> {
355
356    /// Compose self with function f
357    ///
358    /// self:function(A)->B
359    ///
360    /// f:function(B)->C
361    ///
362    /// returns function(A)->C
363    fn then(self, f: F) -> R;
364}
365
366#[macro_use]
367pub mod macros {
368
369
370    #[macro_export]
371    macro_rules! compose {
372        ($fnLeft:ident,$is_left_fn_async:ident,-> with_args($args:expr) $($others:tt)*) => {
373            {
374            let r = $fnLeft($args);
375            r
376            }
377        };
378
379        ($fnLeft:ident,$is_left_fn_async:ident,.provide($p1:expr) $($others:tt)*) => {
380            {
381                use function_compose::Injector;
382                let p = $fnLeft.provide($p1);
383                let p1 = compose!(p,$is_left_fn_async,$($others)*);
384                p1
385            }
386        };
387
388        ($f_left:ident,$is_left_fn_async:ident,$f_right:ident, $isRightAsync:ident,  .provide($p:expr) $($others:tt)*) =>{
389            {
390                let f_right = $f_right.provide($p);
391                let f3 = compose!($f_left,$is_left_fn_async,f_right,$isRightAsync,$($others)*);
392                f3
393            }
394        };
395
396        ($f_left:ident,$is_left_fn_async:ident,$f_right:ident, $isRightAsync:ident,  ->  $($others:tt)*) =>{
397            {
398                let f_left = $f_left.then($f_right);
399                let is_left_fn_async = $isRightAsync || $is_left_fn_async;
400                let f3 = compose!(f_left,is_left_fn_async, -> $($others)*);
401                f3
402            }
403        };
404
405        ($f_left:ident,$is_left_fn_async:ident,-> $fn:ident.provide($p:expr) $($others:tt)*) =>{
406            {
407                let f4;
408                
409                crate::concat_idents!(lifted_fn_name = fn_composer__lifted_fn,_, $fn {
410                    paste!{
411                    let is_retryable = [< fn_composer__is_retryable_ $fn >]();
412                    let current_f = if !is_retryable{
413                        [<fn_composer__lifted_fn_ $fn>]($fn)
414                    }else {
415                        [<fn_composer__lifted_fn_ $fn>]([< fn_composer__ retry_ $fn>])
416                    };
417                    }
418                    crate::concat_idents!(asyn_check_fn = fn_composer__is_async_, $fn {
419                        let is_right_async = asyn_check_fn();
420                        let f_right = current_f.provide($p);
421                        let f3 = compose!($f_left,$is_left_fn_async,f_right,is_right_async,$($others)*);
422                        f4 = f3;
423                    });
424                });
425                f4
426            }
427        };
428
429        ($f_left:ident,$isLeftFnAsync:ident,-> $fn:ident.provide($p:expr) $($others:tt)*) =>{
430            {
431                let f4;
432                crate::concat_idents!(lifted_fn_name = fn_composer__lifted_fn,_, $fn {
433                    let is_retryable = [<fn_composer__is_retryable $fn>]();
434                    let current_f = if !is_retryable{
435                        [<lifted_fn_name $fn>]($fn)
436                    }else {
437                        [<lifted_fn_name $fn>]([<fn_composer__ retry_ $fn>])
438                    };
439                    crate::concat_idents!(asyn_check_fn = fn_composer__is_async_, $fn {
440                        let isRightAsync = asyn_check_fn();
441                        let f_right = current_f.provide($p);
442                        let f3 = compose!($f_left,$is_left_fn_async,f_right,isRightAsync,$($others)*);
443                        f4 = f3;
444                    });
445                });
446                f4
447            }
448        };
449
450
451        ($f_left:ident,$is_left_fn_async:ident,-> $fn:ident $($others:tt)*) =>{
452            {
453                let f4;
454                paste!{
455
456                    let asyn_check_fn = [<fn_composer__is_async_ $fn>];
457                    let current_async = asyn_check_fn();
458                    let _is_result_async = current_async || $is_left_fn_async;
459                    let is_retryable = [<fn_composer__is_retryable_ $fn>]();
460                    let current_f = if !is_retryable{
461                        [<fn_composer__lifted_fn_ $fn>]($fn)
462                    }else {
463                        [<fn_composer__lifted_fn_ $fn>]([<fn_composer__ retry_ $fn>])
464                    };
465                    let f3 = $f_left.then(current_f);
466                    let f3 = compose!(f3,_is_result_async,$($others)*);
467                    f4 = f3;
468                }
469                f4
470            }
471        };
472
473
474
475        ($fn:ident $($others:tt)*) => {
476            {
477
478                use Then;
479                let f2;
480                paste!{
481                    let f = [<fn_composer__lifted_fn_ $fn>]($fn);
482                    let is_async = [<fn_composer__is_async_ $fn>]();
483                    let is_retryable = [<fn_composer__is_retryable_ $fn>]();
484                    let f = if !is_retryable{
485                        [<fn_composer__lifted_fn_ $fn>]($fn)
486                    }else {
487                        [<fn_composer__lifted_fn_ $fn>]([<fn_composer__ retry_ $fn>])
488                    };
489                    let f1 = compose!(f,is_async,$($others)*);
490                    f2 = f1;
491                };
492                f2
493            }
494        };
495
496
497    }
498}
499