🧱

utoipa の #[schema] proc_macro で定数が使えない時は、ObjectBuilder を使おう

に公開

TL;DR

 use serde::{Deserialize, Serialize};
 use utoipa::{
    ToSchema,
+   openapi::{Object, ObjectBuilder},
 };

 const ID_MAX: usize = 1000;

 #[derive(Debug, Serialize, Deserialize, ToSchema)]
 struct RequestContent {
-    #[schema(minimum = 1, maximum = ID_MAX)] // ID_MAX 定数が使えない
+    #[schema(schema_with = Self::id_schema)]
     id: usize
 }

+impl RequestContent {
+    fn id_schema() -> Object {
+        ObjectBuilder::new()
+            .minimum(Some(1))
+            .maximum(Some(ID_MAX))
+            .build()
+    }
+}

https://docs.rs/utoipa/latest/utoipa/openapi/schema/struct.ObjectBuilder.html

utoipa 便利だけど、マクロで定数が使えないの悲しい🥲

Rust で OpenAPI をコードファーストで生成したいとなる場合は、特定の Web フレームワークに依存せずに利用できる柔軟性から utoipa クレートによくお世話になるかと思います。

#[derive(Debug, Serialize, Deserialize, ToSchema)]
struct RequestContent {
   #[schema(minimum = 1, maximum = ID_MAX)] // error: no `literal` value
   id: usize
}
stdout
 1  error: no `literal` value found after this point
   --> src/main.rs:33:37
    |
 33 |     #[schema(minimum = 1, maximum = ID_MAX)]
    |                                     ^^^^^^
 
 error: could not compile `api` (bin "api") due to 1 previous error

しかしならがら、maximumなどのよく使う属性でさえも、リテラル(直接数値をコードに記載する)しかサポートしておらず、定数値を利用できないという欠点があります。
これは、定数値をスキーマ情報として利用したいというのはあまりにもよくあるユースケースのため、マクロが定数をサポートしてないのは不便です。

また、このエラーを回避しようと安直にその定数をリテラルにしてしまうと、DRY 原則を違反するので良い回避策ではないことは明確です。

Builder パターンを使おう 🧱

この問題の解決のために、utoipa の類似クレートである aide を1度試してみたのですが、設計的に、マクロで定数が使えるというものではなく、Builder パターンを利用して、属性を設定していくというようなものでした。

それくらい、utoipa でもやっているだろうと思い、utoipa のドキュメントを調べたところ ObjectBuilder というものがありました。

具体的な記述は、TL;DR に示した通りです。

https://docs.rs/utoipa/latest/utoipa/openapi/schema/struct.ObjectBuilder.html

そのほかにも、配列に対応する ArrayBuilder や enum に対応する AnyOfBuilder なども提供されていることを確認しました。
utoipa は OpenAPI ドキュメントの組み立てをいずれもビルダーパターンを利用して実装しているようです。

https://docs.rs/utoipa/latest/utoipa/openapi/schema/struct.ArrayBuilder.html

https://docs.rs/utoipa/latest/utoipa/openapi/schema/struct.AnyOfBuilder.html

まとめ

utoipa の #[schemas] マクロで定数が使えない問題について、ObjectBuilder で解決できることがわかりました。
Builder パターンを利用すると、自由度がある一方で記述量が増えてしまうデメリットもあるので、いつか定数も使えるようになればとても嬉しいですね。

Discussion