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 Schemas。

许多工具不支持引用外部资源。捆绑是一种方便的方法,可以将分布在多个文件中的模式资源打包到一个文件中,以便在其他地方使用,例如 OpenAPI 文档。

现有解决方案?新的解决方案!

有几个库提供捆绑解决方案,但是它们都有局限性,而且到目前为止,我还没有看到任何完全支持 JSON Schema 的库。其中最流行的库称为 json-schema-ref-parser,但它 报告 它并非旨在支持 JSON Schema,而只是为了涵盖 JSON Reference 规范(该规范现在已被重新捆绑到 JSON Schema 规范中)。

我们希望为您提供规范实现(没错,迈克?!)以及足够的入门信息,以便您在您选择的语言中构建自己的实现。(尽管,在开发实现时,最好阅读完整的规范。)

捆绑基础

首先,让我们回顾 JSON Schema 草案 2020-12 中的一些关键定义。 $id 关键字用于标识“模式资源”。在下面的示例中,$idhttps://jsonschema.dev/schemas/mixins/integer,用于资源。

模式
{ "$id": "https://jsonschema.dev/schemas/mixins/integer", "$schema": "https://json-schema.fullstack.org.cn/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.fullstack.org.cn/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.fullstack.org.cn/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.fullstack.org.cn/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”)嵌入到引用文档中。捆绑 SHOULD 以这样的方式完成,即基础文档和任何引用/嵌入文档中的所有 URI(用于引用)都不需要更改。”

牢记这些定义,现在我们可以看一下 JSON 模式资源的定义的捆绑过程!在本文中,我们只讨论理想情况。这里的目标是不存在外部模式资源。

请注意,本文不涵盖“完全解除引用”,即从模式中删除所有 $ref 的使用。不建议这样做,而且并非总是可行,例如当存在自引用时。

捆绑简单的外部资源

在我们的第一个示例中,我们有一个理想的捆绑情况。每个模式都有一个 $id$schema 定义,使得捆绑过程变得简单。我们将介绍各种其他情况和边缘情况在进一步的示例中,但是让每个资源定义自己的标识和方言始终是优先的。我们的主要模式资源使用就地应用器 $ref 来引用其他两个模式资源,其值是一个相对 URI。相对 URI 相对于基准 URI 解析,在本例中是在主要模式资源的 $id 值中找到。通过组合“整数”和“非负数”模式,我们创建了“非负整数”模式。

模式
{ "$id": "https://jsonschema.dev/schemas/mixins/integer", "$schema": "https://json-schema.fullstack.org.cn/draft/2020-12/schema", "description": "Must be an integer", "type": "integer"}
模式
{ "$id": "https://jsonschema.dev/schemas/mixins/non-negative", "$schema": "https://json-schema.fullstack.org.cn/draft/2020-12/schema", "description": "不允许为负数", "minimum": 0}
模式
{ "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer", "$schema": "https://json-schema.fullstack.org.cn/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"}

如果在实现中使用 "non-negative-integer" 模式作为主模式,则其他模式也需要对实现可用。此时,实现加载模式的确切方式无关紧要,因为它们在 $id 中定义了其身份的完整限定 URI。任何加载模式的实现都应构建一个 $id 中定义的模式 URI 到模式资源的内部本地索引。

请记住,任何为 $id 提供值的模式都被认为是模式资源。

让我们解析(解除引用)主模式中的一个引用。 "$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.fullstack.org.cn/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.fullstack.org.cn/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.fullstack.org.cn/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 Schema 文档间接相关。例如,建立基本 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 规范文档开始。为了简洁起见,我们只显示包含单个组件的组件部分,但假设文档的其他部分使用组件模式“non-negative-integer”。

“non-negative-integer” 对 JSON Schema 资源只有一个引用。引用 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.fullstack.org.cn/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.fullstack.org.cn/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.fullstack.org.cn/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 已经为我们所有人澄清了这个过程,我们可以使用这个例子来完全满足 JSON Schema 在编写将多个资源捆绑到复合 OpenAPI 文档的工具时的捆绑预期。谢谢,Ben! - Mike

商业照片由 vanitjan 创建 - www.freepik.com

本文最初发布在 JSON Schema 博客上,其规范位置为 https://json-schema.fullstack.org.cn/blog/posts/bundling-json-schema-compound-documents