ロケットチュートリアル02:ミニマリストAPI


フォトバイAdetunji Paul on Unsplash ]
このインストレーションでは、ロケットを使用してミニマリストCRUD APIサーバーを作成します.実際に、我々はちょうど1つをスケッチし、来てもっと基礎を準備します.
このチュートリアルのコードは、このリポジトリにあります.github.com/davidedelpapa/rocket-tut , そして、あなたの便宜のためにタグ付けされました:
git clone https://github.com/davidedelpapa/rocket-tut.git
cd rocket-tut
git checkout tags/tut2

クリーンアップ


まず第一に、我々はコードをリファクタします.
これはファイルシステムです.
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│   ├── lib.rs
│   ├── main.rs
│   └── routes
│       ├── echo.rs
│       └── mod.rs
├── static
│   └── abruzzo.png
└── tests
    └── basic_test.rs
src/では、ルートルートを作成します/ここで我々のルートは行くでしょう、Src/外側の間、我々はすべてのテストを持つためにテスト/フォルダを作成します
必要なすべてのファイルに行きましょう.
メインのRSと一緒にsrc/内部でlibを作成します.rs以下のコードを使用します.
#![feature(proc_macro_hygiene, decl_macro)]
#![allow(unused_attributes)]

#[macro_use] use rocket::*;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::helmet::SpaceHelmet;

mod routes;

pub fn rocket_builder() -> rocket::Rocket {
    rocket::ignite().attach(SpaceHelmet::default())
    .mount("/", routes![routes::echo::echo_fn])

    .mount("/files", StaticFiles::from("static/"))
}
ご覧のように、私たちはfn rocket() , 名前を変えるrocket_builder() それがロケット木箱と衝突しないために.
我々は宣言するmod routes このファイルの中にも.echo関数のルートの名前空間が今ではroutes::echo::echo_fn .
src/route/ディレクトリをモジュールとして使用するにはmodを作成します.それの中のRS、以下の内容で
pub mod echo;
次に、src/route/echoを作成します.以下の内容を持つrs :
use rocket::*;

#[get("/echo/<echo>")]
pub fn echo_fn(echo: String) -> String {
    echo
}
これは単に我々echo_fn() 分解されます.
最後に我々のsrc/main.rsは次のようになります.
use rocket_tut::rocket_builder;

fn main() {

    rocket_builder().launch();
}
すべては、それがとても素骨を感じる理由です.
With cargo run 我々はすべてが正常に動作していることを保証します.
最後に、内部テスト/サーバーのすべてのテストを作成します.したがって、私たちが既に持っているコードから始めましょう.RS
use rocket::local::Client;
use rocket::http::Status;
use rocket_tut::rocket_builder;

#[test]
fn echo_test() {
    let client = Client::new(rocket_builder()).expect("Valid Rocket instance");
    let mut response = client.get("/echo/test_echo").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.body_string(), Some("test_echo".into()));
}
テストのためのコードも削減されている.
With cargo test すべてが正しくテストされていることを確認できます.

使用可能なAPIの最低限の最小値


私たちはCRUD ユーザを登録し、ユーザアカウントを管理する機能を持つサーバ.
以下のエンドポイントを持ちます.
すべての登録ユーザーのリストを取得します
post/user/-新規ユーザを登録する
get/user/- id = idでユーザに情報を取得する
put/user/- id = userでユーザの情報を更新する
delete/user/- id = userでユーザを削除する
今のところ我々はそれをシンプルに保つ.
に関してはecho_fn() , 我々はそれを必要としないので、我々はエコーからping関数へそれを変えます.これは、サーバーが稼働しているかどうかを確認するのに便利です.
src/route/echoRSはsrc/route/pingとなります.rs以下の内容を指定します.
use rocket::*;

#[get("/ping")]
pub fn ping_fn() -> String {
    "PONG!".to_string()
}
src/route/modを更新する必要があります.rs新しいファイルを指す
pub mod ping;
また、src/libも参照してください.RS
.mount("/", routes![routes::ping::ping_fn])
最後に、テスト/basicoundテスト.RSは新しいルートとテスト条件を反映しなければなりません.
#[test]
fn ping_test() {
    let client = Client::new(rocket_builder()).expect("Valid Rocket instance");
    let mut response = client.get("/ping").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.body_string(), Some("PONG!".into()));
}
今両方cargo test and cargo run 期待通りに動作するはずです.

利用者の路線


ユーザを加えましょう.SRC/ルート内のRS
我々は、これを足場にして迅速にテストするために、プレースホルダーのルートを使用します.
use rocket::*;

#[get("/users")]
pub fn user_list_rt() -> String {
    "List of users".to_string()
}

#[post("/users")]
pub fn new_user_rt() -> String {
    "Creation of new user".to_string()
}

#[get("/users/<id>")]
pub fn info_user_rt(id: String) -> String {
    format!("Info for user {}", id)
}

#[put("/users/<id>")]
pub fn update_user_rt(id: String) -> String {
    format!("Update info for user {}", id)
}

