An open API service indexing awesome lists of open source software.

https://github.com/poi2/callback_fn

Callback function for Rust
https://github.com/poi2/callback_fn

rust rust-macro

Last synced: 11 months ago
JSON representation

Callback function for Rust

Awesome Lists containing this project

README

          

# Callback function for Rust

callback_fn is a library that adds functions before, after and around the target function.

## Features

- **Custom Functions**: Users can specify custom functions to be executed before, after, and around the target function.
- **Seamless Integration**: Integrates seamlessly into existing codebases.
- **Error Handling**: Handles errors that occur within the callback functions

## Uses

- **Callback Functions**: Add functions before and after the target function.
- Useful for tasks such as logging, authentication, and other cross-cutting concerns.
- **Design-by-Contracts**: Add pre-conditions and post-conditions to the target function.
- Specific conditions can be applied, such as only when testing, using the features flag.

## Installation

Add callback_fn to your `Cargo.toml`.

```toml
[dependencies]
callback_fn = "0.1.0"
```

## Examples

### For callback

After user created, user cache will be created.

```rust
use callback_fn::after_callback;

#[allow(dead_code)]
#[derive(Clone, Debug)]
struct User {
name: String,
}

struct UserRepository {}
impl UserRepository {
async fn save(&self, _user: User) -> Result<(), String> {
tokio::time::sleep(Duration::from_micros(1)).await;
Ok(())
}
}

struct UserCache {}
impl UserCache {
async fn save(&self, _user: User) -> Result<(), String> {
tokio::time::sleep(Duration::from_micros(1)).await;
Ok(())
}
}

struct UserUseCase {
user_repository: UserRepository,
user_cache: UserCache,
}

impl UserUseCase {
#[after_callback(let _ = self.create_user_cache(ret.clone()?).await?)]
async fn create_user(&self, name: String) -> Result {
let user = User { name };
self.user_repository.save(user.clone()).await?;
Ok(user)
}

async fn create_user_cache(&self, user: User) -> Result {
self.user_cache.save(user.clone()).await?;
Ok(user)
}
}
```

### For logging

Add logging around the target function.

```rust
use callback_fn::around_callback;

#[around_callback(my_logger())]
fn hello(str: &str) {
println!("Hello {}", str);
}

fn my_logger() {
println!("{}", chrono::Local::now());
}

// hello will print:
//
// 2024-04-01T00:00:000.000000+09:00
// Hello world
// 2024-04-01T00:00:000.000100+09:00
#[test]
fn test_hello() {
hello("world");
}
```

### For Authentication

Add authentication before UseCase function.

```rust
use callback_fn::before_callback;
use strum_macros::Display;

#[before_callback(has_permission(current_user, Permission::ReadArticle).map_err(UseCaseError::from)?)]
fn get_article_by_id(current_user: &User, id: usize) -> Result {
Ok(Article {
id,
title: "Dummy Title".to_string(),
body: "Dummy Body".to_string(),
})
}

#[before_callback(has_permission(current_user, Permission::CreateArticle).map_err(UseCaseError::from)?)]
fn create_article(current_user: &User, title: String, body: String) -> Result {
Ok(Article { id: 1, title, body })
}

#[derive(Debug)]
struct User {
permissions: Vec,
}

#[derive(Debug, Display, PartialEq)]
pub enum Permission {
ReadArticle,
CreateArticle,
}

fn has_permission(user: &User, permission: Permission) -> Result<(), PermissionError> {
if user.permissions.contains(&permission) {
Ok(())
} else {
Err(PermissionError::PermissionDenied(permission))
}
}

#[derive(Debug, PartialEq)]
struct Article {
id: usize,
title: String,
body: String,
}

#[derive(thiserror::Error, Debug)]
pub enum PermissionError {
#[error("User don't have {0} permission.")]
PermissionDenied(Permission),
}

#[derive(thiserror::Error, Debug)]
pub enum UseCaseError {
#[error("PermissionError: {0}")]
PermissionError(#[from] PermissionError),
}
```

### For Design-by-contract

After adding to the cart or cleaning, ensure if the total_price is correct.

```rust
use callback_fn::around_callback;

struct Cart {
total_price: usize,
items: Vec,
}
struct Item {
price: usize,
}

impl Cart {
fn new() -> Self {
Self {
total_price: 0,
items: vec![],
}
}

// Ensure total_price is correct around add_item.
// Error handling is available in runtime when conditions are not ensure.
#[around_callback(self.ensure_total_price()?)]
fn add_item(&mut self, item: Item) -> Result<(), String> {
self.items.push(item);
self.update_total_price();
Ok(())
}

fn update_total_price(&mut self) {
self.total_price = self.items.iter().map(|item| item.price).sum()
}

fn ensure_total_price(&self) -> Result<(), String> {
if self.total_price == self.items.iter().map(|item| item.price).sum() {
Ok(())
} else {
Err("Total price is not correct".to_string())
}
}
}
```

### Use only in specific features

If you want to use callback_fn only in specific features, you can use `cfg_attr`.

```rust
use callback_fn::after_callback;

#[cfg_attr(test, after_callback(bar()))]
fn foo() {
println!("foo");
}

fn bar() {
println!("bar");
}
```

If you run `cargo run --features test`, foo function will be like below.

```rust
fn foo() {
#[allow(unused_mut)]
let mut ret = {
{
::std::io::_print(format_args!("foo\n"));
};
};
bar();
ret
}
fn bar() {
{
::std::io::_print(format_args!("bar\n"));
};
}
```

If you run `cargo run`, foo function will be like below.

```rust
fn foo() {
{
::std::io::_print(format_args!("foo\n"));
};
}
fn bar() {
{
::std::io::_print(format_args!("bar\n"));
};
}
```