验证 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 中的模式被假定为使用 OpenAPI 3.1 的自定义 JSON Schema 方言。该方言包括对所有 draft 2020-12 功能的完全支持,以及一些额外的关键字和format
值。
使用默认方言进行验证
有两种模式可用于验证 OpenAPI 3.1 文档。https://spec.openapis.org.cn/oas/3.1/schema
包括验证文档的所有约束,除了模式之外。您不应单独针对此模式验证 OpenAPI 文档。将此模式视为一个抽象模式,它旨在扩展以包括对您正在使用的 JSON Schema 方言的模式验证支持。
这就是为什么还存在https://spec.openapis.org.cn/oas/3.1/schema-base
,它将抽象模式扩展为对 OpenAPI 3.1 Schema 方言的验证支持。如果您使用的是开箱即用的 OpenAPI 3.1,那么这就是您要针对其验证文档的模式。如果您想使用不同的方言,请继续阅读以了解如何扩展主模式,为您的选择的方言获得验证支持。
这得益于在 JSON Schema 2020-12 中添加的动态引用。本文档将不介绍动态引用的工作原理,但我们将涵盖足够的知识,以便您为 OpenAPI 3.1 文档中选择的任何方言创建自己的具体模式。
示例
这些示例使用@hyperjump/json-schema来验证 OpenAPI 文档。请注意,动态引用是 JSON Schema 的一个相对较新的功能,许多验证器尚未支持它们,或者支持有限,或者存在错误。
无模式验证
1import { validate } from "@hyperjump/json-schema/openapi-3-1";
2
3const validateOpenApi = await validate("https://spec.openapis.org.cn/oas/3.1/schema");
4
5const example = YAML.parse(await readFile("./example.openapi.json"));
6const result = validateOpenApi(example);
7console.log(result);
使用 OpenAPI Schema 方言模式验证
1import { validate } from "@hyperjump/json-schema/openapi-3-1";
2
3(async function () {
4 const validateOpenApi = await validate("https://spec.openapis.org.cn/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 对象的位置。$dynamicAnchor
声明此子模式是可以被其他模式有效覆盖的内容。如果未覆盖,则默认行为是验证该值为对象或布尔值。不会对模式进行其他验证。
1$defs:
2 schema:
3 $dynamicAnchor: meta
4 type:
5 - object
6 - boolean
当此模式中的某些内容想要引用 Schema 对象时,它不会像往常一样引用#/$defs/schema
,而是使用动态引用到之前选择中设置的“meta”动态锚点。现在,它不再总是解析为#/$defs/schema
,而是其他模式可能会覆盖它解析到的位置。
1$defs:
2 components:
3 type: object
4 properties:
5 schemas:
6 type: object
7 additionalProperties:
8 $dynamicRef: '#meta'
根据默认方言验证 Schema 对象
有了这些模糊的构建块,让我们推导出一个模式,该模式“扩展”抽象模式以创建使用默认方言元模式验证 Schema 对象的模式。
第一步是包含抽象模式。
1$schema: 'https://json-schema.fullstack.org.cn/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org.cn/oas/3.1/schema/latest'
然后,我们需要添加一个$dynamicAnchor
,它与抽象模式中的那个匹配,以覆盖“meta”的动态引用解析到的位置。从那里,我们可以引用默认方言的元模式。
1$schema: 'https://json-schema.fullstack.org.cn/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org.cn/oas/3.1/schema/latest'
4
5$defs:
6 schema:
7 $dynamicAnchor: meta
8 $ref: 'https://spec.openapis.org.cn/oas/3.1/dialect/base'
这足以获得我们需要的 Schema 对象验证,但我们还需要解决几个松散的细节。jsonSchemaDialect
字段可在 OpenAPI 3.1 文档中用于更改使用的方言。由于此模式仅支持默认方言,因此我们要限制人们将其更改为其他内容。如果他们需要更改它,则需要使用不同的模式进行验证。我们也不希望人们使用$schema
关键字来更改单个模式的方言。
1$schema: 'https://json-schema.fullstack.org.cn/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org.cn/oas/3.1/schema'
4properties:
5 jsonSchemaDialect:
6 $ref: '#/$defs/dialect'
7
8$defs:
9 dialect:
10 const: 'https://spec.openapis.org.cn/oas/3.1/dialect/base'
11 schema:
12 $dynamicAnchor: meta
13 $ref: 'https://spec.openapis.org.cn/oas/3.1/dialect/base'
14 properties:
15 $schema:
16 $ref: '#/$defs/dialect'
有了这些,我们就有了您在官方https://spec.openapis.org.cn/oas/3.1/schema-base
模式中找到的完全内容。
支持多种方言
随着 JSON Schema 2020-12 的采用,对$id
和$schema
关键字的支持也随之而来,它们共同允许我们覆盖模式的默认 JSON Schema 方言。让我们假设我们有一个 OpenAPI 3.1 文档,其中默认使用 JSON Schema 2020-12,但我们也有一些我们想要使用的遗留 JSON Schema draft-07 模式。
1jsonSchemaDialect: 'https://json-schema.fullstack.org.cn/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.fullstack.org.cn/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,默认情况下,模式将不会理解在 OpenAPI 3.1 词汇表中添加的关键字,例如discriminator
。只有标准 JSON Schema 2020-12 关键字会被识别。
/components/schemas/foo
模式被理解为被解释为 JSON Schema 2020-12,因为我们将其设置为默认值。
/components/schemas/bar
模式将该模式的方言更改为 JSON Schema draft-07。有一些因素共同使这成为可能。$schema
关键字设置模式的方言,但$schema
仅允许出现在其出现的文档的根目录中。这就是为什么我们还需要包含$id
关键字。该$id
关键字实际上使该模式成为一个独立的文档,具有自己的标识符,以及该位置作为根目录。它是嵌入在 OpenAPI 3.1 文档中的一个独立文档。您可以将其视为 HTML 中的 iframe。
这样做的一个后果是,/components/schemas/bar
不能使用本地引用,例如#/components/schemas/foo
来引用/components/schemas
中的另一个模式,因为它现在技术上位于另一个文档中。有两种方法可以解决这个问题。一种选择是使用对 OpenAPI 3.1 文档的外部引用,例如myapi.openapi.yml#/components/schemas/foo
。另一种选择是让/components/schemas/foo
也拥有一个$id
,并引用该$id
,./schemas/foo
。
验证
现在我们已经了解了其工作原理,让我们推导出一个模式来验证一个 OpenAPI 3.1 文档,其中 JSON Schema 2020-12 作为默认方言,JSON Schema draft-07 作为允许的备选方案。
1$schema: 'https://json-schema.fullstack.org.cn/draft/2020-12/schema'
2
3$ref: 'https://spec.openapis.org.cn/oas/3.1/schema'
4properties:
5 jsonSchemaDialect:
6 const: 'https://json-schema.fullstack.org.cn/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.fullstack.org.cn/draft/2020-12/schema'
17 - 'https://json-schema.fullstack.org.cn/draft-07/schema#'
18 allOf:
19 - if:
20 properties:
21 $schema:
22 const: 'https://json-schema.fullstack.org.cn/draft/2020-12/schema'
23 then:
24 $ref: 'https://json-schema.fullstack.org.cn/draft/2020-12/schema'
25 - if:
26 type: object
27 properties:
28 $schema:
29 const: 'https://json-schema.fullstack.org.cn/draft-07/schema#'
30 required:
31 - $id
32 - $schema
33 then:
34 $ref: 'https://json-schema.fullstack.org.cn/draft-07/schema'
第一个变化是jsonSchemaDialect
字段现在是必需的,因为我们不再使用默认值。
接下来,我们必须更新模式定义,以仅允许我们想要允许的方言的$schema
值。
第一个if
/then
将在没有使用$schema
关键字的情况下,或$schema
设置为JSON Schema 2020-12时,将模式验证为JSON Schema 2020-12模式。当然,在这种情况下,使用$schema
是不必要的,但它是允许的。
第二个if
/then
将在存在$id
和$schema
指示草案-07的情况下,将模式验证为JSON Schema 草案-07模式。
您可以将此模式扩展到您要支持的任何数量的方言。
图片来自Gonzalo Facello 在Unsplash