2021年8月4日(水曜日) 10約○分

JSON スキーマバンドリングが正式に規定されました

「最近、OpenAPI のバンドリング実装を書き直していなければ、OpenAPI 3.1 をサポートしていません。」と私は言っています。この発言は正しいかもしれませんが、もう少し詳しく説明した方が良いかもしれませんね?oas-kitでOAS 3.1とJSON スキーマドラフト2020-12のサポートを実装する際、JSON スキーマ仕様の複合ドキュメントのバンドリングに関するセクションを読んでも、準拠ツールに何が期待されているのか完全に明確ではありませんでした。幸いにも、ベン・ハットンが実例を挙げて説明してくれます。 - マイク・ラルフソン、OAI TSC

バンドリングの重要性の高まり

OpenAPI は以前から JSON スキーマに注目しており、OpenAPI 3.1 のリリースは、両プロジェクトの将来に大きな影響を与えます。私は本当に興奮しています。

OpenAPI を使用するプラットフォームやライブラリの開発者は、これまでにこのような大きな変化を経験したことがなく、すべての新しい優れた機能を正しく実装するには、いくつかのリリース以上が必要になるのではないかと思います。

JSON スキーマドラフト-04 からドラフト 2020-12 への変更は膨大であり、興味深いブログ投稿よりも多くの投稿の対象となっていますが、ドラフト 2020-12 の主要な「機能」の 1 つは、定義されたバンドリングプロセスです。(ドラフト-04 は、バージョン 3.1.0 より前の OAS が使用していた JSON スキーマのバージョン、あるいはそのサブセット/スーパーセットです。)

実際、バンドリングは、これまで以上に正しく行うことが重要になるでしょう。OAS 3.1 で完全な JSON スキーマのサポートが導入されたことにより、既存の JSON スキーマドキュメントを持つ開発者が、新しい OpenAPI 定義や更新された OpenAPI 定義で参照によってそれらを使用する可能性が大幅に高まります。究極の真実の源は重要であり、それは多くの場合 JSON スキーマです。

多くのツールは、外部リソースの参照をサポートしていません。バンドリングは、複数のファイルに分散されたスキーマリソースを単一のファイルにパッケージ化して、OpenAPI ドキュメントなど、他の場所で使用するのに便利な方法です。

既存のソリューション?新しいソリューション!

バンドリングソリューションを提供するライブラリはいくつかありますが、すべてに注意点があり、これまで JSON スキーマに対応しているものを見たことがありません。これらのライブラリの中で最も人気のあるものは json-schema-ref-parser と呼ばれていますが、報告書 によると、JSON スキーマに対応することを意図したものではなく、JSON リファレンス仕様(現在は JSON スキーマ仕様にバンドルされている)のみを対象としているとのことです。

標準的な実装(マイク、そうでしょう?!)と、選択した言語で独自のバンドリングを構築するための十分な情報を提供することを目指しています。(ただし、実装を開発する際には、常に完全な仕様書を読むのが最善です。)

バンドリングの基本

まず、JSON スキーマドラフト 2020-12 の重要な定義を見てみましょう。$id キーワードは、「スキーマリソース」を識別するために使用されます。以下の例では、$id はリソースの https://jsonschema.dev/schemas/mixins/integer です。

スキーマ
{ "$id": "https://jsonschema.dev/schemas/mixins/integer", "$schema": "https://json-schema.dokyumento.jp/draft/2020-12/schema", "description": "Must be an integer", "type": "integer"}

「複合スキーマドキュメント」とは、複数の埋め込み JSON スキーマリソースを持つ JSON ドキュメントです。以下は、後で少し詳しく説明する簡略化された例です。

スキーマ
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer-bundle", "$schema": "https://json-schema.dokyumento.jp/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.dokyumento.jp/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.dokyumento.jp/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 スキーマ仕様による「バンドリング」の慎重に作成された定義を見てみましょう。

「複合スキーマドキュメントを作成するためのバンドリングプロセスは、外部スキーマリソースへの参照(「$ref」など)を取得し、参照されているスキーマリソースを参照ドキュメント内に埋め込むこととして定義されています。バンドリングは、基本ドキュメントと参照/埋め込まれたドキュメントのすべての URI(参照に使用される)を変更する必要がないように行う必要があります。」

これらの定義を踏まえ、JSON スキーマ リソースの定義されたバンドル処理を見ていきましょう!この記事では理想的な状況のみを扱います。目標は、外部スキーマ リソースを一切使用しないことです。

注記:この記事では、スキーマから`$ref`の使用をすべて削除する「完全な逆参照」は扱いません。これは推奨されず、自己参照がある場合など、常に可能とは限りません。

シンプルな外部リソースのバインド

最初の例では、バンドル処理に最適な状況を示します。各スキーマには`$id`と`$schema`が定義されており、バンドル処理が簡単になります。他の様々な状況やエッジケースについては、さらに後の例で説明しますが、各リソースが独自のIDと方言を定義することが常に望ましいです。プライマリ スキーマ リソースは、インプレース適用子`$ref`を使用して、値が相対URIである他の2つのスキーマ リソースを参照します。この相対URIはベースURIに対して解決されます。この例では、ベースURIはプライマリ スキーマ リソースの`$id`の値にあります。「整数」と「非負」スキーマを組み合わせることで、「非負整数」スキーマを作成します。

スキーマ
{ "$id": "https://jsonschema.dev/schemas/mixins/integer", "$schema": "https://json-schema.dokyumento.jp/draft/2020-12/schema", "description": "Must be an integer", "type": "integer"}
スキーマ
スキーマ
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer", "$schema": "https://json-schema.dokyumento.jp/draft/2020-12/schema", "description": "Must be a non-negative integer", "$comment": "A JSON Schema that uses multiple external references", "$defs": { "nonNegativeInteger": { "allOf": [ { "$ref": "/schemas/mixins/integer" }, { "$ref": "/schemas/mixins/non-negative" } ] } }, "$ref": "#/$defs/nonNegativeInteger"}

実装において「非負整数」スキーマを主スキーマとして使用する必要がある場合、他のスキーマも実装で使用可能である必要があります。$idで定義されているように、実装がこれらのスキーマをどのようにロードするかは、それらのスキーマが完全に修飾されたURIをIDとして持つため、この時点では問題になりません。スキーマをロードする実装は、$idで定義されたスキーマURIをスキーマリソースに関連付ける内部ローカルインデックスを構築する必要があります。

$idの値を提供するスキーマは、スキーマ・リソースと見なされます。

主スキーマ内の参照の1つを解決(逆参照)しましょう。"$ref": "/schemas/mixins/integer" は、まずベースURIを決定し、そのベースURIに対して相対URIを解決するというルールに従って、https://jsonschema.dev/schemas/mixins/integer の完全修飾URIに解決されます。その後、実装はスキーマ識別子とスキーマリソースの内部インデックスをチェックし、一致するものを見つけ、適切に事前にロードされたスキーマリソースを使用する必要があります。

バンドルプロセスが完了しました。以前は外部参照されていたスキーマは、そのまま主スキーマの$defsにコピーされます。$defsオブジェクトのキーは識別URIですが、それらの値は参照されないため、何でもかまいません(必要であればUUIDを使用できます)。最終的なバンドル済みスキーマ、つまり「複合スキーマドキュメント」を見ると、複数のスキーマリソースが単一のスキーマドキュメントに埋め込まれていることがわかります。

スキーマ
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer-bundle", "$schema": "https://json-schema.dokyumento.jp/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.dokyumento.jp/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.dokyumento.jp/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の確立は、JSON Schemaが再定義しないRFC3986に記載されている手順に従います。

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仕様ドキュメントから始めます。簡潔にするため、単一のコンポーネントを含むコンポーネントセクションのみを表示していますが、ドキュメントの他の部分でコンポーネントスキーマ「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.dokyumento.jp/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.dokyumento.jp/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.dokyumento.jp/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.dokyumento.jp/blog/posts/bundling-json-schema-compound-documentsにあります。