如何正確模組化你的 OpenAPI 文件 - Node.js 為例
Preface
OpenAPI | Swagger | |
---|---|---|
Picture | ||
Picture Reference | https://swagger.io/ | https://swagger.io/ |
OpenAPI 的規範在 2021/02 的時候來到 3.0,而它跟 Swagger 的關係就有點像是 Docker 跟 Open Container Initiative 的關係
也就是說是 Swagger 的開發團隊將他們的規範,貢獻出去給 OpenAPI Initiative
所以如果你看到 Swagger, 可以簡單的把它聯想為 OpenAPI(還是要注意他們的關係)
簡言之,OpenAPI 是文件的規範,Swagger 除了貢獻規範之外它還有其他的工具,這裡就不贅述
Introduction
An OpenAPI document MAY be made up of a single document or be divided into multiple,
connected parts at the discretion of the author.
從上述可以得知,OpenAPI 文件是可以進行模組化處理的
不過使用 swagger-ui-express 的時候
需要用一點點的方法才可以 reference 到其他文件
所以這篇 blog 會詳細紀錄如何達到這件事情,以及有哪些坑
Reference Anchor $ref
OpenAPI 有一個我發現很討厭的事情是,它寫起來又臭又長
理所當然的能夠將文件拆分,不僅改寫容易,模組化也有助於 maintain
慶幸的是,OpenAPI 裡面你可以使用 $ref
的關鍵字
根據 OpenAPI 3.1.0 §4.8.23
$ref
可以用於 internal 或是 external reference, 其中 $ref
必須是 URI 的形式(注意到它跟 URL 的差異)
Internal Reference
# main.yaml
$ref: '#/components/schemas/Cat'
要指到同一個文件的 reference 是這樣寫
其中,#
字號代表該文件下的,後綴就代表指向文件內容的 URI
# main.yaml
...
components:
schemas:
Dog:
description: This is a dog
Cat:
description: This is a cat
所以它會從 document root 開始找,找到 components 再找到 schema 再找到 Pet
因此上述的 ref 它會被替換成
description: This is a pet
External Reference
# main.yaml
$ref: './pet.yaml#/components/schemas/Cat'
# pet.yaml
components:
schemas:
Dog:
description: This is a dog
Cat:
description: This is a cat
外部 reference 就是在前面新增檔案位置
所以 main.yaml
經過編譯後會長成這樣
description: This is a cat
需要注意 encode 的問題,可參考 JSON pointer
Circular Reference
在 OpenAPI 3.1.0 的規範中,並沒有實際的提到對於 circular reference 的限制
實際上在測試的時候,它也是允許的,並不會報錯
只是說有一些工具如 Redocly OpenAPI VS Code extension
對於 circular reference 沒辦法正確的識別,會造成套件部份失效
這個就要額外注意
JSON pointer
在使用 $ref
的時候有一點要注意,如果你是 reference 到 endpoint 的時候
該 endpoint url 必須要 encode
RFC 6901
encode 在一般情況下是使用 URL encode, 但是有些字元在 JSON pointer 裡有特殊的意義
根據 RFC 6901 所述
Because the characters ‘~’ (%x7E) and ‘/’ (%x2F) have special
meanings in JSON Pointer, ‘~’ needs to be encoded as ‘~0’ and ‘/’
needs to be encoded as ‘~1’ when these characters appear in a
reference token.
所以當你想要 reference url 的時候要稍微處理一下
比如說 /cat/{catId}
要被 encode 成
$ref: "./cat/cat.yaml#/~1cat~1%7BcatId%7D"
其中
/
是~1
{
是%7B
}
是%7D
swagger-jsdoc
swagger-jsdoc 本身可以拿來組合多個 yaml 檔
將它合併成完整的一個 OpenAPI doc
// config.js
import swaggerJSDoc from "swagger-jsdoc";
export const option = swaggerJSDoc({
definition: {
openapi: "3.1.0",
info: {
title: "Swagger API Test",
description:
"This document shows how to use $ref in multiple swagger file",
version: "1.0.0",
servers: [
{
url: "http://localhost",
},
],
},
},
apis: ["./doc/**/*.yaml"],
});
// doc.js
import express from "express";
import swaggerUi from "swagger-ui-express";
import { option } from "./swagger-jsdoc/config.js";
const uiOption = {
swaggerOptions: {
docExpansion: false,
},
};
const app = express();
app.use("/", swaggerUi.serve, swaggerUi.setup(option, uiOption));
app.listen(3000);
從上述設定檔可以看到,我們指定了 apis: ["./doc/**/*.yaml"]
因此,它會找到所有的文件檔並生成一個新的檔案,所以最後 express 的資料,會是處理過後的
值得一提的是,跨檔案使用 $ref
是沒有效果的
因為它合併成一個檔案了,所以那些 external link 都會找不到
Cons
- 跨檔案
$ref
無法使用 - 需要額外定義一次 info, servers … 等等的區塊
這裡要稍微提一下,其實你可以在 yaml 那裡單純的從 path 開始寫,info, servers 不需要兩邊寫
只不過如果你有用 redocly 這類的 linter, 針對單純的 yaml 它會報錯
但這部份就看你們的選擇
swagger-combine
swagger-combine 也是另外一個可行的選擇
// config.js
import swaggerCombine from "swagger-combine";
export const option = await swaggerCombine("./doc/api.yaml");
// doc.js
import express from "express";
import swaggerUi from "swagger-ui-express";
import { option } from "./swagger-combine/config.js";
const uiOption = {
swaggerOptions: {
docExpansion: false,
},
};
const app = express();
app.use("/", swaggerUi.serve, swaggerUi.setup(option, uiOption));
app.listen(3000);
僅須簡單的將你的進入點丟給它處理,swagger-combine 就會自動 resolve 所有 reference
此外你也不需要像是 swagger-jsdoc 一樣重複定義區塊
Cons
- 需要手動引入 endpoint
Implementation Example
上述的例子你可以在 ambersun1234/blog-labs/swagger 當中找到
References
- maxdome/swagger-combine
- How to split a Swagger spec into smaller files
- Splitting your swagger spec into multiple files in a Node project
- OpenAPI 和 Swagger 是什麼?他們是什麼關係?Swagger 規範和 Swagger 工具不同嗎?
- Using $ref
- When do we need to add file extension when importing JavaScript modules?
- Including Multiple File Paths in Open API Doc
Leave a comment