OpenAPI ドキュメント生成

OpenAPI は、RESTful API のインターフェース設計を記述するためのオープンソース仕様です。JSON または YAML 形式で、API のリクエストとレスポンスの構造、パラメータ、戻り値の型、エラーコードなどの詳細を定義し、クライアントとサーバー間の通信をより明確かつ標準化されたものにします。

OpenAPI は、もともと Swagger 仕様のオープンソース版でしたが、現在では独立したプロジェクトとなり、多くの大企業や開発者から支持されています。OpenAPI 仕様を使用することで、開発チームのコラボレーションが向上し、コミュニケーションコストが削減され、開発効率が向上します。同時に、OpenAPI は開発者に API ドキュメント、モックデータ、テストケースなどを自動生成するツールを提供し、開発とテスト作業を便利にします。

Salvo は OpenAPI の統合を提供しています(utoipa をベースに修正)。salvo は自身の特性に基づき、Handler から関連する OpenAPI データ型情報を非常にエレガントに自動取得します。salvo はまた、SwaggerUI、scalar、rapidoc、redoc などの人気のあるオープンソースの OpenAPI インターフェースも統合しています。

Rust の型名が長く、OpenAPI での使用に適さない場合があるため、salvo-oapiNamer 型を提供し、必要に応じてルールをカスタマイズし、OpenAPI 内の型名を変更することができます。

サンプルコード

main.rs
Cargo.toml
use salvo::oapi::extract::*;
use salvo::prelude::*;

#[endpoint]
async fn hello(name: QueryParam<String, false>) -> String {
    format!("Hello, {}!", name.as_deref().unwrap_or("World"))
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();

    let router = Router::new().push(Router::with_path("hello").get(hello));

    let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);

    let router = router
        .unshift(doc.into_router("/api-doc/openapi.json"))
        .unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));

    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}

ブラウザで http://localhost:8698/swagger-ui を入力すると、Swagger UI のページが表示されます。

