2021 年 8 月 4 日,星期三 ·10分鐘閱讀

JSON Schema 捆綁作業終於正式化

我曾經說過「如果您最近沒有重寫 OpenAPI 捆綁實作,那麼您就不支援 OpenAPI 3.1」。這個觀察可能是正確的,但或許更多細節會有幫助?在 oas-kit 中實作 OAS 3.1 和 JSON Schema 草案 2020-12 的支援時,閱讀 JSON Schema 規範中關於捆綁複合文件的章節,我仍然不完全清楚符合規範的工具應該做什麼。幸好,Ben Hutton 在這裡透過一個實際的例子來釐清一切。 - Mike Ralphson,OAI TSC

捆綁作業的重要性再次提升

OpenAPI 長期以來一直將焦點放在 JSON Schema 上,而 OpenAPI 3.1 的發布對這兩個專案的未來都有巨大的影響。我真的很興奮。

使用 OpenAPI 的平台和程式庫的開發人員以前沒有經歷過如此大的變動,而且我的感覺是,可能需要多次發佈才能正確實作所有 JSON Schema 提供的全新閃亮功能。

雖然從 JSON Schema 草案 04 到草案 2020-12 的變更數量龐大,而且相關的部落格文章可能多到令人覺得無趣,但草案 2020-12 的關鍵「功能」之一是定義的捆綁程序。(草案 04 是 OAS 在 3.1.0 版之前使用的 JSON Schema 版本;或者更確切地說,是它的子集/超集。)

事實上,如果有的話,捆綁作業會比以往任何時候都更重要。OAS 3.1 引入完整 JSON Schema 支援,大幅提高了開發人員使用現有 JSON Schema 文件,在新的和更新的 OpenAPI 定義中以參考方式使用的可能性。最終的真實來源很重要,而且通常是 JSON Schema。

許多工具不支援參考外部資源。捆綁作業是一種方便的方式,可將分散在多個檔案中的架構資源封裝到單一檔案中,以在其他地方(例如 OpenAPI 文件)使用。

現有解決方案?新解決方案!

有幾個程式庫提供捆綁解決方案,但它們都有一些注意事項,而且我至今還沒有看到任何完全了解 JSON Schema 的程式庫。這些程式庫中最流行的是 json-schema-ref-parser,但它報告指出,它並非旨在了解 JSON Schema,而僅旨在涵蓋 JSON Reference 規範(現在已將其重新捆綁到 JSON Schema 規範中)。

我們希望為您提供一個標準的實作(對吧,Mike?!)以及足夠的資訊,讓您開始使用您選擇的語言建構自己的實作。(不過,在開發實作時,最好還是閱讀完整的規範。)

捆綁作業基本原理

首先,讓我們看看 JSON Schema 草案 2020-12 中的一些關鍵定義。$id 關鍵字用於識別「架構資源」。在下面的範例中,資源的 $idhttps://jsonschema.dev/schemas/mixins/integer

