Axum error handling/trait question

Greetings,

Hobby programmer here trying to use axum (I am actually following an actix_web book/project and converting to axum as I read. Axum syntax is closer to what I am used to in python :slight_smile: )
I am trying to add error handling now to my program, and there in one place where I cannot understand the fix I've had to add to fix a compiler error. I have to specify axum:Json on a return value and I don't get why.

First. some code
Here is the handler:

pub async fn get_courses_for_tutor(
    app_state: extract::Extension<Arc<AppState>>,
    extract::Path(tutor_id): extract::Path<i32>,
) -> impl IntoResponse {
    let courses = get_courses_for_tutor_db(&app_state.db, tutor_id).await;
    courses
}

and the DB access function:

pub async fn get_courses_for_tutor_db(pool: &PgPool, tutor_id: i32) -> Result<Vec<Course>, ZataError> {
    let course_rows = sqlx::query_as!(
        Course,
        "SELECT * FROM ezy_course_c6 where tutor_id = $1",
        tutor_id
    )
    .fetch_all(pool)
    .await?;
	
    Ok(course_rows)
}

and my custom error struct implementation

pub enum ZataError {
    DBError(String),
    ActumError(String),
    NotFound(String),
}
impl IntoResponse for ZataError {
    type Body = Full<Bytes>;
    type BodyError = Infallible;

    fn into_response(self) -> Response<Self::Body> {
        let (status, error_message) = match self {
            ZataError::ActumError(msg) => {
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
            }
            ZataError::DBError(msg) => {
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error - Database")
            }
            ZataError::NotFound(msg) => {
                (StatusCode::NOT_FOUND, "Not Found")
            }
        };
        let body = Json(json!({
            "error": error_message,
        }));

        (status, body).into_response()
    }
}

impl From<AxumError> for ZataError {
    fn from(err: AxumError) -> Self {
        ZataError::ActumError(err.to_string())
    }
}
impl From<SQLxError> for ZataError {
    fn from(err: SQLxError) -> Self {
        ZataError::DBError(err.to_string())
    }
}

trying to run that, I get this error:

the trait bound Vec<Course>: IntoResponse is not satisfied
the following implementations were found:
<Vec as IntoResponse>
required because of the requirements on the impl of IntoResponse for Result<Vec<Course>, ZataError>

To solve this, I acn change the return value in pub async fn get_courses_for_tutor from
courses
to
axum::Json(courses)

and then all works fine. But I don't get why. The other solution is probably to implement IntoResponse for the struct Course, but again, I don't get why I would do that on the Course struct.. In my understanding, only the error type I created should require IntoResponse?

Any clue/help to understand is weclome :slight_smile:

Axum doesn't assume the response format you want is JSON. So by just returning a Vec<Course> it doesn't know whether you want a plain text response, json, xml, or something completely different.

You have to explicitly tell axum that you want JSON by wrapping it in axum::Json. That works because axum::Json<T> implements IntoResponse where T: serde::Serialize.

Thanks for the explanation! I now see in the error-handling example that indeed, most fn have a return type of axum:::Json. I missed that yesterday. Thanks!

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.