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()
+ }
+}
utoipa 便利だけど、マクロで定数が使えないの悲しい🥲
Rust で OpenAPI をコードファーストで生成したいとなる場合は、特定の Web フレームワークに依存せずに利用できる柔軟性から utoipa クレートによくお世話になるかと思います。
#[derive(Debug, Serialize, Deserialize, ToSchema)]
struct RequestContent {
#[schema(minimum = 1, maximum = ID_MAX)] // error: no `literal` value
id: usize
}
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 に示した通りです。
そのほかにも、配列に対応する ArrayBuilder や enum に対応する AnyOfBuilder なども提供されていることを確認しました。
utoipa は OpenAPI ドキュメントの組み立てをいずれもビルダーパターンを利用して実装しているようです。
まとめ
utoipa の #[schemas] マクロで定数が使えない問題について、ObjectBuilder で解決できることがわかりました。
Builder パターンを利用すると、自由度がある一方で記述量が増えてしまうデメリットもあるので、いつか定数も使えるようになればとても嬉しいですね。
Discussion