🏹

Rust の actix_web_validator でバリデーションを実装する

2024/08/31に公開

はじめに

Actix-Web で作ったアプリケーションにバリデーションチェックを実装します。
サーバーサイド側のバリデーションチェックは何かと必須になってくるところかと思いますので、
本記事が参考となれば嬉しいです。

Actix-Web には、actix_web_validator というクレートが存在するので、今回はそちらを使ってやっていきます!
🔻actix_web_validator
https://docs.rs/actix-web-validator/latest/actix_web_validator/

元のソースコードは以下で紹介したものを使っていきます。
https://zenn.dev/collabostyle/articles/1ca9677d9c9777

クレートの追加

Actix-Webで作成したプロジェクトのCargo.tomlに以下の2種類を追加。
パッケージの依存関係がある場合はご自身の環境に合わせて解消いただくと良いかもです。

Cargo.toml
actix-web-validator = "6.0.0"
validator = { version = "0.18.1", features = ["derive"] }

構造体の作成

今回は、クエリパラメーターのバリデーションチェックを実装していきたいと思いますので、
パラメーターを受け付ける構造体を定義しておきます。

Vaidate を追加し、バリデーションを実装したいパラメーターの上部に #[validate()]を追加します。
今回は limit というパラメーターを指定しています。さらに下限値、上限値を min や max で表現することが可能です。

use validator::Validate;

#[derive(Validate, Serialize, Deserialize)]
pub struct FetchParam {
    #[validate(range(min = 1, max = 9999))]
    limit: i32,
}

ハンドラー関数の作成

最後にハンドラーの関数を実装しちゃいます。
get_todos の引数に、actix_web_validator::Queryを指定すると、バリデーションが走るようになります。

use actix_web_validator::Query;

async fn get_todos(query: Query<FetchParam>) -> impl Responder {
    let todos = fetch_all(query.limit).await;
    match todos {
        Ok(t) => HttpResponse::Ok().json(t),
        Err(e) => {
            println!("{e}");
            HttpResponse::NotFound().json("Not Found Error")
        }
    }
}

最終的なコードは以下となります。

use std::env;

use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use actix_web_validator::Query;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{MySql, MySqlPool, Pool};
use validator::Validate;

// MySQL コネクションの作成
async fn get_db_pool() -> Pool<MySql> {
    dotenv::dotenv().expect("Failed to read .env file");
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");

    MySqlPool::connect(&database_url)
        .await
        .unwrap_or_else(|_| panic!("Cannot connect to the database"))
}

#[derive(Serialize)]
pub struct Todos {
    pub id: String,
    pub title: String,
    pub description: Option<String>,
    pub created_at: DateTime<Utc>,
}

impl Todos {
    pub fn new(
        id: String,
        title: String,
        description: Option<String>,
        created_at: DateTime<Utc>,
    ) -> Self {
        Self {
            id,
            title,
            description,
            created_at,
        }
    }
}

#[derive(Validate, Serialize, Deserialize)]
pub struct FetchParam {
    #[validate(range(min = 1, max = 9999))]
    limit: i32,
}

// TiDB からの取得
async fn fetch_all(limit: i32) -> Result<Vec<Todos>, sqlx::Error> {
    let db_pool = get_db_pool().await;
    sqlx::query_as!(
        Todos,
        r#"
        SELECT
            id,
            title,
            description,
            created_at
        FROM
            todos
        ORDER BY created_at DESC
        LIMIT ?
        "#,
        limit
    )
    .fetch_all(&db_pool)
    .await
}

// ハンドラー
async fn get_todos(query: Query<FetchParam>) -> impl Responder {
    let todos = fetch_all(query.limit).await;
    match todos {
        Ok(t) => HttpResponse::Ok().json(t),
        Err(e) => {
            println!("{e}");
            HttpResponse::NotFound().json("Not Found Error")
        }
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/todos", web::get().to(get_todos)))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

おわりに

このように簡単にバリデーションチェックを実装することができました。
バリデーションチェックのライブラリは、構造体に下限値や上限値を書くことが主流なのでしょうか。
どのパラメーターにどんな制約があるのかが一目でわかるようになっているので、個人的には好きですね。

では。

コラボスタイル Developers

Discussion