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?