user63898
user63898

Reputation: 30885

Expected `()`, found `Result<ServiceResponse<B>, _>` using middleware in actix_web trying to return Ok

I'm trying to accomplish a very simple task here. I don't understand why it has become so complex. I'm attempting to modify headers before they reach the service and return a failure if a certain condition is not met.

Here is my middleware file:

use std::future::{ready, Ready};

use actix_http::header::{HeaderValue, HeaderName};
use actix_web::{
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    Error,http::Method
};
use futures_util::future::LocalBoxFuture;
//use crate::constants;

pub struct Heartbeat;

impl<S, B> Transform<S, ServiceRequest> for Heartbeat
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = HeartMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(HeartMiddleware { service }))
    }
}

pub struct HeartMiddleware<S> {
    service: S,
}


impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        

        let fut = self.service.call(req);
        
        

        Box::pin(async move {
            
            let mut res = fut.await?;
            let headers = res.headers_mut();
            headers.insert(
                 HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
             );
            
            if (Method::POST == req.method() || 
                 Method::GET  == req.method()  ||
                 Method::HEAD == req.method()) && req.path() == "/ping" {
                 Ok(res)
            }
             
            Err(actix_web::error::ErrorImATeapot("TeaPot"))
        })
    }
}

and the main file :

use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
use serde::{Deserialize,Serialize};
use actix_cors::Cors;

mod heartbeat;

static PORT :u16 = 9091;

#[get("/")]
async fn index()->impl Responder {
    
    HttpResponse::Ok().body("template")
}

 

#[derive(Serialize)]
pub struct Response {
    pub message: String,
}

async fn not_found() ->Result<HttpResponse> {
    let response = Response {
        message: "Resource not found".to_string(),
    };
    Ok(HttpResponse::NotFound().json(response))
}


#[actix_web::main]
async fn main()-> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "debug");
    env_logger::init();
    HttpServer::new(|| App::new()
    .wrap(
        Cors::default()
            .allowed_origin("http://*") // Allow all http origins
            .allowed_origin("https://*") // Allow all https origins
            .allowed_methods(vec!["GET","POST","PUT","DELETE","OPTIONS"])
            .allowed_headers(vec![http::header::AUTHORIZATION,http::header::CONTENT_TYPE,
                http::header::ACCEPT,http::header::LINK])
            .allowed_header("X-CSRF-TOKEN")
            .supports_credentials()
            .max_age(300)
    )
    .wrap(heartbeat::Heartbeat)
    .service(index)
    .service(web::resource("/ping"))
    .default_service(web::route().to(not_found)))
    .bind(("127.0.0.1",PORT))?
    .run()
    .await        
}


When I compile it, I'm getting:

cargo build
   Compiling broker-service v0.1.0 (C:\dev\my\rust\workspace\broker-service)
warning: unused import: `HttpRequest`
 --> src\main.rs:1:52
  |
1 | use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
  |                                                    ^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `Deserialize`
 --> src\main.rs:2:13
  |
2 | use serde::{Deserialize,Serialize};
  |             ^^^^^^^^^^^

error[E0308]: mismatched types
  --> src\heartbeat.rs:65:18
   |
62 | /             if (Method::POST == req.method() ||
63 | |                  Method::GET  == req.method()  ||
64 | |                  Method::HEAD == req.method()) && req.path() == "/ping" {
65 | |                  Ok(res)
   | |                  ^^^^^^^ expected `()`, found `Result<ServiceResponse<B>, _>`
66 | |             }
   | |_____________- expected this to be `()`
   |
   = note: expected unit type `()`
                   found enum `Result<ServiceResponse<B>, _>`
note: return type inferred to be `()` here
  --> src\heartbeat.rs:56:27
   |
56 |             let mut res = fut.await?;
   |                           ^^^^^^^^^^

For more information about this error, try `rustc --explain E0308`.
warning: `broker-service` (bin "broker-service") generated 2 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 2 warnings emitted

Update

After updating the solution suggested, I am still getting an error:

use std::future::{ready, Ready};

use actix_http::header::{HeaderValue, HeaderName};
use actix_web::{
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    Error,http::Method
};
use futures_util::future::LocalBoxFuture;
//use crate::constants;

pub struct Heartbeat;

impl<S, B> Transform<S, ServiceRequest> for Heartbeat
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = HeartMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(HeartMiddleware { service }))
    }
}

pub struct HeartMiddleware<S> {
    service: S,
}


impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        

        let fut = self.service.call(req);
        
        

        Box::pin(async move {
            
            let mut res = fut.await?;
            let headers = res.headers_mut();
            headers.insert(
                 HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
             );
            
            if (Method::POST == req.method() || 
                 Method::GET  == req.method()  ||
                 Method::HEAD == req.method()) && req.path() == "/ping" {
                 return Ok(res)
            }
             
            Err(actix_web::error::ErrorImATeapot("TeaPot"))
        })
    }
}
cargo build
   Compiling broker-service v0.1.0 (C:\dev\my\rust\workspace\broker-service)
warning: unused import: `HttpRequest`
 --> src\main.rs:1:52
  |
1 | use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
  |                                                    ^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `Deserialize`
 --> src\main.rs:2:13
  |
2 | use serde::{Deserialize,Serialize};
  |             ^^^^^^^^^^^

error[E0382]: use of moved value: `req`
  --> src\heartbeat.rs:54:18
   |
47 |       fn call(&self, req: ServiceRequest) -> Self::Future {
   |                      --- move occurs because `req` has type `ServiceRequest`, which does not implement the `Copy` trait
...
50 |           let fut = self.service.call(req);
   |                                       --- value moved here
...
54 |           Box::pin(async move {
   |  __________________^
55 | |
56 | |             let mut res = fut.await?;
57 | |             let headers = res.headers_mut();
...  |
62 | |             if (Method::POST == req.method() ||
   | |                                 --- use occurs due to use in generator
...  |
68 | |             Err(actix_web::error::ErrorImATeapot("TeaPot"))
69 | |         })
   | |_________^ value used here after move

For more information about this error, try `rustc --explain E0382`.
warning: `broker-service` (bin "broker-service") generated 2 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 2 warnings emitted

Upvotes: 1

Views: 296

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70860

Your if is a statement, not an expression. You're basically telling the compiler "execute this if and throw away the result, then return Err". The result must be of type ().

You want an explicit return:

Box::pin(async move {
    let mut res = fut.await?;
    let headers = res.headers_mut();
    headers.insert(
        HeaderName::from_static("Content-Type"),
        HeaderValue::from_static("text/plain"),
    );

    if (Method::POST == req.method()
        || Method::GET == req.method()
        || Method::HEAD == req.method())
        && req.path() == "/ping"
    {
        return Ok(res);
    }

    Err(actix_web::error::ErrorImATeapot("TeaPot"))
})

Or add an else, to make the whole thing an expression:

Box::pin(async move {
    let mut res = fut.await?;
    let headers = res.headers_mut();
    headers.insert(
        HeaderName::from_static("Content-Type"),
        HeaderValue::from_static("text/plain"),
    );

    if (Method::POST == req.method()
        || Method::GET == req.method()
        || Method::HEAD == req.method())
        && req.path() == "/ping"
    {
        Ok(res)
    } else {
        Err(actix_web::error::ErrorImATeapot("TeaPot"))
    }
})

Upvotes: 1

Related Questions