🫥

rust axum framework cook book1

2023/12/21に公開

Handler

  • text/plainで 固定の文字列を返したい
  • status codeを変えたい
  • response ヘッダを追加したい
  • jsonでpostされた内容をstructをbindしたい
    • jsonとstructが完全に合わなかった場合文字でエラーがでるが、これをカスタマイズしたい()

text/plainで 固定の文字列を返したい

async fn handler() -> &'static str {
    "Hello, world!"
}

固定のhtmlファイルを返したい

async fn handler() -> Html<&'static str> {
    Html(include_str!("../views/index.html"))
}

status codeを変えたい / response headerを変えたい/追加したい

impl IntoResponseで返すことで、細かく制御できます
CustomErrorの構造体定義は省略

async fn status400(params: Json<Value>) -> impl IntoResponse {
let builder = Response::builder();

    let body = Json(json!(CustomError {
            message: "app_name is required".to_string()
        }));
    builder
        .status(StatusCode::BAD_REQUEST)
        .header("Content-type", "application/json")
        .body(body)
        .unwrap()
	.into_body()
}

JSONを受け取りstructにバインドしたい

pub async fn test4(Json(params): Json<HomeResponse>) -> Result<Json<HomeResponse>> {
    format::json(params)
}

Formを受け取りstructにバインドしたい

レスポンスはjsonで返します

pub async fn test4(Form(params): Json<HomeResponse>) -> Result<Json<HomeResponse>> {
    format::json(params)
}

自動でstructを受け取るとエラーメッセージが固定なのでカスタマイズしたい

Optionでないフィールドがpostされたjsonに含まれていなかったり、型が違った場合、

Failed to deserialize the JSON body into the target type: app_name: invalid type: integer 10, expected a string at line 1 column 14

などのメッセージが返されてしまいます。

これをカスタマイズするには

より汎用的な型で受け取り、自分でstructに紐づければ良いです。

fn create_response(status: StatusCode, value: serde_json::Value) -> impl IntoResponse {
    let builder = Response::builder();

    let body = serde_json::to_string(&value).unwrap();

    builder
        .status(status)
        .header("Content-type", "application/json")
        .body(body)
        .unwrap()
}
async fn current3(params: Json<Value>) -> impl IntoResponse {
    let value = serde_json::from_value::<HomeResponse>(params.0);
    if &value.is_ok() == &true {
        let v = json!(value.unwrap());
        create_response(StatusCode::OK, v)
    } else {
        create_response(
            StatusCode::BAD_REQUEST,
            json!(CustomError {
                message: value.err().unwrap().to_string()
            }),
        )
    }
}

この方法であれば、自分でカスタマイズしたエラーを返すことができます.

他にも FromRequest traitを実装しておく方法もあります。

#[async_trait]
impl<B> FromRequest<B> for HomeResponse {
    type Rejection = Response<Body>;

    async fn from_request(req: Request, state: &B) -> std::result::Result<Self, Self::Rejection> {
        let app_name = req
            .headers()
            .get("app_name")
            .and_then(|value| value.to_str().ok())
            .map(|value| value.to_string());
        let error = CustomError {
            message: "app_name is required".to_string(),
        };
        Err(format::json(error).into_response())
    }
}

JsonRejectionを使う方法もありそうです。

Discussion