Axum: Custom ApiResponse Enum with Optional Redirects/Cookies – Better to Implement IntoResponse or Just Return impl IntoResponse?

⚓ Rust    📅 2025-10-12    👤 surdeus    👁️ 1      

surdeus

Would it be better to just return impl IntoResponse in axum handlers? Or is the use of enums a good thing here?

Would you do a if let Some() = fn {} else {}.

/// Handler for login page, here the form data gets send to when a request gets made to '/login'
async fn login(
    State(state): State<AppState>,
    jar: CookieJar,
    Form(auth): Form<AuthCredentials>,
) -> Result<ApiResponse, ApiError> {
    println!("login: jar: {jar:?}, auth: {auth:?}");
    let pool = state.pool;

    if let Some(cookie) = auth_login(&pool, auth).await {
        println!("login: cookie: {cookie:?}");
        let cookie = Cookie::build((cookie.0, cookie.1)).max_age(Duration::seconds(60));
        let jar = jar.add(cookie);
        let redirect = Redirect::to("app");

        return Ok(ApiResponse::OK {
            jar: Some(jar),
            redirect: Some(redirect),
        });
    }

    let redirect = Redirect::to("/registration");
    Err(ApiError::Unauthorised { redirect })
}

#[derive(Debug)]
struct CookieBase(String, String);

impl CookieBase {
    fn new() -> Self {
        let token = Uuid::new_v4().to_string();
        Self(SESSION_ID.to_string(), token)
    }
}

#[derive(Debug, Deserialize)]
struct AuthCredentials {
    username: String,
    password: String,
}

pub enum ApiResponse {
    OK {
        jar: Option<CookieJar>,
        redirect: Option<Redirect>,
    },
    Created,
}

impl IntoResponse for ApiResponse {
    fn into_response(self) -> Response {
        match self {
            Self::OK { jar, redirect } => {
                if let Some(jar) = jar {
                    if let Some(redirect) = redirect {
                        return (StatusCode::OK, jar, redirect).into_response();
                    }
                    return (StatusCode::OK, jar).into_response();
                }
                (StatusCode::OK).into_response()
            }
            Self::Created => (StatusCode::CREATED).into_response(),
        }
    }
}

pub enum ApiError {
    BadRequest,
    Forbidden,
    Unauthorised { redirect: Redirect },
    InternalServerError,
    Conflict { message: String },
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        match self {
            Self::BadRequest => (StatusCode::BAD_REQUEST).into_response(),
            Self::Forbidden => (StatusCode::FORBIDDEN).into_response(),
            Self::Unauthorised { redirect } => (StatusCode::UNAUTHORIZED, redirect).into_response(),
            Self::InternalServerError => (StatusCode::INTERNAL_SERVER_ERROR).into_response(),
            Self::Conflict { message } => (StatusCode::CONFLICT, Json(message)).into_response(),
        }
    }
}

1 post - 1 participant

Read full topic

🏷️ Rust_feed