rusthyperrust-axum

How to make an axum router handle return different content-type responses?


For example, when user access http://127.0.0.1:8080/hello, if query parameter id is 1, a plain text response return. If id is 2, give a JSON structure.

Summary:

id (input) status code content-type body
1 200 application/json {"name": "world"}
2 400 text/plain no such person
struct HelloParam {
    id: u16,
}

struct HelloResponse {
    name: String,
}

async fn hello_get(Query(params): Query<HelloParam>) -> Response {
    // how to implement it? 
}

let router = Router::new().route("/hello", get(hello_get));

Solution

  • Check out the examples at the beginning of the response module. Axum gives you a bevvy of different ways to return data so that the Content-type header is automatically set appropriately.

    (This seems like a homework question to me, so I'm not going to write your function for you exactly.)

    For example, if you return a String as a body, the Content-type will automatically be set to "text/plain":

    use axum::response::{IntoResponse, Response};
    
    async fn returns_string() -> Response {
        String::from("Hello, world!").into_response()
    }
    

    There is also a custom Json struct for returning as JSON (with the Content-type header set appropriately) any type that implements serde::Serialize.

    use axum::response::{Json, IntoResponse, Response};
    use serde::Serialize;
    
    #[derive(Serialize)]
    struct Hello {
        name: String,
    }
    
    async fn returns_json() -> Response {
        let hello = Hello {
            name: String::from("world"),
        };
    
        Json(hello).into_response()
    }
    

    So we can write a function that could return either type of response based on some property of the request. Let's choose based on the value of the "Accept" header:

    use axum::{
        http::{
            header::{ACCEPT, HeaderMap},
            status::StatusCode,
        },
        response::{Json, IntoResponse, Response},
    };
    use serde::Serialize;
    
    #[derive(Serialize)]
    struct Hello {
        name: String,
    }
    
    async fn heterogeneous_handle(headers: HeaderMap) -> Response {
        match headers.get(ACCEPT).map(|x| x.as_bytes()) {
            Some(b"text/plain") =>
                String::from("Hello, world!").into_response(),
            Some(b"application/json") => {
                let hello = Hello {
                    name: String::from("world"),
                };
                Json(hello).into_response()
            },
            _ => StatusCode::BAD_REQUEST.into_response(),
        }
    }