3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Rust] WASMでFetch API

Last updated at Posted at 2022-12-03

web-sys/js-sysのラッパーであるglooを用いてFetch APIを使ってみます.axumでテスト用のサーバーを立て,gloo-net用いてjsonの受け渡しを実装し,wasm-packでテストします.コード全体はこちらにあります.

Fetch APIを利用してJsonのGET/POST

gloo-netではserdeSerialize, Deserializeを実装した型を用いてjsonとして受け渡しができるため,以下のように受け渡す型を定義しています.それ以外にderiveするトレイトはテストやサーバー側で使います.

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
pub struct Client {
    pub id: usize,
    pub name: String,
    pub location: String,
}

またテストの時とそれ以外でurlとcorsの設定が異なる(テストの時はオリジンの異なるサーバーを利用する)ため,以下のように定数を定義しています.

use gloo_net::http::RequestMode;

const API_DOMAIN: &str = if cfg!(test) {
    "http://127.0.0.1:8080/api"
} else {
    "/api"
};
const CORS_MODE: RequestMode = if cfg!(test) {
    RequestMode::Cors
} else {
    RequestMode::SameOrigin
};

またテストを分かりやすくするために以下のエラーを定義しています.

#[derive(thiserror::Error, Debug)]
pub enum FetchError {
	// gloo-netのエラー(serdeのエラーを含む)
    #[error(transparent)]
    GlooNetError(#[from] gloo_net::Error),
	// Clientが見つからないときのエラー
    #[error("Not Found Error")]
    NotFoundError,
    // Clientが重複する場合のエラー
    #[error("Already Exists Error")]
    AlreadyExistsError,
}

jsonを取得してパースする関数は以下となります.Rustの他のhttpクライアントのクレートと同じように使えます.

use gloo_net::http::Request;

pub async fn get_client(id: usize) -> Result<Client, FetchError> {
    let response = Request::get(&format!("{}/client/{}", API_DOMAIN, id))
        .mode(CORS_MODE)
        .send()
        .await?;

    if response.ok() {
        let client = response.json::<Client>().await?;
        Ok(client)
    } else {
        Err(FetchError::NotFoundError)
    }
}

リクエストポディにjsonを含めて送信する関数は以下となります.

 pub async fn post_client(client: Client) -> Result<(), FetchError> {
    let res = Request::post(&format!("{}/client", API_DOMAIN))
        .mode(CORS_MODE)
        .json(&client)?
        .send()
        .await?;

    if res.ok() {
        Ok(())
    } else {
        Err(FetchError::AlreadyExistsError)
    }
}

テスト用サーバー

上で定義した関数を利用するためのサーバーを定義します.簡単のためidが0のClientのみを取得でき,重複の判定をしています.またwebdriverを用いてテストを行うため,CORSの設定もしています.

bin/test_server.rs
use axum::{
	extract::{Json, Path},
	http::StatusCode,
	routing::{get, post},
	Router,
};
use tower_http::cors::{Any, CorsLayer};
use wasm_fetch_example::Client;

async fn get_client_handler(Path(id): Path<usize>) -> (StatusCode, Json<Client>) {
	if id == 0 {
		let client = Client {
			id: 0,
			name: "John".to_string(),
			location: "NewYork".to_string(),
		};

		(StatusCode::OK, Json(client))
	} else {
		(StatusCode::NOT_FOUND, Json(Client::default()))
	}
}

async fn post_client_handler(Json(client): Json<Client>) -> StatusCode {
	if client.id == 0 {
		StatusCode::CONFLICT
	} else {
		StatusCode::OK
	}
}

// CORSの設定
let cors_layer = CorsLayer::new()
	.allow_methods(Any)
	.allow_headers(Any)
	.allow_origin(Any);

let test_app: Router<()> = Router::new()
	.route("/api/client", post(post_client_handler))
	.route("/api/client/:id", get(get_client_handler))
	.layer(cors_layer);

axum::Server::bind(&"127.0.0.1:8080".parse().unwrap())
	.serve(test_app.into_make_service())
	.await
	.unwrap();

テスト

wasm_bindgen_testを用いてテストを書いています.先ほど定義したFetchErrorを用いてエラーの判定を行っています.

#[cfg(all(test, target_arch = "wasm32"))]
mod test {
    use wasm_bindgen_test::*;

    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

    #[wasm_bindgen_test]
    async fn test_get_client() {
        let res_ok = super::get_client(0).await;
        assert!(res_ok.is_ok());

        let client = res_ok.unwrap();
        assert_eq!(
            client,
            super::Client {
                id: 0,
                name: "John".to_string(),
                location: "NewYork".to_string(),
            }
        );

        let res_err = super::get_client(1).await;
        assert!(res_err.is_err());
    }

    #[wasm_bindgen_test]
    async fn test_post_client() {
        let client_ok = super::Client {
            id: 1,
            name: "Ethan".to_string(),
            location: "Washington".to_string(),
        };

        let res_ok = super::post_client(client_ok).await;
        assert!(res_ok.is_ok());

        let client_err = super::Client {
            id: 0,
            name: "John".to_string(),
            location: "NewYork".to_string(),
        };

        let res_err = super::post_client(client_err).await;
        assert!(res_err.is_err())
    }
}

wasm-packを用いてテストを行います.

wasm-pack test --chrome --headless
test wasm_fetch_example::test::test_post_client ... ok
test wasm_fetch_example::test::test_get_client ... ok

gloo-netではserdeを用いて簡単にデータの受け渡しができて便利ですね.工夫すればエラーを受け渡すこともできるため,httpリクエストにおいてもRustの型システムのメリットが発揮できそうです.

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?