[SOLVED] Building a future before an execution


#1

I have an issue with building a future, that will be returned to a caller and fired in run-time per each client request. The main idea is pretty simple:

  1. Extract a token
  2. Create a “connection” with a Redis instance.
  3. Before executing a command, probably necessary to AUTH with the Redis.
  4. Build a expression, so, that we will extract a “user_id” by its token, process the result and validate a passed token. Otherwise will return an error, if something goes wrong.
  5. Return a caller future that will return a certain result: Ok(()) or a PathfinderError::AuthenticationError.

In my case, I’m stuck with a 3rd point, when necessary to authenticate with Redis. After sending a command and processing an outcome, I’d like to return a connection, so that it would be reused later with chained map/map_err calls and so on, but getting an errors on a compiling stage.

Code:

pub type MiddlewareFuture = Box<Future<Item=(), Error=PathfinderError> + 'static>;

impl Middleware for JwtTokenMiddleware {
    fn process_request(&self, message: &JsonMessage, handle: &Handle) -> MiddlewareFuture {
        let token = match message["token"].as_str() {
            Some(token) => String::from(token),
            None => {
                return Box::new(lazy(move || {
                    let message = String::from("Token was not specified.");
                    Err(PathfinderError::AuthenticationError(message))
                }))
            }
        };

        let redis_socket_address = self.redis_address.parse().unwrap();
        let redis_connection = match self.redis_password {
            // AUTH with Redis here, before invoking any command 
            Some(ref password) => {
                let password_inner = password.clone();
                paired_connect(&redis_socket_address, handle)
                    .and_then(|connection| {
                        connection.send::<String>(resp_array!["AUTH", password_inner])
                            .map(|_| connection)
                    })
                    .map_err(|_| {
                        let message = String::from("A technical issue with Redis...");
                        println!("{}", message);
                        PathfinderError::AuthenticationError(message)
                    })
                    .map(|connection| connection)
            },
            // AUTH no needed, so left it as is
            _ => paired_connect(&redis_socket_address, handle)
        };

        let token_inner = token.clone();
        let validation_struct = self.get_validation_struct();
        let jwt_secret_inner = self.jwt_secret.clone();
        Box::new(
            redis_connection
                // Get the User ID from Redis by the token
                .and_then(move |connection| {
                    connection.send::<String>(resp_array!["GET", token])
                })
                // Connection issue or token is already deleted
                .map_err(|_| {
                    let message = String::from("Token is expired.");
                    PathfinderError::AuthenticationError(message)
                })
                // Extracted user_id used here for additional JWT validation
                .map(move |user_id| {
                    let mut validation_struct_inner = validation_struct.clone();
                    validation_struct_inner.set_audience(&user_id);
                    validate(&token_inner, &jwt_secret_inner, &validation_struct)
                })
                // The passed token is expired or has an invalid data
                .map_err(|_| {
                    let message = String::from("Token is invalid.");
                    PathfinderError::AuthenticationError(message)
                })
                // Drop the result, because everything that we need was done
                .map(|_| { () })
        )
    }
}

Compiler errors:

error[E0308]: match arms have incompatible types
  --> src/token/middleware.rs:76:32
   |
76 |           let redis_connection = match self.redis_password {
   |  ________________________________^
77 | |             Some(ref password) => {
78 | |                 let password_inner = password.clone();
79 | |                 paired_connect(&redis_socket_address, handle)
...  |
91 | |             _ => paired_connect(&redis_socket_address, handle)
92 | |         };
   | |_________^ expected struct `futures::Map`, found struct `std::boxed::Box`
   |
   = note: expected type `futures::Map<futures::MapErr<futures::AndThen<std::boxed::Box<futures::Future<Item=redis_async::client::PairedConnection, Error=redis_async::error::Error>>, futures::Map<std::boxed::Box<futures::Future<Item=std::string::String, Error=redis_async::error::Error>>, [closure@src/token/middleware.rs:82:34: 82:48 connection:_]>, [closure@src/token/middleware.rs:80:31: 83:22 password_inner:_]>, [closure@src/token/middleware.rs:84:30: 88:22]>, [closure@src/token/middleware.rs:89:26: 89:49]>`
              found type `std::boxed::Box<futures::Future<Item=redis_async::client::PairedConnection, Error=redis_async::error::Error> + 'static>`
note: match arm with an incompatible type
  --> src/token/middleware.rs:91:18
   |
91 |             _ => paired_connect(&redis_socket_address, handle)
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound `futures::Future<Item=(), Error=error::PathfinderError>: std::marker::Sized` is not satisfied
  --> src/token/middleware.rs:97:9
   |
97 |         Box::new(
   |         ^^^^^^^^ `futures::Future<Item=(), Error=error::PathfinderError>` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `futures::Future<Item=(), Error=error::PathfinderError>`
   = note: required by `<std::boxed::Box<T>>::new`

So, as far as I understood, it impossible to “generate” a future based on the “match” expression so that it could reused later and should to write and huge match, that will return Box::new(...) depends on the match result, correct? Maybe I have other ways to solve this particular issue?


#2

The issue is that your match statement, whose return value is assigned to redis_connection, has different types in the Some and None arms - that’s not allowed in Rust in general.

The easiest fix is to return a Box<Future<Item=..., Error=...>> from within both match arms (and make sure the Item and Error are the same type as well).


#3

The easiest fix is to return a Box<Future<Item=…, Error=…>> from within both match arms (and make sure the Item and Error are the same type as well).

That’s helped me a lot. Thank you so much! :slight_smile: