實際上,自訂 Query String,Parameter 以及 Request 的概念幾乎是一樣的,分別是透過實做 FromQueryFromParam 以及 FromRequest 來達到這個目的。

Query String

在 Query String 這個段落中,將寫一個 FromQuery 的範例,這個範例是當 search 這個參數的值是一個必須大於兩個字元的字串,才會被該路由所接受:

use rocket::http::RawStr;
use rocket::request::{Query, FromQuery};

struct Search<'q>(&'q RawStr);

impl<'q> FromQuery<'q> for Search<'q> {
    type Error = &'q str;

    fn from_query(query: Query<'q>) -> Result<Search, Self::Error> {
        // 尋找 key 為 search 且 value 的長度 > 2 的 query
        let key_items = query.filter(|item| item.key == "search" && item.value.len() >= 2);

        // 如果找到的數量為 0 代表沒有 search 或者有 search 但是值的長度不到 2
        let count = key_items.clone().count();
        if count == 0 {
            return Err("no matching search");
        }

        // 將找到的資料包裝成 Search 並返回第 0 個
        // The `ok_or` gets us a `Result`. We will never see `Err(0)`.
        return key_items.map(|i| Search(i.value)).nth(0).ok_or("no matching search");
    }
}

#[get("/products?<search..>")]
fn search_product(search: Search) -> String {
    format!("Search: {}", search.0)
}

fn main() {
    rocket::ignite().mount("/", routes![search_product]).launch();
}

Parameter

在 Parameter 這個段落中,將寫一個 FromParam 的範例,這個範例是當/users/<mobile>路由中的<mobile>參數必須大於10個字元,且必須全部皆是數字,且必須以 09 開頭才能被路由所接受:

use rocket::request::FromParam;
use rocket::http::RawStr;

struct Mobile<'r> {
    value: &'r str
}

impl<'r> FromParam<'r> for Mobile<'r> {
    type Error = &'r str;

    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {

        // 如果 param 的長度小於 10 則返回錯誤
        if param.len() < 10 {
            return Err("length cannot shorter than 10.");
        }
        
        extern crate regex;
        use regex::Regex;

        // 必須全部為 digit
        let re = Regex::new(r"^\d+$").unwrap();

        // 不匹配也返回錯誤
        if !re.is_match(param) {
            return Err("param should contain digit.")
        }

        return Ok(Mobile{value: param.as_str()});
    }
}

#[get("/users/<mobile>")]
fn get_user_with_mobile(mobile: Mobile) -> String
{
    return format!("User with mobile: {}", mobile.value);
}

fn main() {
    rocket::ignite().mount("/", routes![
        get_user_with_mobile
    ]).launch();
}

Request

在 Request 這個段落中,將寫一個 FromRequest 的範例,這個範例是當/profile請求中的 Header 必須包含Api-Key的請求頭,才會被路由所接受。

use rocket::request::{self, Request, FromRequest};
use rocket::Outcome;
use rocket::http::Status;

struct ApiKey(String);

fn is_valid_key(key: &str) -> bool {
    return key == "valid_key";
}

impl<'a, 'r> FromRequest<'a, 'r> for ApiKey {
    type Error = &'r str;

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
        let keys: Vec<_> = request.headers().get("Api-Key").collect();
        match keys.len() {
            // 沒有提供 Api-Key
            0 => Outcome::Failure((Status::BadRequest, "Api-Key no provided")),
            // 提供且 Api-Key 正確
            1 if is_valid_key(keys[0]) => Outcome::Success(ApiKey(keys[0].to_string())),
            // Api-Key 不正確
            1 => Outcome::Failure((Status::BadRequest, "Api-Key invalid")),
            // 其他狀態,代表過多個 Api-Key
            _ => Outcome::Failure((Status::BadRequest, "Too many Api-Key"))
        }
    }
}

#[get("/profile")]
fn profile(api_key: ApiKey) -> String {
    return format!("Valid User, key: {}", api_key.0);
}

fn main() {
    rocket::ignite().mount("/", routes![
        profile
    ]).launch();
}

這樣,只有當請求的 Header 中帶上valid_key時,請求才有辦法進入profile()路由中:

GET /profile HTTP/1.1
Host: localhost:8000
API-Key: valid_key