# function-compose function-compose is a function composition library that allows composition of async and sync functions There are two macros required for the fn-composer function composition to work. Proc attribute Macro - composeable Declarative Macro - compose! Proc attribute macro composeable is add to any function that can be composed. e.g below is simple function to add 10 to a given number. [Composeable macro documentation](./funciton-compose-proc-macros/README.MD) ```rust #[composeable()] pub fn add_10(a: i32) -> Result { Ok(a + 10) } ``` Below snippet is an async example. The async function should return BoxFuture. ```rust #[composeable()] pub fn add_async(a: i32, b: i32) -> BoxFuture<'static, Result> { async move { let r = a + b; Ok(r) }.boxed() } ``` Below is example of async multiplication and add_100 to a number asynchronously ```rust #[composeable()] pub fn multiply_async(a: i32, b: i32) -> BoxFuture<'static, Result> { async move { let r = a * b; Ok(r) }.boxed() } #[composeable()] pub fn add_100_async(a: i32) -> BoxFuture<'static, Result> { async move { let r = a + 100; Ok(r) }.boxed() } ``` Below is example of async addition of 3 values ```rust #[composeable()] pub fn add_3_arg_async(a: i32,b: i32, c:i32) -> BoxFuture<'static, Result>{ async move{ let r = a + b + c; Ok(r) }.boxed() } ``` Below is example of async addition of 3 values with last parameter accepting reference ```rust #[composeable()] pub fn add_3_arg_ref_async<'a>(a: i32,b: &'a i32, c:&'a i32) -> BoxFuture<'a, Result>{ async move{ let r = a + b + c; Ok(r) }.boxed() } ``` It is possible to compose single arg sync function with two arg async function and vice versa. Below are the examples of how to compose async functions ```rust let result = compose!(add_10 -> add_100 -> with_args(10)); let result = compose!(add_10 -> add_100 -> add_10 -> add_100 -> with_args(10)); ``` Below are examples of composing async and sync functions ```rust let result = compose!(add_100_async -> add_100 -> with_args(10)).await; assert_eq!(210, result.unwrap()); let result = compose!(add_100_async -> add_100_async -> with_args(10)).await; assert_eq!(210, result.unwrap()); ``` It is possible to inject the seconds args of async functions using `.provide` method. This could be useful for injecting database connection or other external service interaction. See the example below ```rust // Check the '.provide' method below of injecting second args to add_async function let result = compose!(add_async.provide(100) -> add_100_async -> with_args(10)).await; assert_eq!(210, result.unwrap()); ``` Example of multiple injection to async function ```rust let result = compose!(add_3_arg_async.provide(100).provide(200) -> add_100_async -> with_args(10)).await; assert_eq!(410, result.unwrap()); ``` Example of multiple injection to async function in the second position ```rust let result = compose!(add_100 -> add_3_arg_async.provide(1).provide(1) -> with_args(10)).await; assert_eq!(112, result.unwrap()); ``` Example of multiple injection to shared reference to async function ```rust let one = &1; let result = compose!(add_3_arg_ref_async.provide(one).provide(one) -> add_3_arg_async.provide(1).provide(1) -> with_args(10)).await; assert_eq!(14, result.unwrap()); ``` ### Retry in Fn Composer Composeable macro supports retrying a function at specified interval in case of Error returned by the function. This could be useful when trying make a database call or connect to network endpoint. Make sure to install https://docs.rs/retry/latest/retry/ before proceeding with retry feature. Retry mechanism is implemented as part of composeable procedureal macro. Below is example of add_10 function configured to be retried 2 times after initial failure. ```rust use retry::delay::*; #[composeable(retry = Fixed::from_millis(100).take(2))] pub fn add_10(a: i32) -> Result { Ok(a + 10) } ``` Retry can be applied to both sync and async functions. for async functions, __all arguments to the function must be either shared reference or exclusive reference.__ Below is example of async function with retry. ```rust #[composeable(retry = Fixed::from_millis(100))] pub fn add_3_arg_ref__non_copy_async<'a>( a: &'a mut Vec, b: &'a mut Vec, c: &'a Vec, ) -> BoxFuture<'a, Result> { async move { let r = a.len() + b.len() + c.len(); Ok(r as i32) } .boxed() } ``` Apart from fixed duration retries, it is possible to configure with exponential delay. Refer to retry documentation for all available delay options https://docs.rs/retry/latest/retry/all.html