特に作りたいものがなかったので、積んである本の中から面白そうなのを選んで、プログラミングをやってみようと思ったわけです。
その本が、「Webアプリ開発で学ぶ Rust言語入門」。
axumを使ってTodoアプリを作ってみよう、ってやつ。面白そうでしょ。Webアプリ初心者にはぴったりな感じがします。本にも入門って書いてあるからね。
しかし、ツンドク期間が長すぎて、現行バージョンのaxumではコードが動かなくなっています。
あー、いきなり挫折しそう。と思ったけど、昔よりはコードを読めるようになったので、勉強を兼ねて現行バージョンの**axum = "0.8.1"**で動くように修正しながらやっていこうと思います。
本のコードは著作権にひっかかりそうなので、私が修正した部分のコードをメインに載せていきます。
著者のGitHubはこちら
axum::Serverを使えない
axum = "0.8.1"では、axum::Serverが使えなくなっています。
だから、mainは大幅に書き換える必要があります。
tokio::net::TcpListener::bind("127.0.0.1:3000")
axum::serve(listener, app).await.unwrap();これに変えると何とか動くようにはなったかな。
unwrap()が気に入らないなら、処理はお好きなように。
GitHub - tokio-rs/axum: Ergonomic and modular web framework built with Tokio, Tower, and Hyper
↑を見るとexampleがたくさん載っているので、詳しく知りたい場合はここのコードを見るといいかもね。
↑axumのドキュメントはここ。
main.rs
use axum::{
Json, Router,
http::StatusCode,
routing::{get, post},
};
use serde::{Deserialize, Serialize};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = create_app();
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
tracing::info!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
fn create_app() -> Router {
Router::new()
.route("/", get(root))
.route("/users", post(create_user))
}
async fn root() -> &'static str {
"Hello, World!"
}
async fn create_user(Json(payload): Json<CreateUser>) -> (StatusCode, Json<User>) {
let user = User {
id: 1,
username: payload.username,
};
tracing::info!("Created user: {:?}", user);
(StatusCode::CREATED, Json(user))
}テスト用のコードも動かない
let bytes = hyper::body::to_bytes(res.into_body()).await.unwrap();hyperを使ったこのコードですが、エラーが出ます。
だから、hyperを使わずに、
use http_body_util::BodyExt;
let bytes = res.into_body().collect().await.unwrap().to_bytes();VSCodeで見ていると、collect()の部分でhttp_body_util::BodyExtを使ってるっぽいですね。
このテスト用コードでは、.await.unwrap()をしておかないと、戻り値を設定する必要があって結構面倒かも。
とりあえず、動くようになったよーっていうコードは下記の通りです。
main.rs(テスト用コード)
#[cfg(test)]
mod test {
use super::*;
use axum::{
body::Body,
http::{Method, Request, header},
};
use http_body_util::BodyExt;
use tower::ServiceExt;
#[tokio::test]
async fn should_return_hello_world() {
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let res = create_app().oneshot(req).await.unwrap();
let bytes = res.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "Hello, World!");
}
#[tokio::test]
async fn should_return_user_data() {
let req = Request::builder()
.method(Method::POST)
.uri("/users")
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(r#"{"username":"alice"}"#))
.unwrap();
let res = create_app().oneshot(req).await.unwrap();
let bytes = res.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
let user: User = serde_json::from_str(&body).unwrap();
assert_eq!(
user,
User {
id: 1,
username: "alice".to_string()
}
);
}
}これでHello,World!できるようになった。やったね。
引数の順序が大事
p120, p128
エラーは、ファイルを分割する前から出ますが、
分かりやすくするために、修正したファイル分割後のコードを載せておきます。
handlers.rs(修正後)
use axum::{
extract::Extension,
Json,
http::StatusCode,
};
use std::sync::Arc;
use crate::repositories::{CreateTodo, Todo, TodoRepository};
pub async fn create_todo<T: TodoRepository>(
Extension(repository): Extension<Arc<T>>,
Json(payload): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
let todo = repository.create(payload);
(StatusCode::CREATED, Json(todo))
}エラーが出る場所
以下は、本に記載されているコード。
pub async fn create_todo<T: TodoRepository>(
Json(payload): Json<CreateTodo>,
Extension(repository): Extension<Arc<T>>,
)Extensionを第一引数にしないとエラーが出る仕様に変更されたっぽいです。
コメント