Crate function-compose provides utilities for composing functions and way to inject arguments to functions
Composing functions
step 1
Mark a function as composeable as below. Note that the functions must always return Result type
use composeable;
step 2
use compose! macro to compose the above two functions.
let result = compose!(add_10 -> add_100 -> with_args(10));
assert_eq!(220, result.unwrap());
Argument 10(from with_args(10)). is passed to add_10 function and result of add_10 is passed to add_100
composing Async functions
It is also possible to compose sync and asycn function.
For async function, return type should be BoxedFuture(futures crate)
use function_compose::composeable;
use futures::{future::BoxFuture, FutureExt};
#[composeable()]
pub fn add_async(a: i32, b: i32) -> BoxFuture<'static, Result<i32, String>> {
async move {
let r = a + b;
Ok(r)
}.boxed()
}
Composing async and sync functions usage
use function_compose::compose;
use fn_macros::composeable;
use futures::{future::BoxFuture, FutureExt};
#[composeable()]
pub fn add_10_async(a: i32) -> BoxFuture<'static, Result<i32, String>> {
async move {
let r = a + 10;
Ok(r)
}.boxed()
}
#[composeable()]
pub fn add_10(a: i32) -> Result<i32, String> {
Ok(a + 10)
}
async fn test(){
let result = compose!(add_async.add_10_async -> add_10 -> with_args(10)).await;
assert_eq!(30, result.unwrap());
}
Injecting dependencies in multi-args function
For function with multiple arguments(say 2), One of the argument can be injected during composition itself.
Function argument injection usage
use function_compose::composeable;
use futures::{future::BoxFuture, FutureExt};
#[composeable()]
pub fn add_3_arg_async(a: i32,b: i32, c:i32) -> BoxFuture<'static, Result<i32, String>>{
async move{
let r = a + b + c;
Ok(r)
}.boxed()
}
use crate::compose;
let result = compose!(add_3_arg_async.provide(100).provide(200) -> add_10 -> with_args(10)).await;
assert_eq!(320, result.unwrap());
In the above example function add_3_arg_async, out of three arguments, 2 are injected during composing the function itself (using provide(100)) . This feature could be used for injecting connection pool or a repository instance(see the example project).
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 add https://docs.rs/retry/latest/retry/ to your project 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.
use retry::delay::*;
#[composeable(retry = Fixed::from_millis(100).take(2))]
pub fn add_10(a: i32) -> Result<i32, String> {
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.
#[composeable(retry = Fixed::from_millis(100))]
pub fn add_3_arg_ref__non_copy_async<'a>(
a: &'a mut Vec<String>,
b: &'a mut Vec<String>,
c: &'a Vec<String>,
) -> BoxFuture<'a, Result<i32, String>> {
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