Salvo における OpenAPI の統合は非常にエレガントです。上記の例では、通常の Salvo プロジェクトと比較して、以下の手順のみを行っています:

  • Cargo.tomloapi 機能を有効化: salvo = { workspace = true, features = ["oapi"] };
  • [handler][endpoint] に置き換え;
  • name: QueryParam<String, false> を使用してクエリ文字列の値を取得。http://localhost/hello?name=chris という URL にアクセスすると、この name クエリ文字列が解析されます。QueryParam<String, false>false は、このパラメータが省略可能であることを意味します。http://localhost/hello にアクセスしてもエラーにはなりません。逆に、QueryParam<String, true> はこのパラメータが必須であることを意味し、提供されない場合はエラーが返されます。
  • OpenAPI を作成し、対応する Router を作成。OpenApi::new("test api", "0.0.1").merge_router(&router)merge_router は、この OpenAPI が特定のルートを解析して、それ自身とその子孫ルートから必要なドキュメント情報を取得することを意味します。一部のルートの Handler はドキュメント生成に必要な情報を提供していない可能性があり、それらのルートは無視されます(例えば、#[endpoint] マクロではなく #[handler] マクロで定義された Handler)。つまり、実際のプロジェクトでは、開発の進捗などの理由により、OpenAPI ドキュメントを生成しないか、部分的に生成するかを選択できます。後で OpenAPI インターフェースの生成数を段階的に増やすことができ、必要な作業は #[handler]#[endpoint] に変更し、関数シグネチャを修正するだけです。

データ抽出器

use salvo::oapi::extract::*; を使用して、事前定義された一般的なデータ抽出器をインポートできます。抽出器は、Salvo が OpenAPI ドキュメントを生成するために必要な情報を提供します。

  • QueryParam<T, const REQUIRED: bool>: クエリ文字列からデータを抽出する抽出器。QueryParam<T, false> はこのパラメータが必須ではなく、省略可能であることを意味します。QueryParam<T, true> はこのパラメータが必須であり、省略できないことを意味します。提供されない場合はエラーが返されます。
  • HeaderParam<T, const REQUIRED: bool>: リクエストヘッダーからデータを抽出する抽出器。HeaderParam<T, false> はこのパラメータが必須ではなく、省略可能であることを意味します。HeaderParam<T, true> はこのパラメータが必須であり、省略できないことを意味します。提供されない場合はエラーが返されます。
  • CookieParam<T, const REQUIRED: bool>: リクエストの Cookie からデータを抽出する抽出器。CookieParam<T, false> はこのパラメータが必須ではなく、省略可能であることを意味します。CookieParam<T, true> はこのパラメータが必須であり、省略できないことを意味します。提供されない場合はエラーが返されます。
  • PathParam<T>: リクエスト URL からパスパラメータを抽出する抽出器。このパラメータが存在しない場合、ルートのマッチングは成功しないため、省略可能な状況は存在しません。
  • FormBody<T>: リクエストで送信されたフォームから情報を抽出します。
  • JsonBody<T>: リクエストで送信された JSON 形式のペイロードから情報を抽出します。

#[endpoint]

OpenAPI ドキュメントを生成する際には、通常の #[handler] マクロの代わりに #[endpoint] マクロを使用する必要があります。これは実際には強化版の #[handler] マクロです。

  • 関数シグネチャから OpenAPI 生成に必要な情報を取得できます。
  • シグネチャを通じて提供が不便な情報については、#[endpoint] マクロに属性を直接追加することで提供できます。この方法で提供された情報は、関数シグネチャから取得された情報とマージされ、競合がある場合は関数シグネチャから提供された情報が上書きされます。

Rust 組み込みの #[deprecated] 属性を使用して、特定の Handler が非推奨であることをマークできます。#[deprecated] 属性は非推奨の理由やバージョンなどの情報を追加することをサポートしていますが、OpenAPI はこれらの情報をサポートしていないため、OpenAPI 生成時には無視されます。

コード内のドキュメントコメント部分は自動的に抽出され、OpenAPI の生成に使用されます。最初の行は summary の生成に使用され、コメント全体は description の生成に使用されます。

/// これはオペレーションの概要です
///
/// ドキュメントコメントのすべての行がオペレーションの説明に含まれます。
#[endpoint]
fn endpoint() {}

ToSchema

#[derive(ToSchema)] を使用してデータ構造を定義できます:

#[derive(ToSchema)]
struct Pet {
    id: u64,
    name: String,
}

#[salvo(schema(...))] を使用してオプションの設定を定義できます:

  • example = ...json!(...) を指定できます。json!(...)serde_json::json! によって serde_json::Value に解析されます。

    #[derive(ToSchema)]
    #[salvo(schema(example = json!({"name": "bob the cat", "id": 0})))]
    struct Pet {
        id: u64,
        name: String,
    }
  • xml(...) は Xml オブジェクトの属性を定義するために使用できます:

    #[derive(ToSchema)]
    struct Pet {
        id: u64,
        #[salvo(schema(xml(name = "pet_name", prefix = "u")))]
        name: String,
    }

ToParameters

構造体のフィールドから [パスパラメータ][path_parameters] を生成します。

これは [ToParameters][to_parameters] トレイトの #[derive] 実装です。

通常、パスパラメータは endpoint の [#[salvo_oapi::endpoint(...parameters(...))]][path_parameters] で定義する必要があります。しかし、パラメータを定義するために [struct][struct] を使用する場合、この手順を省略できます。それでも、説明を追加したりデフォルト設定を変更したりする必要がある場合は、[プリミティブ型][primitive] や [String][std_string] のパスパラメータ、または [タプル] スタイルのパスパラメータは parameters(...) で定義する必要があります。

Rust 組み込みの #[deprecated] 属性を使用してフィールドを非推奨としてマークでき、これは生成される OpenAPI 仕様に反映されます。

#[deprecated] 属性は、非推奨の理由やどのバージョンから非推奨になったかなどの追加情報をサポートしていますが、OpenAPI はサポートしていません。OpenAPI は非推奨かどうかを決定するブール値のみをサポートしています。#[deprecated = "There is better way to do this"] のように理由付きで非推奨を宣言することは完全に可能ですが、この理由は OpenAPI 仕様には表示されません。

構造体フィールドのドキュメントコメントは、生成される OpenAPI 仕様のパラメータ説明として使用されます。

#[derive(salvo_oapi::ToParameters, serde::Deserialize)]
struct Query {
    /// 名前で Todo アイテムをクエリします。
    name: String
}

#[salvo(parameters(...))] の ToParameters コンテナ属性

以下の属性は、ToParameters から派生した構造体のコンテナ属性 #[salvo(parameters(…))] で使用できます:

  • names(...) パスパラメータとして使用される構造体の無名フィールドに対して、カンマ区切りの名前リストを定義します。無名構造体でのみサポートされます。
  • style = ... すべてのパラメータのシリアライズ方法を定義でき、[ParameterStyle][style] によって指定されます。デフォルト値は parameter_in 属性に基づいています。
  • default_parameter_in = ... このフィールドのパラメータが使用するデフォルトの位置を定義し、その値は [parameter::ParameterIn][in_enum] から取得されます。この属性が提供されない場合、デフォルトは query になります。
  • rename_all = ... serderename_all の代替として機能します。実際には同じ機能を提供します。

names を使用して、単一の無名パラメータに名前を定義します。

# use salvo_oapi::ToParameters;

#[derive(ToParameters, serde::Deserialize)]
#[salvo(parameters(names("id")))]
struct Id(u64);

names を使用して、複数の無名パラメータに名前を定義します。

# use salvo_oapi::ToParameters;

#[derive(ToParameters, serde::Deserialize)]
#[salvo(parameters(names("id", "name")))]
struct IdAndName(u64, String);

#[salvo(parameter(...))] の ToParameters フィールド属性

以下の属性は、構造体フィールドで #[salvo(parameter(...))] を使用できます:

  • style = ... パラメータが [ParameterStyle][style] によってどのようにシリアライズされるかを定義します。デフォルト値は parameter_in 属性に基づいています。
  • parameter_in = ... [parameter::ParameterIn][in_enum] からの値を使用して、このフィールドパラメータがどこにあるかを定義します。この値が提供されない場合、デフォルトは query になります。
  • explode object または array 内の各パラメータに対して新しい parameter=value ペアを作成するかどうかを定義します。
  • allow_reserved パラメータ値に予約文字 :/?#[]@!$&'()*+,;= を許可するかどうかを定義します。
  • example = ... メソッド参照または json!(...) を指定できます。指定された例は、基になるパラメータ型の例を上書きします。
  • value_type = ... OpenAPI 仕様でフィールドが使用するデフォルトの型をオーバーライドするために使用できます。デフォルトの型が実際の型と一致しない場合、例えば [ToSchema][to_schema] または [プリミティブ型][primitive] で定義されていないサードパーティの型を使用する場合に便利です。値は、通常 JSON にシリアライズ可能な任意の Rust 型、または Object のようなカスタム型にすることができます。Object は汎用 OpenAPI オブジェクトとしてレンダリングされます。
  • inline 有効にすると、このフィールドの型定義は [ToSchema][to_schema] から取得する必要があり、その定義はインライン化されます。
  • default = ... メソッド参照または json!(...) を指定できます。
  • format = ... [KnownFormat][known_format] 列挙型のバリアント、または文字列形式のオープンな値にすることができます。デフォルトでは、フォーマットは OpenAPI 仕様に従ってプロパティの型に基づいて推論されます。
  • write_only プロパティが 書き込み 操作 POST,PUT,PATCH のみで使用され、GET では使用されないことを定義します。
  • read_only プロパティが 読み取り 操作 GET のみで使用され、POST,PUT,PATCH では使用されないことを定義します。
  • nullable プロパティが null になり得るかどうかを定義します(必須ではないこととは異なることに注意)。
  • required = ... パラメータを必須にするために使用されます。ルールを参照
  • rename = ... serderename の代替として機能します。実際には同じ機能を提供します。
  • multiple_of = ... 値の倍数を定義するために使用されます。このキーワードの値でパラメータ値を除算した結果が整数である場合にのみ、パラメータ値は有効と見なされます。倍数の値は厳密に 0 より大きくなければなりません。
  • maximum = ... 値を含む上限を定義するために使用されます。
  • minimum = ... 値を含む下限を定義するために使用されます。
  • exclusive_maximum = ... 値を含まない上限を定義するために使用されます。
  • exclusive_minimum = ... 値を含まない下限を定義するために使用されます。
  • max_length = ... string 型の値の最大長を定義するために使用できます。
  • min_length = ... string 型の値の最小長を定義するために使用できます。
  • pattern = ... フィールド値が一致しなければならない有効な正規表現を定義するために使用され、正規表現は ECMA-262 バージョンを使用します。
  • max_items = ... array 型のフィールドで許可される最大項目数を定義するために使用できます。値は非負の整数でなければなりません。
  • min_items = ... array 型のフィールドで許可される最小項目数を定義するために使用できます。値は非負の整数でなければなりません。
  • with_schema = ... デフォルトの schema の代わりに関数参照を使用して作成された schema を使用します。関数は fn() -> Into<RefOr<Schema>> というシグネチャを満たす必要があります。引数を受け取らず、RefOr<Schema> に変換可能な値を返さなければなりません。
  • additional_properties = ... HashMap や [BTreeMap](https://doc.rust-lang.org/std/collections