Search code examples
rustrust-axum

When converting a service error to a client error in axum I can’t pass a response with an error stored in extensions


Here's the relevant code, I can't locate what's wrong. If anyone can help, I'd really appreciate it

pub fn routes() -> Router {
    Router::new().route("/api/login", post(api_login))
}

async fn api_login(cookies: Cookies, Json(payload): Json<LoginPayload>) -> Result<Json<Value>> {
    info!("Request /login");

    // TODO: implement real db/json logic.
    if payload.username != "test" && payload.password != "test" {
        return Err(Error::LoginFail)
    }

    ……
}

I'm sure this place returns the error——Err(Error::loginFail)

pub enum Error {
    LoginFail,
    ……
}

impl IntoResponse for Error {
    fn into_response(self) -> Response {
        // create a placeholder Axum response
        let mut response = StatusCode::INTERNAL_SERVER_ERROR.into_response();

        // Insert the Error into response
        response.extensions_mut().insert(self);
        dbg!(response.extensions().get::<Error>());

        response
    }
}

This does generate a response with error when I debug it (You can see this in the screenshot below)

main.rs:

let app = Router::new()
        .merge(web::routes_static::routes())
        .merge(web::routes_login::routes())
        .layer(middleware::map_response(main_response_mapper)) 
        .layer(middleware::from_fn_with_state(
            lc.clone(),
            web::mw_auth::mw_ctx_resolver,
        ))
        .layer(CookieManagerLayer::new());

async fn main_response_mapper(mut res: Response) -> Response {
   ……
    // get eventual response error
    let service_error = res.extensions().get::<Error>();
   ……
}

i don't know why service_error in main_response_mapper is None

2024-10-29 15:28:05.159 DEBUG c2::error: ->> INTO_RES     - AuthFaildNoAuthTokenCookie
[src\error.rs:34:9] response.extensions().get::<crate::error::Error>() = Some(
    AuthFaildNoAuthTokenCookie,
)
2024-10-29 15:28:05.159 DEBUG c2: ->> Res_Mapper
[src/main.rs:96:5] response.extensions().get::<crate::error::Error>() = None

It seems that the extensions of response are removed, And I found that I could pass String values, but my custom Error would fail

I tried to debug the code, but debugging rust doesn't allow me to see changes to the response variable like other languages. Can you tell me what's wrong with my code or how I can fix it


Solution

  • I ended up not being able to work it out. I took a compromise where strings could be passed through extensions normally, I converted the enum type to a string, and then converted the string to an enum in mapper_response

    #[derive(Debug, Deserialize, Clone, AsRefStr, EnumString)]
    #[serde(tag = "type", content = "data")]
    pub enum Error {
        LoginFail,
    
        // Auth error
        AuthFaildNoAuthTokenCookie,
        AuthFaildTokenFormatWrong,
        AuthFaildCtxNotInRequestExt,
    
        // model errors.
        ListenerCreationFailed(ListenerCreationError),
        ListenerRemoveFailed,
        ListenerEditFailed,
    }
    

    mapper_response:

    response.extensions_mut().insert(self.as_ref().to_string());