#回调 # #契约设计

callback_fn

回调函数库

1 个不稳定版本

0.1.0 2024年4月22日

#353过程宏

MIT 许可协议

15KB
175

Rust的回调函数

callback_fn 是一个库,可以在目标函数之前、之后和周围添加函数。

特性

  • 自定义函数:用户可以指定在目标函数之前、之后和周围执行的函数。
  • 无缝集成:与现有代码库无缝集成。
  • 错误处理:处理回调函数中发生的错误

使用

  • 回调函数:在目标函数前后添加函数。
    • 适用于日志记录、身份验证和其他横切关注点等任务。
  • 契约设计:向目标函数添加前置条件和后置条件。
    • 可以应用特定条件,例如仅在测试时、使用功能标志。

安装

将 callback_fn 添加到您的 Cargo.toml

[dependencies]
callback_fn = "0.1.0"

示例

对于回调

用户创建后,将创建用户缓存。

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<User, String> {
        let user = User { name };
        self.user_repository.save(user.clone()).await?;
        Ok(user)
    }

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

对于日志记录

在目标函数周围添加日志。

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");
}

对于身份验证

在 UseCase 函数之前添加身份验证。

use callback_fn::before_callback;
use strum_macros::Display;

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

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

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

#[derive(Debug, Display, PartialEq)]
pub enum Permission {
    ReadPost,
    CreatePost,
}

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

#[derive(Debug, PartialEq)]
struct Post {
    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),
}

对于契约设计

在添加到购物车或清理后,确保 total_price 是正确的。

use callback_fn::around_callback;

struct Cart {
    total_price: usize,
    items: Vec<Item>,
}
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())
        }
    }
}

仅在特定功能中使用

如果您只想在特定功能中使用 callback_fn,可以使用 cfg_attr

use callback_fn::after_callback;

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

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

如果您运行 cargo run --features test,foo 函数将如下所示。

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"));
    };
}

如果您运行 cargo run,foo 函数将如下所示。

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

依赖项

~225–660KB
~16K SLoC