TL;DR

OpenAPI ドキュメントを自動で生成するための Rust のクレート。エンドポイントにマクロを付けると、そこから OpenAPI が生成される(Code first)。

仕組み

Actix Web、axum、Rocket など、さまざまな Web フレームワーク向けのユーティリティを提供している。ドキュメントを表示するインタフェースは、Swagger UI のほか Redoc や RapiDoc にも対応している。

ドキュメントの定義は #[derive(utoipa::OpenApi)] を付けた構造体に書く。#[openapi(...)] には次の 3 つを指定する。

  • info: タイトルや連絡先などのメタデータ
  • paths: ハンドラ関数。指定する関数には #[utoipa::path] を付け、HTTP メソッドとパス、リクエストボディ、レスポンス(正常系と異常系の両方)を書く
  • components: リクエストやレスポンスの型。ToSchema トレイトを実装する必要があり、derive マクロが用意されている。ここで指定した型は、#[utoipa::path] の中で型名で参照できる

例

axum と Redoc を使い、レスポンスの型に chrono の日付型と UUID を使う場合の例。OpenAPI の情報はリリースビルドに不要なので、#[cfg(debug_assertions)] や #[cfg_attr(debug_assertions, ...)] でデバッグビルドに限っている。

utoipa = { version = "4.2.3", features = ["axum_extras", "uuid", "chrono"] }
utoipa-redoc = { version = "4.0.0", features = ["axum"] }
#[derive(utoipa::OpenApi)]
#[openapi(
    info(
        title = "...",
        contact(name = "...", url = "...", email = "..."),
        description = "...",
    ),
    paths(
        ...
    ),
    components(schemas(
        model::book::CreateBookRequest,
        model::book::CreateBookResponse,
        ...
    ))
)]
pub struct ApiDoc;

ハンドラ関数に #[utoipa::path] を付ける。

#[cfg_attr(
    debug_assertions,
    utoipa::path(
        post,
        path = "/api/v1/books",
        request_body = CreateBookRequest,
        responses(
            (status = 201, description = "蔵書の登録に成功した場合", response = CreateBookResponse),
            (status = 400, description = "リクエストのパラメータに不備があった場合"),
            (status = 401, description = "認証されていないユーザがアクセスした場合"),
            (status = 422, description = "リクエストした蔵書の登録に失敗した場合")
        )
    )
)]
pub async fn register_book(...) ... {...}

リクエストの型に ToSchema を実装する。

#[derive(Debug, Deserialize, Validate)]
#[cfg_attr(debug_assertions, derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CreateBookRequest {
    pub title: String,
    pub author: String,
    pub isbn: String,
    pub description: String,
}

Redoc のエンドポイントを追加する。

#[cfg(debug_assertions)]
use api::openapi::ApiDoc;
#[cfg(debug_assertions)]
use utoipa::OpenApi;
#[cfg(debug_assertions)]
use utoipa_redoc::{Redoc, Servable};
 
// 通常のエンドポイント
let router = Router::new().merge(...);
 
#[cfg(debug_assertions)]
let router = router.merge(Redoc::with_url("/docs", ApiDoc::openapi()));

関連

出典