驗證 OpenAPI 和 JSON Schema
從 OpenAPI 3.1 的發布開始,OpenAPI 文件中使用的 JSON Schema 方言是可設定的。預設情況下,您會獲得 OpenAPI 3.1 Schema 方言,但如果您選擇,可以使用 draft 2020-12 或任何其他方言。這引出了一個問題,如果 OpenAPI 3.1 文件的其中一個組件(JSON Schema)是開放式的,要如何驗證它?在本文中,我們將介紹如何設定 OpenAPI 3.1 文件的預設 JSON Schema 方言,以及如何驗證該文件,包括 JSON Schema,無論您選擇使用哪種方言。
什麼是 JSON Schema 方言?
因為不是每個人都熟悉這個情境中的「方言」一詞,所以讓我們花點時間在繼續之前定義它。JSON Schema 方言是 JSON Schema 的任何獨特實現。這包括 JSON Schema 的任何官方版本,例如 draft-07 或 draft 2020-12,但也包括 JSON Schema 的自訂版本。OpenAPI 實際上在 2.0、3.0 和 3.1 版本中引入了三種 JSON Schema 方言。JSON Schema 方言與 JSON Schema 的核心架構相容,但可能會新增關鍵字、移除關鍵字或修改關鍵字的行為。
OpenAPI 3.1 Schema 方言
預設情況下,OpenAPI 3.1 中的 Schema 假定使用 OpenAPI 3.1 的自訂 JSON Schema 方言。此方言完整支援所有 draft 2020-12 的功能,外加一些額外的關鍵字和 format
值。
使用預設方言驗證
有兩種 Schema 可用於驗證 OpenAPI 3.1 文件。https://spec.openapis.org/oas/3.1/schema
包含驗證文件的所有約束,除了 Schema 之外。您不應該單獨根據此 Schema 驗證您的 OpenAPI 文件。將此 Schema 視為一個抽象 Schema,旨在擴充以包含您正在使用的 JSON Schema 方言的 Schema 驗證支援。
這就是為什麼還有 https://spec.openapis.org/oas/3.1/schema-base
,它使用 OpenAPI 3.1 Schema 方言的驗證支援來擴充抽象 Schema。如果您使用的是純粹開箱即用的 OpenAPI 3.1,這是您要用來驗證文件的 Schema。如果您想使用不同的方言,請繼續閱讀以了解如何擴充主要 Schema,以取得您選擇的方言的驗證支援。
這是透過 JSON Schema 2020-12 中新增的動態參考來實現的。動態參考如何運作的詳細資訊不在本文的範圍內,但我們將涵蓋足夠的內容,讓您可以為您在 OpenAPI 3.1 文件中選擇使用的任何方言建立自己的具體 Schema。
範例
這些範例使用 @hyperjump/json-schema 來驗證 OpenAPI 文件。請注意,動態參考是 JSON Schema 的相對較新功能,許多驗證器尚未支援它們,或僅有有限的支援,或存在錯誤。
不含 Schema 驗證
1import { validate } from "@hyperjump/json-schema/openapi-3-1";
2
3const validateOpenApi = await validate("https://spec.openapis.org/oas/3.1/schema");
4
5const example = YAML.parse(await readFile("./example.openapi.json"));
6const result = validateOpenApi(example);
7console.log(result);
使用 OpenAPI Schema 方言 Schema 驗證
1import { validate } from "@hyperjump/json-schema/openapi-3-1";
2
3(async function () {
4 const validateOpenApi = await validate("https://spec.openapis.org/oas/3.1/schema-base");
5
6 const example = YAML.parse(await readFile("./example.openapi.json"));
7 const result = validateOpenApi(example);
8 console.log(result);
9}());
它是如何運作的?
為了了解其運作方式,讓我們看一下 OpenAPI 3.1 Schema 中的一些選取內容。
這是定義 Schema 物件的位置。$dynamicAnchor
宣告此子 Schema 為可由另一個 Schema 有效覆寫的內容。如果未覆寫,則預設行為是驗證值是否為物件或布林值。不會對 Schema 執行其他驗證。
1$defs:
2 schema:
3 $dynamicAnchor: meta
4 type:
5 - object
6 - boolean
當此 Schema 中的某些內容想要參考 Schema 物件時,它不會像平常一樣參考 #/$defs/schema
,而是使用動態參考來參考先前選取中設定的「meta」動態錨點。現在,另一個 Schema 可以覆寫它解析到的位置,而不是總是解析為 #/$defs/schema
。
1$defs:
2 components:
3 type: object
4 properties:
5 schemas:
6 type: object
7 additionalProperties:
8 $dynamicRef: '#meta'
根據預設方言驗證 Schema 物件
有了這些模糊的基礎構建模組,讓我們導出一個 Schema,該 Schema「擴充」抽象 Schema,以建立一個使用預設方言 meta-schema 驗證 Schema 物件的 Schema。
第一步是包含抽象 Schema。
1$schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org/oas/3.1/schema/latest'
然後,我們需要新增一個 $dynamicAnchor
,它與抽象 Schema 中的 $dynamicAnchor
相符,以覆寫對「meta」的動態參考將解析到的位置。從那裡,我們可以參考預設方言的 meta Schema。
1$schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org/oas/3.1/schema/latest'
4
5$defs:
6 schema:
7 $dynamicAnchor: meta
8 $ref: 'https://spec.openapis.org/oas/3.1/dialect/base'
這足以取得我們想要的 Schema 物件驗證,但還有一些鬆散的結尾也需要處理。jsonSchemaDialect
OpenAPI 3.1 文件中的欄位可用於變更所使用的方言。由於此 Schema 僅支援預設方言,我們希望限制人們將其變更為其他方言。如果他們需要變更它,他們需要一個不同的 Schema 來進行驗證。我們也不希望人們使用 $schema
關鍵字來變更個別 Schema 的方言。
1$schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org/oas/3.1/schema'
4properties:
5 jsonSchemaDialect:
6 $ref: '#/$defs/dialect'
7
8$defs:
9 dialect:
10 const: 'https://spec.openapis.org/oas/3.1/dialect/base'
11 schema:
12 $dynamicAnchor: meta
13 $ref: 'https://spec.openapis.org/oas/3.1/dialect/base'
14 properties:
15 $schema:
16 $ref: '#/$defs/dialect'
有了這個,我們就擁有您在官方 https://spec.openapis.org/oas/3.1/schema-base
Schema 中找到的內容。
支援多種方言
隨著 JSON Schema 2020-12 的採用,支援了 $id
和 $schema
關鍵字,它們共同讓我們可以覆寫 Schema 的預設 JSON Schema 方言。假設我們有一個 OpenAPI 3.1 文件,預設情況下我們使用 JSON Schema 2020-12,但我們也有一些想要使用的舊 JSON Schema draft-07 Schema。
1jsonSchemaDialect: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
2components:
3 schemas:
4 foo:
5 type: object
6 properties:
7 foo:
8 $ref: '#/components/schemas/baz'
9 unevaluatedProperties: false
10 bar:
11 $id: './schemas/bar'
12 $schema: 'https://json-schema.dev.org.tw/draft-07/schema#'
13 type: object
14 properties:
15 bar:
16 $ref: '#/definitions/number'
17 definitions:
18 number:
19 type: number
20 baz:
21 type: string
這裡發生什麼事
首先,我們使用 jsonSchemaDialect
欄位來設定文件的預設方言。透過將預設方言設定為 JSON Schema 2020-12,預設情況下,Schema 不會理解 OpenAPI 3.1 詞彙中新增的關鍵字,例如 discriminator
。只會識別標準 JSON Schema 2020-12 關鍵字。
/components/schemas/foo
Schema 被理解為以 JSON Schema 2020-12 解釋,因為這是我們設定的預設值。
/components/schemas/bar
Schema 將該 Schema 的方言變更為 JSON Schema draft-07。有幾個因素共同作用使其成為可能。$schema
關鍵字設定 Schema 的方言,但 $schema
僅允許出現在它出現的文件的根目錄中。這就是為什麼我們還需要包含 $id
關鍵字的原因。$id
關鍵字有效地將該 Schema 設定為具有其自己的識別碼的獨立文件,且該位置為根目錄。它是嵌入在 OpenAPI 3.1 文件中的獨立文件。您可以將其視為 HTML 中的 iframe。
這樣做的結果是 /components/schemas/bar
無法使用本機參考,例如 #/components/schemas/foo
,來參考 /components/schemas
中的另一個 Schema,因為它現在在技術上位於不同的文件中。有兩種方法可以解決這個問題。一個選項是使用對 OpenAPI 3.1 文件的外部參考,例如 myapi.openapi.yml#/components/schemas/foo
。另一個選項是也為 /components/schemas/foo
提供 $id
,並改為參考該 ./schemas/foo
。
驗證
現在我們了解它是如何運作的,讓我們導出一個 Schema,以驗證預設方言為 JSON Schema 2020-12 且允許 JSON Schema draft-07 作為替代方案的 OpenAPI 3.1 文件。
1$schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org/oas/3.1/schema'
4properties:
5 jsonSchemaDialect:
6 const: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
7required:
8 - jsonSchemaDialect
9
10$defs:
11 schema:
12 $dynamicAnchor: meta
13 properties:
14 $schema:
15 enum:
16 - 'https://json-schema.dev.org.tw/draft/2020-12/schema'
17 - 'https://json-schema.dev.org.tw/draft-07/schema#'
18 allOf:
19 - if:
20 properties:
21 $schema:
22 const: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
23 then:
24 $ref: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
25 - if:
26 type: object
27 properties:
28 $schema:
29 const: 'https://json-schema.dev.org.tw/draft-07/schema#'
30 required:
31 - $id
32 - $schema
33 then:
34 $ref: 'https://json-schema.dev.org.tw/draft-07/schema'
第一個變更是現在需要 jsonSchemaDialect
欄位,因為我們不再使用預設值。
接下來,我們必須更新 Schema 定義,以僅允許我們想要允許的方言的 $schema
值。
如果沒有使用 $schema
關鍵字,或是 $schema
設定為 JSON Schema 2020-12,則第一個 if
/then
將會驗證 schema 為 JSON Schema 2020-12 的 schema。當然,在這種情況下不需要使用 $schema
,但允許使用。
如果存在 $id
且 $schema
指示為 draft-07,則第二個 if
/then
將會驗證 schema 為 JSON Schema draft-07 的 schema。
您可以擴展此模式來支援您想要支援的任意數量的方言。
圖片來源:Gonzalo Facello 在 Unsplash