如何将特征作为应用程序数据传递给 Actix Web?
How do I pass a Trait as application data to Actix Web?
我想创建一个 actix-web 服务器,我可以在其中提供我的 Search
特征作为应用程序数据,以便在多个实现之间轻松交换或使用模拟实现进行测试。无论我尝试什么,我都无法编译它,或者当我编译它时,在网络浏览器中访问路由时出现以下错误:
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
不直接接受未调整大小的类型,因此您必须先创建一个 Arc
(),然后将其转换为 Data
。我们将使用 app_data
方法来避免将搜索器包裹在双弧中。
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
实现上是通用的:
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
:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let search_client = SearchClient::new();
create_server(search_client).unwrap().await
}
当您创建用于测试目的的服务器时,您可以使用 TestClient
:
#[actix_rt::test]
async fn test_search() {
let search_client = TestClient {};
let server = create_server(search_client).unwrap();
}
基于泛型的方法的缺点是您不能使用 #[get("")]
宏进行路由,因为您必须指定处理程序的通用参数:
App::new()
.route("/{query}", web::get().to(index::<T>))
我想创建一个 actix-web 服务器,我可以在其中提供我的 Search
特征作为应用程序数据,以便在多个实现之间轻松交换或使用模拟实现进行测试。无论我尝试什么,我都无法编译它,或者当我编译它时,在网络浏览器中访问路由时出现以下错误:
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
不直接接受未调整大小的类型,因此您必须先创建一个 Arc
(Data
。我们将使用 app_data
方法来避免将搜索器包裹在双弧中。
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
实现上是通用的:
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
:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let search_client = SearchClient::new();
create_server(search_client).unwrap().await
}
当您创建用于测试目的的服务器时,您可以使用 TestClient
:
#[actix_rt::test]
async fn test_search() {
let search_client = TestClient {};
let server = create_server(search_client).unwrap();
}
基于泛型的方法的缺点是您不能使用 #[get("")]
宏进行路由,因为您必须指定处理程序的通用参数:
App::new()
.route("/{query}", web::get().to(index::<T>))