Hi,
I've been experimenting with using the tower crate(s) to build an internal service because the Service and Layer abstraction seem like a good design. However, I'm struggling with how to return errors from layers because I can't get the returned future types to match the type alias on Service::Future
. The following is a simple example using the HelloWorld and LogService examples from the documentation. On line 76 I've added a synthetic fault injection to demonstrate a layer returning an error.
use hyper::{Request, Response};
use std::fmt;
use tower::{Layer, Service};
use http::StatusCode;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tower::ServiceBuilder;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let log_layer = LogLayer{target: "foobar"};
let api_service = HelloWorld{};
let mut svc = ServiceBuilder::new().layer(log_layer).service(api_service);
let request = Request::builder()
.uri("https:// www. rust-lang. org/")
.header("User-Agent", "my-awesome-agent/ 1.0")
.body(vec![0u8, 1u8, 2u8]).unwrap();
let _ = svc.call(request).await;
Ok(())
}
pub struct LogLayer {
target: &'static str,
}
impl<S> Layer<S> for LogLayer {
type Service = LogService<S>;
fn layer(&self, service: S) -> Self::Service {
LogService {
target: self.target,
service
}
}
}
// This service implements the Log behavior
pub struct LogService<S> {
target: &'static str,
service: S,
}
impl<S, Request> Service<Request> for LogService<S>
where
S: Service<Request>,
Request: fmt::Debug,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
// inject synthetic failure
let current_time = chrono::offset::Local::now();
if current_time.timestamp_millis() % 100 == 0 {
// This line won't compile
return Box::pin(std::future::ready(Err("oh noes".to_string())));
}
// Insert log statement here or other functionality
println!("request = {:?}, target = {:?}", request, self.target);
self.service.call(request)
}
}
#[derive(Clone)]
struct HelloWorld;
impl Service<Request<Vec<u8>>> for HelloWorld {
type Response = Response<Vec<u8>>;
type Error = String;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: Request<Vec<u8>>) -> Self::Future {
// create the body
let body: Vec<u8> = "hello, world!\n".as_bytes().to_owned();
// Create the HTTP response
let resp = Response::builder()
.status(StatusCode::OK)
.body(body)
.expect("Unable to create `http::Response`");
// create a response in a future.
let fut = async { Ok(resp) };
// Return the response as an immediate future
Box::pin(fut)
}
}
The error I receive from this is:
note: expected associated type `<S as tower::Service<Request>>::Future`
found struct `Pin<Box<std::future::Ready<Result<_, String>>>>`
I've tried a few variants for how to return the error future, but it's not clear to me the right way to make this compatible with the future type. I've tried to find examples for how to get this to work, but nothing has popped up so far.
Any suggestions would be helpful, thanks!