2 minute read     Posted on:     Updated on:

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

OpenAPI 3.1.0 §4.3 裡提到

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

  1. 跨檔案 $ref 無法使用
  2. 需要額外定義一次 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

  1. 需要手動引入 endpoint

Implementation Example

上述的例子你可以在 ambersun1234/blog-labs/swagger 當中找到

References

Leave a comment