Warp: passing config to endpoints

Hi,

I have this small project Cargo.toml:

[package]
name = "example"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.28"
thiserror = "1.0.15"
warp = { version = "0.2.2", features = ["tls"] }
tokio = { version = "0.2.18", features = ["full"]}
serde_derive = "1.0.106"
serde = { version = "1.0.106", features = ["derive"] }

and this main.rs:

use std::{collections::HashMap, convert::From, convert::Infallible, fmt::Display};

use anyhow::Result;
use serde::Serialize;
use thiserror::Error;
use tokio;
use warp::{http::StatusCode, reject::Reject, Filter, Rejection, Reply};

#[derive(Clone)]
struct Config {}

#[tokio::main]
async fn main() -> Result<()> {
    let cfg = Arc::new(Config {});

    let (get_cfg, post_cfg) = (cfg.clone(), cfg.clone());

    let get_fct1 = warp::path("fct1").and(warp::query()).and_then(
        |params: HashMap<String, String>| async move {
            let cfg = get_cfg.clone();
            fct1(&cfg, params)
                .and_then(|response| Ok(response))
                .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
        },
    );

    let post_fct1 = warp::path("fct1")
        .and(warp::path::end())
        .and(warp::body::form())
        .and_then(|params: HashMap<String, String>| async move {
            let cfg = post_cfg.clone();
            fct1(&cfg, params)
                .and_then(|response| Ok(response))
                .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
        });

    let get_routes = warp::get().and(get_fct1);
    let post_routes = warp::post().and(post_fct1);

    let routes = get_routes.or(post_routes).recover(customize_error);

    warp::serve(routes).run(([127, 0, 0, 1], 4321)).await;
    Ok(())
}

fn fct1(cfg: &Config, p: HashMap<String, String>) -> Result<impl Reply> {
    Ok(warp::reply())
}

async fn customize_error(err: Rejection) -> Result<impl Reply, Infallible> {
    let code;
    let message;

    if let Some(server_error) = err.find::<ServerError>() {
        code = StatusCode::INTERNAL_SERVER_ERROR;
        message = server_error.msg.to_owned();
    } else {
        code = StatusCode::INTERNAL_SERVER_ERROR;
        message = "UNHANDLED_REJECTION".to_string();
    }
    Ok(warp::reply::with_status(message, code))
}

#[derive(Debug, Clone, Error, Serialize, PartialEq)]
pub struct ServerError {
    #[serde(skip)]
    pub status: StatusCode,
    pub msg: String,
}

impl Reject for ServerError {}

impl Display for ServerError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{} ({})", self.msg, self.status)
    }
}

impl ServerError {
    pub fn new(status: StatusCode, msg: &str) -> Self {
        ServerError {
            status,
            msg: msg.to_owned(),
        }
    }
}

impl From<anyhow::Error> for ServerError {
    fn from(err: anyhow::Error) -> Self {
        let e = match err.downcast::<ServerError>() {
            Ok(e) => return e,
            Err(e) => e,
        };

        ServerError::new(
            StatusCode::INTERNAL_SERVER_ERROR,
            &format!("Unhandled error type: {:#?}", e),
        )
    }
}

I'm trying to do is to pass my Config object to fct1 but it gives me these errors:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`    
  --> src\main.rs:19:9
   |
18 |       let get_fct1 = warp::path("fct1").and(warp::query()).and_then(
   |                                                            -------- the requirement to implement `Fn` derives from here
19 |           |params: HashMap<String, String>| async move {
   |  _________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_-
   | |         |
   | |         this closure implements `FnOnce`, not `Fn`
20 | |             let cfg = get_cfg.clone();
21 | |             fct1(&cfg, params)
22 | |                 .and_then(|response| Ok(response))
23 | |                 .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
24 | |         },
   | |_________- closure is `FnOnce` because it moves the variable `get_cfg` out of its environment       

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src\main.rs:30:19
   |
30 |           .and_then(|params: HashMap<String, String>| async move {
   |  __________--------_^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_-
   | |          |        |
   | |          |        this closure implements `FnOnce`, not `Fn`    
   | |          the requirement to implement `Fn` derives from here    
31 | |             let cfg = post_cfg.clone();
32 | |             fct1(&cfg, params)
33 | |                 .and_then(|response| Ok(response))
34 | |                 .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
35 | |         });
   | |_________- closure is `FnOnce` because it moves the variable `post_cfg` out of its environment      

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src\main.rs:19:9
   |
19 |           |params: HashMap<String, String>| async move {
   |  _________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_-
   | |         |
   | |         this closure implements `FnOnce`, not `Fn`
20 | |             let cfg = get_cfg.clone();
21 | |             fct1(&cfg, params)
22 | |                 .and_then(|response| Ok(response))
23 | |                 .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
24 | |         },
   | |_________- closure is `FnOnce` because it moves the variable `get_cfg` out of its environment
...
37 |       let get_routes = warp::get().and(get_fct1);
   |                                    --- the requirement to implement `Fn` derives from here

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src\main.rs:30:19
   |
30 |           .and_then(|params: HashMap<String, String>| async move {
   |  ___________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_-
   | |                   |
   | |                   this closure implements `FnOnce`, not `Fn`
31 | |             let cfg = post_cfg.clone();
32 | |             fct1(&cfg, params)
33 | |                 .and_then(|response| Ok(response))
34 | |                 .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
35 | |         });
   | |_________- closure is `FnOnce` because it moves the variable `post_cfg` out of its environment
...
38 |       let post_routes = warp::post().and(post_fct1);
   |                                      --- the requirement to implement `Fn` derives from here   

error: aborting due to 4 previous errors

How am I suppose to pass Config to the endpoints' function?

(question also posted on reddit)

You need to do the clone in the closure; not in the async block.

let post_fct1 = warp::path("fct1")
    .and(warp::path::end())
    .and(warp::body::form())
    .and_then(move |params: HashMap<String, String>| {
        let cfg = post_cfg.clone();
        async move {
            fct1(&cfg, params)
                .and_then(|response| Ok(response))
                .or_else(|e| Err(warp::reject::custom::<ServerError>(e.into())))
        }
    });
2 Likes

It was an async block, I though it was an async closure ^^'

Async closures don't exist in stable rust.

1 Like

Oh, good to know, thanks!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.