salvo-rs/salvo

Support converting multiple methods into handlers.

Closed this issue · 2 comments

@chrislearn The following is my implementation. If you think it's good, you can consider integrating it into the official package:

use salvo::oapi::extract::*;
use salvo::prelude::*;
use salvo_macro_ext::craft;

#[derive(Clone)]
pub struct Service {
    state: i64,
}

#[craft]
impl Service {
    fn new(state: i64) -> Self {
        Self { state }
    }
    /// doc line 1
    /// doc line 2
    #[salvo_macro_ext::craft(handler)]
    fn add1(&self, left: QueryParam<i64, true>, right: QueryParam<i64, true>) -> String {
        (self.state + *left + *right).to_string()
    }

    #[craft(handler)]
    pub(crate) fn add2(
        self: ::std::sync::Arc<Self>,
        left: QueryParam<i64, true>,
        right: QueryParam<i64, true>,
    ) -> String {
        (self.state + *left + *right).to_string()
    }

    #[craft(handler)]
    pub fn add3(left: QueryParam<i64, true>, right: QueryParam<i64, true>) -> String {
        (*left + *right).to_string()
    }
}

Code after macro expansion:

#[derive(Clone)]
impl Service {
    fn new(state: i64) -> Self {
        Self { state }
    }

    /// doc line 1
    /// doc line 2
    fn add1(&self) -> impl Handler {
        pub struct handle(::std::sync::Arc<Service>);

        impl ::std::ops::Deref for handle {
            type Target = Service;

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
        #[handler]
        impl handle {
            /// doc line 1
            /// doc line 2
            async fn handle(
                &self,
                left: QueryParam<i64, true>,
                right: QueryParam<i64, true>
            ) -> String {
                (self.state + *left + *right).to_string()
            }
        }
        handle(::std::sync::Arc::new(self.clone()))
    }

    fn add2(self: &::std::sync::Arc<Self>) -> impl Handler {
        pub struct handle(::std::sync::Arc<Service>);

        impl ::std::ops::Deref for handle {
            type Target = Service;

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
        #[handler]
        impl handle {
            async fn handle(
                &self,
                left: QueryParam<i64, true>,
                right: QueryParam<i64, true>
            ) -> String {
                (self.state + *left + *right).to_string()
            }
        }
        handle(self.clone())
    }

    fn add3() -> impl Handler {
        #[handler]
        async fn add3(left: QueryParam<i64, true>, right: QueryParam<i64, true>) -> String {
            (*left + *right).to_string()
        }
        add3
    }
}

Sure, you can also replace #[craft(handler)] with #[craft(endpoint(...))].

NOTE: If the receiver of a method is &self, you need to implement the Clone trait for the type.

Repository: https://github.com/andeya/salvo_macro_ext

Example detail: https://github.com/andeya/salvo_macro_ext/blob/main/examples/add.rs

我们是这样去实现的,涉及协同开发的时候:

pub trait RouterContext {

    fn server(&self) -> Option<Router> {   None    }

    fn server_with_anonymous(&self) -> Option<Router> {    None    }

    fn openapi(&self) -> Option<Router> {    None    }

    fn poseidon(&self) -> Option<Router> {    None   }
}

有个统一注册中心,统一管理:

fn router_context_register() -> Vec<Box<dyn RouterContext>> {
    vec![
        Box::new(AssetDataApi),
        Box::new(CategoryApi),
        Box::new(HealthCheckApi),
  ]
}

pub async fn create_service(context: Arc<ApplicationContext>) -> AppResult<Service> {
    let routers = router_context_register();
    let (mut sr, so) = merge_router(&routers, &context, |c| c.server());
    let (mut ar, ao) = merge_router(&routers, &context, |c| c.server_with_anonymous());
    let (or, oo) = merge_router(&routers, &context, |c| c.openapi());
    let (po, _) = merge_router(&routers, &context, |c| c.poseidon());

    // .......
 
    Ok(Service::new(all_routers)
        .hoop(ContextInject { context })
        .hoop(TraceLogger)
        .catcher(SalvoErrorCatcher::catcher()))
}

最后是api层:

pub struct AssetDataApi;

#[endpoint(tags("xxx"), parameters(("id", description = "ID")))]
async fn identity(id: PathParam<i64>, depot: &mut Depot) -> AppResult<ResponseResult<'static, Option<String>>> {
    let ctx = obtain_context(depot)?;
    let store_identity = AssetDataService::identity(ctx, id.into_inner()).await?;
    Ok(ResponseResult::ok(store_identity))
}

impl RouterContext for AssetDataApi {
    fn server(&self) -> Option<Router> {
        let router = Router::with_path("asset-data")
            .push(Router::with_path("sample").get(sample))
            .push(Router::with_path("batch/sample").post(batch_sample))
            .push(Router::with_path("statistical-chart").get(statistical_chart))
            .push(Router::with_path("batch-recent").post(batch_recent))
            .push(Router::with_path("page").get(page))
            .push(Router::with_path("repair").post(repair_asset_data))
            .push(Router::with_path("statistics").get(statistics))
            .push(Router::with_path("<id>").push(Router::with_path("identity").get(identity)));
        Some(router)
    }
}

应该也能满足团队协作的情况吧

当然可以设计一个全局的状态,通过宏的方式将所有路由全部加进去,而不是像现在需要手动添加。