Craft 特性

Craft 允許開發者通過簡單的註解方式自動生成處理函數和端點,同時與 OpenAPI 文件生成無縫整合。

使用場景

Craft 特性在以下場景特別有用:

  • 當你需要從結構體方法快速建立路由處理函數時
  • 當你想要減少手動編寫參數提取和錯誤處理的樣板程式碼時
  • 當你需要為你的 API 自動生成 OpenAPI 文件時
  • 當你希望將業務邏輯與 Web 框架解耦時

基本用法

使用 Craft 特性需要引入以下模組:

use salvo::oapi::extract::*;
use salvo::prelude::*;

建立服務結構體

使用 #[craft] 巨集標註你的 impl 塊,可以將結構體方法轉換為處理函數或端點。

#[derive(Clone)]
pub struct Opts {
    state: i64,
}

#[craft]
impl Opts {
    // 建構函數
    fn new(state: i64) -> Self {
        Self { state }
    }
    
    // 更多方法...
}

建立處理函數

使用 #[craft(handler)] 將方法轉換為處理函數:

#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
    (self.state + *left + *right).to_string()
}

這個方法將成為一個處理函數,它:

  • 自動從查詢參數提取 leftright
  • 存取結構體中的 state 狀態
  • 回傳計算結果作為字串回應

建立端點

使用 #[craft(endpoint)] 將方法轉換為端點:

#[craft(endpoint)]
pub(crate) fn add2(
    self: ::std::sync::Arc<Self>,
    left: QueryParam<i64>,
    right: QueryParam<i64>,
) -> String {
    (self.state + *left + *right).to_string()
}

端點可以利用 Arc 來共享狀態,這在處理並行請求時特別有用。

靜態端點

你也可以建立不依賴於實例狀態的靜態端點:

#[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
    (*left + *right).to_string()
}

在這個例子中,還添加了自訂錯誤回應的描述,這將反映在生成的 OpenAPI 文件中。

參數提取器

Salvo 的 oapi::extract 模組提供了多種參數提取器,最常見的包括:

  • QueryParam<T>: 從查詢字串提取參數
  • PathParam<T>: 從URL路徑提取參數
  • FormData<T>: 從表單資料提取參數
  • JsonBody<T>: 從JSON請求體提取參數

這些提取器會自動完成參數解析和類型轉換,大大簡化了處理函數的編寫。

與 OpenAPI 整合

Craft 特性能自動生成符合 OpenAPI 規範的 API 文件。在範例中:

let router = Router::new()
    .push(Router::with_path("add1").get(opts.add1()))
    .push(Router::with_path("add2").get(opts.add2()))
    .push(Router::with_path("add3").get(Opts::add3()));

// 生成 OpenAPI 文件
let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);

// 添加 OpenAPI 文件和 Swagger UI 路由
let router = router
    .push(doc.into_router("/api-doc/openapi.json"))
    .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

這樣配置後,API 文件將在 /api-doc/openapi.json 端點提供,Swagger UI 將在 /swagger-ui 路徑可用。

完整範例

下面是一個完整的範例,展示了如何使用 Craft 特性建立三種不同類型的端點:

use salvo::oapi::extract::*;
use salvo::prelude::*;
use std::sync::Arc;

#[derive(Clone)]
pub struct Opts {
    state: i64,
}

#[craft]
impl Opts {
    fn new(state: i64) -> Self {
        Self { state }
    }

    #[craft(handler)]
    fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
        (self.state + *left + *right).to_string()
    }

    #[craft(endpoint)]
    pub(crate) fn add2(
        self: ::std::sync::Arc<Self>,
        left: QueryParam<i64>,
        right: QueryParam<i64>,
    ) -> String {
        (self.state + *left + *right).to_string()
    }

    #[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
    pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
        (*left + *right).to_string()
    }
}

#[tokio::main]
async fn main() {
    // 建立共享狀態,初始值為1
    let opts = Arc::new(Opts::new(1));

    // 配置三個端點的路由
    let router = Router::new()
        .push(Router::with_path("add1").get(opts.add1()))
        .push(Router::with_path("add2").get(opts.add2()))
        .push(Router::with_path("add3").get(Opts::add3()));

    // 生成OpenAPI文件
    let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);
    
    // 添加OpenAPI文件和Swagger UI路由
    let router = router
        .push(doc.into_router("/api-doc/openapi.json"))
        .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

    // 在localhost:5800啟動伺服器
    let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}