#[delete("/users/<id>")]
pub fn delete_user_rt(id: String) -> String {
    format!("Delete user {}", id)
}
ご覧の通り、機能があります_rt ) それぞれのルートについては、ルートとHTTPメソッドで合意.
src/route/modを更新する必要があります.RS
pub mod ping;
pub mod user;
src/libと同様に.RS
pub fn rocket_builder() -> rocket::Rocket {
    rocket::ignite().attach(SpaceHelmet::default())
    .mount("/", routes![routes::ping::ping_fn])
    .mount("/api", routes![
        routes::user::user_list_rt,
        routes::user::new_user_rt,
        routes::user::info_user_rt,
        routes::user::update_user_rt,
        routes::user::delete_user_rt
    ])
    .mount("/files", StaticFiles::from("static/"))
}
ご覧の通り、我々はすべてのルートを下に追加しました/api マウントポイント.このように、get user/は以下のようにして取得できます.localhost:8000/api/users/1 例えば.
これをテストするために、手動でAPIをチェックする経験的な方法で行きます.
この場合私はARC for Google Chrome , しかし、郵便配達人、不眠症、ホプスコッチなど、多くの選択肢があります.io(旧女)等curl 目的のために使用することができます!

JSONレスポンス


クイックテストの後、我々の残りのAPIをより良いものに変える時間です
まず必要serde :
cargo add serde --features derive
次に、src/route/userのコードを変更しましょう.r . JSONレスポンスを表すために構造体を追加します.
use serde::{Deserialize, Serialize};


#[derive(Serialize, Deserialize)]
pub struct Response {
    status: String,
    message: String,
}
impl Response {
    fn ok(msg: &str) -> Self {
        Response {
            status: "Success".to_string(),
            message: msg.to_string(),
        }
    }
    fn err(msg: &str) -> Self {
        Response {
            status: "Error".to_string(),
            message: msg.to_string(),
        }
    }
}

今、我々はResponse JSONオブジェクトに変換されるstruct.つのメッセージを返すために2つの機能を実装しましたSuccess , と他のメッセージをError .
さあ、最初の道を変えましょう.fn user_list_rt() :
#[get("/users")]
pub fn user_list_rt() -> Json<Response> {
    Json(Response::ok("List of users"))
}
我々が経験的に反応をテストするならば、我々はそれが働くのを見ます.

今、我々は現在、同じ方法をすべてのルートを更新することができます
#[post("/users")]
pub fn new_user_rt() -> Json<Response> {
    Json(Response::ok("Creation of new user"))
}

#[get("/users/<id>")]
pub fn info_user_rt(id: String) -> Json<Response> {
    Json(Response::ok(&* format!("Info for user {}", id)))
}

#[put("/users/<id>")]
pub fn update_user_rt(id: String) -> Json<Response> {
    Json(Response::ok(&* format!("Update info for user {}", id)))
}

#[delete("/users/<id>")]
pub fn delete_user_rt(id: String) -> Json<Response> {
    Json(Response::ok(&* format!("Delete user {}", id)))
}

テストの実装


テストを実装するために、テスト/フォルダ内にmodを作成し、テストに必要なすべての初期化ロジックを収集します.
これは、後で同様に非常に役に立つでしょう.
ここではディレクトリテスト/common/を作成しましょう.RS
use rocket::local::Client;
use rocket_tut::rocket_builder;

pub fn setup () -> Client {
    Client::new(rocket_builder()).expect("Valid Rocket instance")
}
これは、私たちがinit ロケットクライアントのすべてのテストからだけでなく、我々は可能性を我々に将来的に必要なinitコードを挿入するために与える.
次に、テスト/basicoundテストを変更します.rsは次の通りである.
use rocket::http::{ContentType, Status};

mod common;

#[test]
fn ping_test() {
    let client = common::setup();
    let mut response = client.get("/ping").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.body_string(), Some("PONG!".into()));
}
今、我々はsetup() 関数/test/common/modで定義されています.RS
さらに、src/route/userで書いた最初のルートも確認できます.RSuser_list_rt() 関数.
以下のコードをテストします.
#[test]
fn user_list_rt_test(){
    let client = common::setup();
    let mut response = client.get("/api/users").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.content_type(), Some(ContentType::JSON));
    assert_eq!(response.body_string(), Some("{\"status\":\"Success\",\"message\":\"List of users\"}".into()));
}
ご覧の通り、応答の成功を確認しますが、応答はapplication/json コンテンツタイプでは、最後に、実際のレスポンスもチェックします(本体のJSONを直接文字列として構築します).
現在実行中cargo test すべてのテストが成功したことを確認します.
我々は最終的にすべてのルートのセットをテストすることができますすべての期待通りに動作します.
#[test]
fn new_user_rt_test(){
    let client = common::setup();
    let mut response = client.post("/api/users").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.content_type(), Some(ContentType::JSON));
    assert_eq!(response.body_string(), Some("{\"status\":\"Success\",\"message\":\"Creation of new user\"}".into()));
}

#[test]
fn info_user_rt_test(){
    let client = common::setup();
    let mut response = client.get("/api/users/1").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.content_type(), Some(ContentType::JSON));
    assert_eq!(response.body_string(), Some("{\"status\":\"Success\",\"message\":\"Info for user 1\"}".into()));
}

#[test]
fn update_user_rt_test(){
    let client = common::setup();
    let mut response = client.put("/api/users/1").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.content_type(), Some(ContentType::JSON));
    assert_eq!(response.body_string(), Some("{\"status\":\"Success\",\"message\":\"Update info for user 1\"}".into()));
}

#[test]
fn delete_user_rt_test(){
    let client = common::setup();
    let mut response = client.delete("/api/users/1").dispatch();
    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.content_type(), Some(ContentType::JSON));
    assert_eq!(response.body_string(), Some("{\"status\":\"Success\",\"message\":\"Delete user 1\"}".into()));
}

結論


我々は、我々のCRUDサーバーをスタブに始めました.
もちろん、ルートはまだ意図して動作しませんし、テストは単なる口実ですが、ねえ!すべてが正しく動作し、我々は来るために環境を設定しています.
ステイ!