Rust | Axum の axum-valid を使ってバリデーションを実装する
はじめに
本記事では Rust の Axum という Webフレームワークを使ってクエリー、ボディのバリデーションを実装してみたいと思います!
Axum のバリデーションには、axum-valid と呼ばれるクレートが存在しますので、今回はそちらを使ってバリデーションのロジックを書いていきます。
意外にも紹介している人が少なめかつ、本クレートを利用することで非常に便利なバリデーションを実施することができますので、ぜひ、手元でも動かしてもらえると理解しやすいと思います。
Axum のプロジェクトを作成する
まずは Axum のプロジェクトを作ります。
$ cargo new axum-web
$ cd axum-web
cargo run
を実行して、"Hello, World!"できていれば問題ないです。
Axum の必要なクレートを追加
以下のコマンドで Axum の実装に必要な最低限のクレートが追加できます。
$ cargo add axum
$ cargo add tokio --features full
これで下準備は完了です✨
Valid<E>
で API サーバーのクエリー、ボディにバリデーションを仕掛ける
axum-valid の 本題の axum-valid を使って、バリデーションを走らせてみます。
axum-valid にはさまざまなバリデーションの方法がありますが、
一番シンプルな Valid<E>
を利用していきます!
axum-valid などのクレートを追加する
まずは、クレートの準備です。
以下のコマンドでクレートを追加します。
axum-valid と一緒に validator というクレートも必要になってきますので、追加します。
また、serde クレートの追加も行います。
$ cargo add validator --features derive
$ cargo add axum-valid
$ cargo add serde --features derive
バリデーションロジックを記述していく
GET /users
、POST /users
というシンプルなエンドポイントを2種類用意しています。
use axum::{extract::Query, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
use axum_valid::Valid;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Validate, Serialize, Deserialize)]
pub struct User {
#[validate(range(min = 1, max = 150, message = "Age must be between 1 and 150"))]
pub age: u16,
#[validate(length(
min = 2,
max = 50,
message = "Name must be between 3 and 50 characters"
))]
pub name: String,
}
pub async fn fetch_user(
Valid(Query(query)): Valid<Query<User>>,
) -> Result<impl IntoResponse, StatusCode> {
assert!((1..=150).contains(&query.age));
assert!((2..=50).contains(&query.name.chars().count()));
let user = vec![{
User {
age: query.age,
name: query.name,
}
}];
Ok((StatusCode::OK, Json(user)))
}
pub async fn create_user(
Valid(Json(payload)): Valid<Json<User>>,
) -> Result<impl IntoResponse, StatusCode> {
assert!((1..=150).contains(&payload.age));
assert!((2..=50).contains(&payload.name.chars().count()));
let user = {
User {
age: payload.age,
name: payload.name,
}
};
Ok((StatusCode::CREATED, Json(user)))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/users", get(fetch_user).post(create_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
上記の例では、User
構造体を定義していて、/users
にその構造体をそのまま返すというシンプルなソースコードになっています。
axum-valid を使う上でポイントになるところは、以下のような点かなと思います。
- 構造体のパラメーター上部に
#[validate()]
を追加すること - ハンドラー関数の引数に仕掛けを用意しておくこと
-
Valid(Json(payload)): Valid<Json<User>>,
の部分
-
ポイントを押さえた上でも、
使用感が良く、コードの視認性にも影響が少ない点を踏まえても、
使い勝手が非常に優れているのではないでしょうか。
おわりに
ざっくりですが、axum-valid の紹介でした!
バリデーションの下限値や上限値をソースコード上でも表現しやすくなっているところが作り込まれている印象ですね。ぜひ、皆さんも使ってみてはいかがでしょうか。
ではでは!
PS. 最近、ありがたいことに記事を読んでくださる人が増えてきたように感じます。最後まで読んでいただきありがとうございます!!😋
Discussion
typo ( バリでション → バリデーション )
もっと言うと
assert!((1..=150).contains〜
のタイポですかね?エラーをハンドルしているのは
Valid<T>
のFromRequest
実装であって、このassert!
はaxum_valid::Valid
の説明にあたって「ハンドラーにはバリデーション済のValid<T>
が渡される」ことを分かりやすく示す手段として採用されているだけでは?kanarus さん
コメントいただきありがとうございます!
タイポが大変多く、失礼しました。
バリデーションの意味を再度確認してみたのですが、「確認」や「検証」あるいは「妥当性の確認」の意味であることから、「バリデーションチェック」とすると「頭痛が痛い」みたいな表現になってしまいそうですね。
「普通」が「一般的」と同義とすると「バリデーション」かなと思います!
おっしゃる通り、ドキュメントがわかりやすく表現されているだけでした。
誤解のないよう、適切な説明に修正します!