Handler error in axum framework

I am using the Axum framework to write a backend API and I getting an error when registering a handler in the server.

This is my handler code and rust-analyzer says that there are no errors in it:

use axum::extract::{Extension, Form};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use secrecy::{ExposeSecret, Secret};
use serde_json::json;
use std::sync::{Arc, RwLock};
use tower_cookies::{Cookie, Cookies};
use users::users_repository::{Repository, UsersRepository};

use crate::domain::{Email, Password, UserCredentials};
use crate::helpers::error_chain_fmt;
use crate::services::{HashingService, HashingServiceError, JwtService, JwtTokenType};

pub enum LoginError {
    FailedValidation(String),
    UserNotFound,
    IncorrectPassword(HashingServiceError),
    UnexpectedError(anyhow::Error),
}

impl std::fmt::Debug for LoginError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        error_chain_fmt(self, f)
    }
}

impl std::fmt::Display for LoginError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            LoginError::FailedValidation(e) => {
                write!(f, "{}", e)
            }
            LoginError::UserNotFound => {
                write!(f, "User not found with given email address")
            }
            LoginError::IncorrectPassword(_) => {
                write!(f, "Given password does not match stored password")
            }
            LoginError::UnexpectedError(_) => {
                write!(f, "An unexpected error occurred")
            }
        }
    }
}

impl std::error::Error for LoginError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            LoginError::FailedValidation(_) => None,
            LoginError::UserNotFound => None,
            LoginError::IncorrectPassword(e) => Some(e),
            LoginError::UnexpectedError(e) => e.source(),
        }
    }
}

impl From<anyhow::Error> for LoginError {
    fn from(err: anyhow::Error) -> Self {
        Self::UnexpectedError(err)
    }
}

impl IntoResponse for LoginError {
    fn into_response(self) -> Response {
        let (status, error_message) = match self {
            LoginError::FailedValidation(_)
            | LoginError::IncorrectPassword(_)
            | LoginError::UserNotFound => (StatusCode::BAD_REQUEST, "Invalid email or password"),
            LoginError::UnexpectedError(_) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "An unexpected error occurred",
            ),
        };

        let body = Json(json!({
            "error": error_message,
        }));

        (status, body).into_response()
    }
}

#[derive(serde::Deserialize, Debug)]
pub struct FormData {
    email: String,
    password: Secret<String>,
}

impl TryFrom<FormData> for UserCredentials {
    type Error = String;

    fn try_from(value: FormData) -> Result<Self, Self::Error> {
        let email = Email::parse(value.email)?;
        let password = Password::parse(value.password.expose_secret().clone())?;

        Ok(Self { email, password })
    }
}

#[tracing::instrument(
    name = "Log into the application",
    skip(form, users_repository, cookies),
    fields(email = %form.email)
)]
pub async fn login(
    form: Form<FormData>,
    users_repository: Extension<Arc<RwLock<UsersRepository>>>,
    cookies: Cookies,
) -> Result<Response, LoginError> {
    // Parse form data
    let user_credentials: UserCredentials =
        form.0.try_into().map_err(LoginError::FailedValidation)?;
    println!("{:?}", &user_credentials);

    // Fetch user from database to make sure he/she exists
    let user = {
        users_repository
            .read()
            .expect("Failed to get read lock")
            .get_user_by_email(user_credentials.email.as_ref(), None)
            .await
            .map_err(|e| LoginError::UnexpectedError(anyhow::Error::new(e)))?
            .ok_or(LoginError::UserNotFound)?
    };

    // Verify if password matches stored password
    HashingService::verify(&user.password, user_credentials.password.as_ref()).map_err(
        |e| match e {
            HashingServiceError::PasswordVerification(_) => LoginError::IncorrectPassword(e),
            _ => LoginError::UnexpectedError(anyhow::Error::new(e)),
        },
    )?;

    // Create access and refresh JWT tokens containing user information
    let access_token = JwtService::encode(
        user.id,
        user.tenant_id,
        user.role.clone(),
        15,
        JwtTokenType::AccessToken,
    )
    .map_err(|e| LoginError::UnexpectedError(anyhow::Error::new(e)))?;

    let refresh_token = JwtService::encode(
        user.id,
        user.tenant_id,
        user.role,
        15,
        JwtTokenType::RefreshToken,
    )
    .map_err(|e| LoginError::UnexpectedError(anyhow::Error::new(e)))?;

    // Set cookie with refresh token
    let cookie = Cookie::build("refresh_token", refresh_token)
        .domain("http://localhost:3000")
        .path("/refresh-token")
        .secure(false)
        .http_only(false)
        .finish();

    cookies.add(cookie);

    // Send back access token
    Ok((
        StatusCode::OK,
        Json(json!({ "access_token": access_token })),
    )
        .into_response())
}

And this is the code where I register the login handler shown above and the error that I get:

use axum::Router;

let router = Router::new()
        .nest(
            "/api",
            Router::new()
                .route("/health_check", get(health_check))
                .nest("/auth", Router::new().route("/login", post(login)))
                .nest("/models", Router::new().route("/", get(get_models))),
        )

error: the trait bound `fn(Form<FormData>, Extension<Arc<std::sync::RwLock<UsersRepository>>>, Cookies) -> impl std::future::Future {login}: Handler<_, _>` is not satisfied
label: the trait `Handler<_, _>` is not implemented for `fn(Form<FormData>, Extension<Arc<std::sync::RwLock<UsersRepository>>>, Cookies) -> impl std::future::Future {login}`
```

Can anyone help me figure out what I am doing wrong?

I've posted a link to this in the axum chat room on the Tokio discord server. Hopefully someone there will have your answer. In the meantime, please fix the formatting of your post by editing it.

Try using axum_debug - Rust. It’s designed to make errors like this easier to fix.

Thank you! I had an extra backtick which messed with the formatting

Thank you! I will give it a try

By using ´axum-debug´ as recommended by one of the responses and with the help of @alice , I found out that error was related to using .await while holding a read lock from RwLock.

I managed to fix my code, thank you for your help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.