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 = "不正なリクエストパラメータ"))))]
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 機能を使用して3種類のエンドポイントを作成する完全なサンプル:

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 = "不正なリクエストパラメータ")))]
    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));

    // 3つのエンドポイントルートを設定
    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;
}