架構
{ "$id": "https://jsonschema.dev/schemas/mixins/integer", "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "description": "Must be an integer", "type": "integer"}

「複合架構文件」是具有多個內嵌 JSON Schema 資源的 JSON 文件。以下是我們稍後會稍微拆解的簡化範例。

架構
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer-bundle", "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "description": "Must be a non-negative integer", "$comment": "A JSON Schema Compound Document. Aka a bundled schema.", "$defs": { "https://jsonschema.dev/schemas/mixins/integer": { "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "$id": "https://jsonschema.dev/schemas/mixins/integer", "description": "Must be an integer", "type": "integer" }, "https://jsonschema.dev/schemas/mixins/non-negative": { "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "$id": "https://jsonschema.dev/schemas/mixins/non-negative", "description": "Not allowed to be negative", "minimum": 0 }, "nonNegativeInteger": { "allOf": [ { "$ref": "/schemas/mixins/integer" }, { "$ref": "/schemas/mixins/non-negative" } ] } }, "$ref": "#/$defs/nonNegativeInteger"}

請注意,架構捆綁作業和使用多個定義並非表示非負整數所必需。此範例僅用於說明目的,而且以下架構完全適合在不使用捆綁作業的情況下表示非負整數。json {"type": "integer", "minimum": 0}

最後,讓我們看一下根據 JSON Schema 規範對「捆綁」的精心定義

「複合綱要文件」的捆綁處理程序定義為:取得對外部綱要資源的參考(例如「$ref」),並將被參考的綱要資源嵌入到參考文件中。捆綁的處理方式應確保基礎文件和任何被參考/嵌入的文件中的所有 URI(用於參考)都不需要修改。

有了這些定義,現在我們可以看看 JSON 綱要資源的定義捆綁處理程序了!我們在這篇文章中只會介紹理想的情況。這裡的目標是完全沒有外部綱要資源。

請注意,這篇文章不涵蓋「完全取消參考」,也就是移除綱要中所有使用 $ref 的情況。這是不建議的,而且不總是可行,例如當有自我參考時。

捆綁簡單的外部資源

在我們的第一個範例中,我們有一個理想的捆綁情況。每個綱要都定義了 $id$schema,這使得捆綁處理程序變得簡單。我們將在後面的範例中介紹其他各種情況和邊緣案例,但最好讓每個資源都定義自己的身分和方言。我們的主要綱要資源使用就地應用程式 $ref 參考了另外兩個綱要資源,其值為相對 URI。相對 URI 會針對基礎 URI 進行解析,在此範例中,基礎 URI 位於主要綱要資源的 $id 值中。透過結合「integer」和「non-negative」綱要,我們建立了一個「非負整數」綱要。

架構
{ "$id": "https://jsonschema.dev/schemas/mixins/integer", "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "description": "Must be an integer", "type": "integer"}
架構
{ "$id": "https://jsonschema.dev/schemas/mixins/non-negative", "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "description": "Not allowed to be negative", "minimum": 0}
架構
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer", "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "description": "必須是非負整數", "$comment": "一個使用多個外部參照的 JSON Schema", "$defs": { "nonNegativeInteger": { "allOf": [ { "$ref": "/schemas/mixins/integer" }, { "$ref": "/schemas/mixins/non-negative" } ] } }, "$ref": "#/$defs/nonNegativeInteger"}

如果實作中將「非負整數」(non-negative-integer)綱要作為主要綱要,則其他綱要也需要對該實作可用。此時,該實作如何載入綱要並不重要,因為它們在 $id 中定義了完整的 URI 作為其識別碼。任何載入綱要的實作都應建立一個內部索引,將在 $id 中定義的綱要 URI 對應到綱要資源。

請記住,任何為 $id 提供值的綱要,都被視為綱要資源。

讓我們解析(反參考)主要綱要中的一個參考。"$ref": "/schemas/mixins/integer" 會解析為完全限定的 URI https://jsonschema.dev/schemas/mixins/integer,其做法是先判斷基礎 URI,然後將相對 URI 解析為相對於該基礎 URI 的 URI。接著,實作應檢查其內部綱要識別符和綱要資源索引,找到匹配項,並使用先前載入的對應綱要資源。

捆綁程序已完成。先前外部參考的綱要會按原樣複製到主要綱要的 $defs 中。$defs 物件的鍵是識別 URI,但它們可以是任何值,因為這些值不會被參考(如果你喜歡,它們可以是 UUID)。看看我們最終捆綁的綱要… 我是指「複合綱要文件」,我們現在在單個綱要文件中嵌入了多個綱要資源。

架構
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer-bundle", "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "description": "Must be a non-negative integer", "$comment": "A JSON Schema Compound Document. Aka a bundled schema.", "$defs": { "https://jsonschema.dev/schemas/mixins/integer": { "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "$id": "https://jsonschema.dev/schemas/mixins/integer", "description": "Must be an integer", "type": "integer" }, "https://jsonschema.dev/schemas/mixins/non-negative": { "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "$id": "https://jsonschema.dev/schemas/mixins/non-negative", "description": "Not allowed to be negative", "minimum": 0 }, "nonNegativeInteger": { "allOf": [ { "$ref": "/schemas/mixins/integer" }, { "$ref": "/schemas/mixins/non-negative" } ] } }, "$ref": "#/$defs/nonNegativeInteger"}

當最初載入並評估捆綁綱要時,實作應像以前一樣建立其內部綱要識別符和綱要資源索引。用於參考這些綱要資源的相對 URI 不需要更改。

要查看此捆綁綱要是否按預期工作,最簡單的方法是將其貼到 https://json-schema.hyperjump.io 中,然後嘗試不同的實例值。我希望在接下來的幾個月內為 https://jsonschema.dev 帶來一些更新,但由於我們持續提升 JSON Schema 作為一個組織,所以時間很緊。

值得記住的是,本文中的範例展示了理想情況,即遵循最佳實務的情況。JSON Schema 規範確實定義了針對非理想情況和邊緣案例的其他流程(例如,當 $id$schema 未設定時),但是,某些解決方案可能間接與複合 JSON 綱要文件有關。例如,建立基礎 URI 遵循 RFC3986 中列出的步驟,而 JSON Schema 並未重新定義這些步驟。

OpenAPI 規範範例

讓我們看看這如何在 OpenAPI 定義中使用。

1openapi: 3.1.0
2info:
3  title: API
4  version: 1.0.0
5components:
6  schemas:
7    non-negative-integer:
8      $ref: 'https://jsonschema.dev/schemas/examples/non-negative-integer'

我們先從輸入的 OpenAPI 3.1.0 規範文件開始。為了簡潔起見,我們只顯示具有單個元件的 components 部分,但假設文件的其他部分使用了元件綱要「non-negative-integer」。

「non-negative-integer」只有對 JSON 綱要資源的單一參考。參考 URI 是絕對 URI,包括網域和路徑,這意味著不需要進行任何「將相對 URI 解析為相對於基礎 URI」的處理。

所有用於解析和捆綁參考的綱要都會提供給捆綁工具。在將綱要載入實作後,它們的原始實際位置不再重要。

1openapi: 3.1.0
2info:
3  title: API
4  version: 1.0.0
5components:
6  schemas:
7    # This name has not changed, or been replaced, as it already existed and is likely to be referenced elsewhere
8    non-negative-integer:
9      # This Reference URI hasn't changed
10      $ref: 'https://jsonschema.dev/schemas/examples/non-negative-integer'
11    # The path name already existed. This key doesn't really matter. It could be anything. It's just for human readers. It could be an MD5!
12    non-negative-integer-2:
13      $schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
14      $id: 'https://jsonschema.dev/schemas/examples/non-negative-integer'
15      description: Must be a non-negative integer
16      $comment: A JSON Schema that uses multiple external references
17      $defs:
18        nonNegativeInteger:
19          allOf:
20          # These references remain unchanged because they rely on the base URI of this schema resource
21          - $ref: /schemas/mixins/integer
22          - $ref: /schemas/mixins/non-negative
23      $ref: '#/$defs/nonNegativeInteger'
24    integer:
25      $schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
26      $id: 'https://jsonschema.dev/schemas/mixins/integer'
27      description: Must be an integer
28      type: integer
29    non-negative:
30      $schema: 'https://json-schema.dev.org.tw/draft/2020-12/schema'
31      $id: 'https://jsonschema.dev/schemas/mixins/non-negative'
32      description: Not allowed to be negative
33      minimum: 0

綱要會插入到 OAS 文件的 components/schemas 位置。schemas 物件中使用的鍵對於參考解析並不重要,但您需要避免潛在的重複。參考不需要更改,而產生的捆綁文件或複合文件的處理器應查找 OAS 文件中嵌入的綱要資源的使用情況,並追蹤 $id 值。

但是,如果…?

你們之中精明的人可能已經注意到,複合文件可能無法使用在文件根目錄定義的方言的中繼綱要進行正確驗證。我們的主要貢獻者之一提煉了一個很棒的解釋,他同意與大家分享。

如果嵌入的綱要具有與父綱要不同的 $schema,則複合綱要文件無法根據中繼綱要進行驗證,除非將其解構成單獨的綱要資源並將適當的中繼綱要應用於每個資源。這並不表示複合綱要文件在不解構的情況下不可用,這只是意味著實作需要知道 $schema 可以在評估期間更改,並適當處理此類更改。」- Jason Desrosiers。

如果您想更深入地了解邊緣案例情況,請務必告訴我們。

您可以透過 @jsonschema 或我們的 Slack 伺服器 與我們聯繫。

我希望您會同意,Ben 在這裡為我們澄清了整個流程,並且在編寫將多個資源捆綁到複合 OpenAPI 文件中的工具時,我們可以利用此範例來完全滿足 JSON Schema 的捆綁期望。謝謝你,Ben! - Mike

商業照片由 vanitjan 建立 - www.freepik.com

本文最初發表在 JSON Schema 部落格上,其標準位置為 https://json-schema.dev.org.tw/blog/posts/bundling-json-schema-compound-documents