0

I am trying to implement a filter that sits in all my routes and extracts a header and matches a possible token to what is stored on my system.

I want to implement something like the warp rejection example but I get the error

expected a closure that implements the Fn trait, but this closure only implements FnOnce closure is FnOnce because it moves the variable tmp out of its environment

I kind of get what the compiler saying, but don't know how to solve it. I thought doing let tmp = store.clone() would.

I have the filter:

pub fn haystack_auth_header(store: Store) -> impl Filter<Extract = (Store,), Error = Rejection> + Clone {

   let tmp = store.clone();

    warp::header("Authorization").and_then (|auth_header: String| async move {

        // Authorization: BEARER authToken=xxxyyyzzz
        let result = auth_token(&auth_header); //-> IResult<&'a str, (&'a str, &'a str), (&'a str, ErrorKind)> {

        if result.is_err() {
            return Err(reject::custom(HayStackAuthToken));
        }

        let (_, key_value) = result.unwrap();

        let auth_token_result = tmp.read().get_authtoken();

        if auth_token_result.is_err() {
            return Err(reject::custom(HayStackAuthToken));
        }

        let auth_token_option = auth_token_result.unwrap();

        if auth_token_option.is_none() {
            return Err(reject::custom(HayStackAuthToken));
        }

        let auth_token = auth_token_option.unwrap();

        if auth_token != key_value.1 {
            return Err(reject::custom(HayStackAuthToken));
        }

        Ok(tmp)
    })
}

store is type Store = Arc<RwLock<Box<dyn UserAuthStore>>> and UserAuthStore is trait UserAuthStore: fmt::Debug + Send + Sync.

UserAuthStore is defined as

pub trait UserAuthStore: fmt::Debug + Send + Sync {

    // Return handshake token for username. If user has no handshake token generate one
    fn get_handshake_token(&self, username: &str) -> HaystackResult<String>;
    fn get_username(&self, handshake_token: &str) -> HaystackResult<String>;

    fn set_temporary_value(&mut self, k: &str, v: &str) -> HaystackResult<()>;
    fn get_temporary_value(&self,  k: &str) -> HaystackResult<Option<&String>>;

    fn set_authtoken(&mut self, s: String) -> HaystackResult<()>;

    /// returns a base64 encoded sha256 salt of password.
    fn get_password_salt(&self) -> HaystackResult<String>;
    fn get_salted_password(&self) -> HaystackResult<String>;
    fn get_authtoken(&self) -> HaystackResult<Option<String>>;
}

Why does clone not work here?

The full error is

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
    --> src/server/mod.rs:997:45
     |
997  |       warp::header("Authorization").and_then (|auth_header: String| async move {
     |  ___________________________________--------__^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_-
     | |                                   |         |
     | |                                   |         this closure implements `FnOnce`, not `Fn`
     | |                                   the requirement to implement `Fn` derives from here
998  | |
999  | |         // Authorization: BEARER authToken=xxxyyyzzz
1000 | |         let result = auth_token(&auth_header); //-> IResult<&'a str, (&'a str, &'a str), (&'a str, ErrorKind)> {
...    |
1026 | |         Ok(tmp.clone())
1027 | |     })
     | |_____- closure is `FnOnce` because it moves the variable `tmp` out of its environment

You can see a simplified test case here

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
Glenn Pierce
  • 720
  • 1
  • 6
  • 18
  • Please [edit] your question and paste the exact and entire error that you're getting — that will help us to understand what the problem is so we can help best. Sometimes trying to interpret an error message is tricky and it's actually a different part of the error message that's important. Please use the message from running the compiler directly, not the message produced by an IDE, which might be trying to interpret the error for you. – Shepmaster Jul 28 '20 at 14:39
  • It's hard to answer your question because it doesn't include a [MRE]. We can't tell what crates (and their versions), types, traits, fields, etc. are present in the code. It would make it easier for us to help you if you try to reproduce your error on the [Rust Playground](https://play.rust-lang.org) if possible, otherwise in a brand new Cargo project, then [edit] your question to include the additional info. There are [Rust-specific MRE tips](//stackoverflow.com/tags/rust/info) you can use to reduce your original code for posting here. Thanks! – Shepmaster Jul 28 '20 at 14:39
  • *`store` is ... and `UserAuthStore` is* — please don't describe code solely using prose. Instead, provide a [MRE] that includes all the definitions needed to reproduce the error but nothing else. For example, the majority of your filter's logic can probably be replaced with a hard-coded value like `true`. – Shepmaster Jul 28 '20 at 14:44
  • I assume the problem is that you have `let tmp = store.clone()` outside instead of inside the closure, but it's hard to say without an example that I can compile myself – msrd0 Jul 28 '20 at 14:52
  • This is almost certainly a duplicate of [Is there another option to share an Arc in multiple closures besides cloning it before each closure?](https://stackoverflow.com/q/31360003/155423) – Shepmaster Jul 28 '20 at 15:01
  • 1
    I see that you are editing to add more information, but it appears that you are ignoring the request to produce a **minimal** example. Please do not ignore that. – Shepmaster Jul 28 '20 at 15:04
  • [To make Stack Overflow a useful resource for future visitors beyond the context of your repository](https://meta.stackoverflow.com/q/380194/155423), please [edit] your question to add a [MRE] in the question itself, in addition to the link to your repository. – Shepmaster Jul 28 '20 at 16:52

2 Answers2

3

After creating a simple test case I managed to get it going with the following function.

pub fn haystack_auth_header(store: Store) -> impl Filter<Extract = (Store,), Error = Rejection> + Clone {

    warp::header("Authorization").and_then (
        
            move |auth_header: String| 
            {
                let tmp = store.clone();
                async move {

                    let tmp = tmp.clone();

                    if tmp.read().get_authtoken().is_none() {
                        return Err(reject::custom(HayStackAuthToken));   
                    }
                    
                    Ok(tmp.clone())
                }
            }
    )
}

So in the end just needed clone in the correct place.

Glenn Pierce
  • 720
  • 1
  • 6
  • 18
0

This question suggests that you don't have yet a clear distinction about the fn hierarchy: enter image description here taken from here.

Warp's filter function need to implement the Fn trait. This means that you cannot access the outer context(read: environment).

The first time AndThen call your closure, the function will consume the "only" tmp variable available, thus it won't be available for future calls. Since the closure it's taking ownership of its context, it means it's implementing FnOnce.

As suggested by @msrd0 in the comment, probably this is solvable by moving that call in the function, but I would need an MRE to be certain about it.

Federico Ponzi
  • 2,682
  • 4
  • 34
  • 60