如何将 Trait 作为应用程序数据传递给 Actix Web? [英] How do I pass a Trait as application data to Actix Web?

查看:42
本文介绍了如何将 Trait 作为应用程序数据传递给 Actix Web?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个 actix-web 服务器,我可以在其中提供我的 搜索 trait 作为应用程序数据,以便在多个实现之间轻松交换或使用模拟实现进行测试.无论我尝试什么,我都无法编译它,或者当我编译它时,在 Web 浏览器中访问路由时出现以下错误:

I want to create a actix-web server where I can provide my Search trait as application data in order to easily swap between multiple implementations or use mock implementation for testing. Whatever I try I can't get it to compile or when I get it to compile I get the following error when visiting the route in the web browser:

未配置应用数据,配置使用App::data()

App data is not configured, to configure use App::data()

这是我目前所拥有的

// main.rs

use actix_web::dev::Server;
use actix_web::{get, web, App, HttpServer, Responder};

pub trait Search {
    fn search(&self, query: &str) -> String;
}

#[derive(Clone)]
pub struct SearchClient {
    base_url: String,
}

impl SearchClient {
    pub fn new() -> Self {
        Self {
            base_url: String::from("/search"),
        }
    }
}

impl Search for SearchClient {
    fn search(&self, query: &str) -> String {
        format!("Searching in SearchClient: {}", query)
    }
}

#[get("/{query}")]
async fn index(
    web::Path(query): web::Path<String>,
    search: web::Data<dyn Search>,
) -> impl Responder {
    search.into_inner().search(&query)
}

pub fn create_server(
    search: impl Search + Send + Sync + 'static + Clone,
) -> Result<Server, std::io::Error> {
    let server = HttpServer::new(move || App::new().data(search.clone()).service(index))
        .bind("127.0.0.1:8080")?
        .run();
    Ok(server)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let search_client = SearchClient::new();
    create_server(search_client).unwrap().await
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Clone)]
    pub struct TestClient;

    impl Search for TestClient {
        fn search(&self, query: &str) -> String {
            format!("Searching in TestClient: {}", query)
        }
    }

    #[actix_rt::test]
    async fn test_search() {
        let search_client = TestClient {};
        let server = create_server(search_client).unwrap();
        tokio::spawn(server);
    }
}

# Cargo.toml

[package]
name = "actix-trait"
version = "0.1.0"
edition = "2018"

[dependencies]
actix-rt = "1.1.1"
actix-web = "3.3.2"

[dev-dependencies]
tokio = "0.2.22"

推荐答案

当将 data 添加到你的 App 时,你必须指定你希望它被向下转换作为特征对象.Data 不直接接受 unsized 类型,所以你必须先创建一个 Arc ( 接受未定义大小的类型),然后将其转换为 Data.我们将使用 app_data 方法来避免将搜索器包裹在双弧中.

When adding the data to your App, you have to specify that you want it to be downcasted as a trait object. Data does not accept unsized types directly, so you have to first create an Arc (which does accept unsized types) and then convert it to a Data. We will use the app_data method to avoid wrapping the searcher in a double arc.

pub fn create_server(
    search: impl Search + Send + Sync + 'static,
) -> Result<Server, std::io::Error> {
    let search: Data<dyn Search> = Data::from(Arc::new(search));
    
    HttpServer::new(move || {
        App::new()
            .app_data(search.clone())
    })
}

async fn index(
    query: Path<String>,
    search: Data<dyn Search>,
) -> impl Responder {     
    search.into_inner().search(&*query)
}

另一种方法是使用泛型.您的处理程序和 create_server 函数在 Search 实现上将是通用的:

An alternative approach is using generics. Your handler and create_server functions would be generic over a Search implementation:

async fn index<T: Search>(
    web::Path(query): web::Path<String>,
    search: web::Data<T>,
-> impl Responder {
    search.into_inner().search(&query)
}

pub fn create_server<T: Search + Send + Sync + 'static + Clone>(
    search: T,
) -> Result<Server, std::io::Error> {
    let server = HttpServer::new(move || {
        App::new()
            .data(search.clone())
            .route("/{query}", web::get().to(index::<T>))
    })
    .bind("127.0.0.1:8080")?
    .run();
    Ok(server)
}

现在,当您在 main 中创建服务器时,您可以使用 SearchClient:

Now, when you create the server in main, you can use SearchClient:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
     let search_client = SearchClient::new();
     create_server(search_client).unwrap().await
}

当您创建用于测试目的的服务器时,您可以使用 TestClient:

And when you create the server for testing purposes, you could use a TestClient:

#[actix_rt::test]
async fn test_search() {
    let search_client = TestClient {};
    let server = create_server(search_client).unwrap();
}

基于泛型的方法的缺点是您不能使用 #[get("")] 宏进行路由,因为您必须指定处理程序的通用参数:

The downside to the generics based approach is that you cannot use the #[get("")] macros for routing because you have to specify the handler's generic parameters:

App::new()
    .route("/{query}", web::get().to(index::<T>))

这篇关于如何将 Trait 作为应用程序数据传递给 Actix Web?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