0

This is my first attempt at writing a small webservice with rust, using actix-web.

The code below is a request handler that is intended to do three things, insert an entry in the database, send an email if that db call was successful, and then return a json payload as the response.

data.dal (database call) and data.email_service are references to Actors.

The issue: is I am unable to capture the error returned by data.dal. Any attempt to reconfigure the below code seems to give me an error stating the compiler wasn't able to find a conversion from Actix Mailbox to [Type].

Is there an alternate/better way to rewrite this? Basically when the request is issued, I'd like to be able to call Actor A. And if the result from A is Ok then call Actor B. If the results from both are okay return a JSON payload. If either A or B return an error (can have different error types), return an custom error message.

pub fn register_email(
    invitation: Json<EmailInvitationInput>,
    data: web::Data<AppState>,
) -> impl Future<Item=HttpResponse, Error=Error> {
    let m = dal::queries::CreateEmailInvitation { email: invitation.email.clone() };
    data.dal.send(m)
        .from_err()
        .and_then(move |res| {
            let invite = res.unwrap();
            let email_input = email::SendLoginLink {
                from: "from_email".to_string(),
                to: "to_email".to_string(),
            };
            data.email_service.send(email_input)
                .from_err()
                .and_then(move |res| match res {
                    Ok(_) => {
                        Ok(HttpResponse::Ok().json(EmailInvitationOutput { expires_at: invite.expires_at }))
                    }
                    Err(err) => {
                        debug!("{:#?}", err);
                        Ok(ServiceError::InternalServerError.error_response())
                    }
                })
        })
}
States
  • 548
  • 5
  • 15

1 Answers1

0

What I usually do is to have an Error type that agglomerates all different errors, the coercion to this type can be achieved implicitly by declaring the appropriate From implementations and what you are doing from_err() but here I am being explicit:

I haven't tested this code snippet but this is how I have done it in projects I'm working on that use Actix:

data.dal.send(m)
    .map_err(Error::Mailbox)
    .and_then(|res| res.map_err(Error::Service))
    .and_then(move |invite| {
        let email_input = email::SendLoginLink {
            from: "from_email".to_string(),
            to: "to_email".to_string(),
        };
        data.email_service.send(email_input)
            .map_err(Error::Mailbox)
            .and_then(|res| res.map_err(Error::Service))
            .and_then(move |res| HttpResponse::Ok().json(EmailInvitationOutput { expires_at: invite.expires_at }))
    })
    .or_else(|err| {
        debug!("{:#?}", err);
        ServiceError::InternalServerError.error_response()
    })

(I'm assuming ServiceError implements IntoFuture just like HttpResponse does)

Anler
  • 1,793
  • 13
  • 21