互联网工程任务组 | H. Andrews,编辑 |
互联网草案 | |
预期状态:信息 | A. Wright,编辑 |
过期时间:2020 年 3 月 20 日 | 2019 年 9 月 17 日 |
JSON 超级模式:用于 JSON 超媒体注释的词汇
draft-handrews-json-schema-hyperschema-02
JSON 模式是一种基于 JSON 的格式,用于使用各种词汇描述 JSON 数据。本文档指定了一种用于使用超链接注释 JSON 文档的词汇。这些超链接包括描述如何通过 HTTP 等超媒体环境操作和交互远程资源的属性,以及根据实例值确定链接是否可用。本文档中描述的超链接序列化格式也独立于 JSON 模式可用。
此草案的问题列表可以在 <https://github.com/json-schema-org/json-schema-spec/issues> 找到。
有关更多信息,请参见 <https://json-schema.fullstack.org.cn/>。
要提供反馈,请使用此问题跟踪器、主页上列出的通信方法或通过电子邮件联系文档编辑。
此互联网草案完全符合 BCP 78 和 BCP 79 的规定提交。
互联网草案是互联网工程任务组 (IETF) 的工作文档。请注意,其他组织也可能将工作文档作为互联网草案发布。当前互联网草案列表位于 https://datatracker.ietf.org/drafts/current/。
互联网草案是有效期最长为六个月的草案文档,可能随时被其他文档更新、替换或废弃。不适宜将互联网草案用作参考材料或引用,除非是作为“正在进行的工作”。
此互联网草案将于 2020 年 3 月 20 日过期。
版权所有 (c) 2019 IETF 信托基金和本文档作者。保留所有权利。
本文档受 BCP 78 和 IETF 信托基金的《与 IETF 文档相关的法律条款》(https://trustee.ietf.org/license-info)约束,这些条款在本文档发布之日生效。请仔细阅读这些文档,因为它们描述了您对本文档的权利和限制。从本文档中提取的代码组件必须包含简化的 BSD 许可证文本,如信托基金法律条款第 4.e 节所述,并且按简化的 BSD 许可证所述提供,不提供任何担保。
JSON 超级模式是一种 JSON 模式词汇,用于使用超链接和说明通过 HTTP 等超媒体环境处理和操作远程 JSON 资源来注释 JSON 文档。
术语 JSON 超级模式用于指代使用这些关键字的 JSON 模式。术语“超级模式”本身是指本规范范围内的一个 JSON 超级模式。
为指定链接引入的主要机制是链接描述对象 (LDO),它是 RFC 8288,第 2 节 中定义的抽象链接模型的序列化。
本规范将使用 JSON 模式核心 和 JSON 模式验证 规范中定义的概念、语法和术语。建议读者获取这些规范的副本。
本文档中的关键术语“必须”、“不得”、“必需”、“应”、“不应”、“建议”、“不建议”、“可”、“可选”应按 RFC 2119 中所述进行解释。
JSON 超级模式通过描述如何从实例数据构建超链接,使构建来自 JSON 文档的超媒体系统成为可能。
JSON 实例文档和该实例的有效 application/schema+json 超级模式的组合充当单个超媒体表示。通过允许这种分离,基于超级模式的系统可以优雅地支持预期为纯 JSON 的应用程序,同时为支持超级模式的应用程序和用户代理提供完整的超媒体功能。
用户代理可以通过查找 application/schema+json 媒体类型和指示超级模式词汇存在的“$schema”值来检测超级模式的存在。然后,用户代理可以使用 JSON 超级模式的实现将模式和实例文档的组合作为一个资源的单个逻辑表示进行处理,就像处理任何单个文档超媒体表示格式一样。
超级模式允许表示在网络上传输时占用更少的字节,并将链接构建的负担从服务器分发到每个客户端。用户代理无需构建链接,除非客户端应用程序请求该链接。JSON 超级模式还可以在服务器端用于在运行时生成其他链接序列化或表示格式,或预先遵循链接以促进服务器推送使用。
以下是一个示例超级模式,它添加了一个带有 IANA 注册的链接关系类型“self”的单个链接,该链接是从具有一个名为“id”的已知对象字段的实例构建的
{ "type": "object", "properties": { "id": { "type": "number", "readOnly": true } }, "links": [ { "rel": "self", "href": "thing/{id}" } ] }
如果实例为 {"id": 1234},并且根据 RFC 3986 第 5.1 节 的基本 URI 为“https://example.com/api/”,则“https://example.com/api/thing/1234”是生成的链接的目标 URI。
术语“模式”、“实例”和“元模式”应按 JSON 模式核心规范 中定义的进行解释。
术语“适用”和“附加”应按 JSON 模式核心规范第 3.1 节 中定义的进行解释。
术语“链接”、“链接上下文”(或“上下文”)、“链接目标”(或“目标”)和“目标属性”应按 RFC 8288 第 2 节 中定义的进行解释。
术语“用户代理”应按 RFC 7230 第 2.1 节 中定义的进行解释,并进行概括以应用于超媒体系统中可能使用的任何协议,而不局限于 HTTP 客户端。
本规范定义了以下术语
JSON 超级模式实现能够接收超级模式、实例,并在某些情况下接收客户端输入,并生成一组完全解析的有效链接。如 RFC 8288,第 2 节 所定义,链接包含上下文、类型化关系、目标,以及可选的目标属性。
关系类型和目标属性直接取自每个链接的链接描述对象。上下文和目标标识符由 URI 模板、实例数据以及(在目标标识符的情况下)客户端输入的某种组合构建。
目标始终由 URI 完全标识。由于缺乏对 application/json 和许多其他可用于 JSON 超级模式的媒体类型的 URI 片段标识符语法的支持,上下文可能仅由 URI 部分标识。在这种情况下,其余标识将作为 JSON 指针提供。
JSON 超级模式文档中对一些 IANA 注册的链接关系类型赋予了特定语义。 “self” 链接用于与实例文档所代表的资源进行交互,而 “collection” 和 “item” 链接标识可以为其假设特定于集合的语义的资源。
JSON 超级模式元模式的当前 URI 为 <https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema#>.
此词汇表的当前 URI(称为超级模式词汇表)为:<https://json-schema.fullstack.org.cn/draft/2019-09/vocab/hyper-schema>.
对应元模式的当前 URI(与上面的便捷元模式不同,它仅描述超级模式关键字(“base” 和 “link”))为:<https://json-schema.fullstack.org.cn/draft/2019-09/meta/hyper-schema>.
链接描述格式 可与 JSON Schema 一起使用,并且可以通过将规范性链接描述模式引用为使用链接的数据结构的模式来声明此格式的使用。规范性链接描述模式的 URI 为:<https://json-schema.fullstack.org.cn/draft/2019-09/links#>.
JSON 超级模式实现可以自由地以任何格式提供输出。但是,已定义一种特定格式用于一致性测试套件,该套件还用于说明 “实现要求” 中的要点,以及显示由 示例 生成的输出。建议实现能够以这种格式生成输出,以方便测试。描述推荐输出格式的 JSON Schema 的 URI 为 <https://json-schema.fullstack.org.cn/draft/2019-09/output/hyper-schema#>.
更新的词汇表和元模式 URI 可能会在规范草案之间发布,以更正错误。实现应考虑此规范草案之后和下一个草案之前的日期,以指示与此处列出的相同的语法和语义。
来自所有适用于实例中某个位置的模式的超级模式关键字(如 JSON Schema 核心第 3.1 节 所定义)可用于该实例。
当多个子模式适用于给定的子实例时,所有 “link” 数组必须合并为一个集合(顺序任意)。结果集合中的每个对象必须保留其自己的 “base” 值列表(按解析顺序),来自同一模式和任何父模式。
与所有 JSON Schema 关键字一样,本节中描述的所有关键字都是可选的。最小的有效 JSON 超级模式是空对象。
如果存在,此关键字必须首先 解析为 URI 模板,然后必须作为 URI 引用相对于实例的当前 URI 基准解析。结果必须在处理包含 “base” 的子模式及其所有子模式时,设置为实例的新 URI 基准。
在解析以用于 “anchor” 时,解析 “base” 模板的过程可能与解析以用于 “href” 时不同,这将在 URI 模板部分详细解释。
模式的 “links” 属性用于将链接描述对象与实例关联。此属性的值必须是数组,数组中的项必须是链接描述对象,如下所定义。
链接描述对象 (LDO) 是 RFC 8288,第 2 节 中定义的抽象链接模型的序列化。如该文档所述,链接包含上下文、关系类型、目标,以及可选的目标属性。JSON 超级模式的 LDO 提供了所有这些,以及使用 JSON Schema 来描述以各种方式与链接一起使用的输入的附加功能。
由于使用 URI 模板来标识链接上下文和目标,以及在标识目标时可选地进一步使用客户端输入,因此 LDO 是一个链接模板,它在与 JSON 实例文档一起使用时可能会解析为多个链接。
LDO 的特定用途通常涉及跨协议的请求和响应,称为操作。对于许多协议,在任何给定链接上都可以执行多个操作。协议由目标的 URI 方案指示。请注意,并非所有 URI 方案都指示可用于通信的协议,即使具有指示此类协议的 URI 方案的资源也不一定可以通过该协议访问。
链接描述对象必须是对象,并且必须存在 “href” 和 “rel” 属性。每个关键字在本节中简要介绍,并在本文档的后面提供其他用法说明和综合示例。
在 JSON 超级模式中,链接的上下文资源默认情况下是与其关联的子实例(如 JSON Schema 核心规范第 3.1 节 所定义)。这通常不是整个实例文档。可以使用本节中的关键字更改此默认上下文。
根据实例的媒体类型,可能无法或无法为完全默认上下文资源分配 URI。特别是,application/json 没有定义 URI 片段解析语法,因此普通 JSON 文档中的属性或数组元素无法由 URI 完全标识。当无法生成完整的 URI 时,上下文的应使用实例文档的 URI 以及单独的普通字符串 JSON 指针来传达。
实现必须能够构建链接上下文的 URI,以及(如果需要完全标识)按 RFC 6901,第 5 节 的字符串表示形式,以 JSON 指针的形式代替 URI 片段。在 URI 模板 部分给出了基于 URI 模板构建 URI 的过程。
此属性设置链接的上下文 URI。该属性的值是一个 URI 模板,并且生成的 URI 引用 必须相对于实例的基准 URI 解析。
URI 是使用为 “href” 属性描述的相同过程从提供的 URI 模板计算得出的,但 “hrefSchema” 必须不适用。与目标 URI 不同,上下文 URI 不接受用户输入。
此属性更改实例中被视为链接的上下文资源的点。该属性的值必须是 JSON 字符串表示形式的有效 JSON 指针,或相对于默认上下文评估的有效 相对 JSON 指针。
虽然具有已知 URI 的备用上下文最好使用 “anchor” 关键字设置,但由于缺乏对 application/json 的片段标识符语法的支持,因此通常无法使用 URI 更改 JSON 实例中的上下文。
即使在将 JSON 指针定义为片段标识符语法的 “+json” 媒体类型中,如果默认上下文嵌套在数组中,也无法获取默认上下文在该数组中的位置的索引,以构建指向同一嵌套 JSON 对象中的另一个属性的指针。这将在示例中演示。
处理此关键字的结果应为 URI 片段(如果实例的媒体类型允许此类片段)。否则,它必须是字符串编码的 JSON 指针。
链接的关系类型标识其语义。它是传达应用程序如何与资源交互的主要方式。
关系定义通常不依赖于媒体类型,鼓励用户使用最合适的现有已接受的关系定义。
此属性的值必须是字符串或字符串数组。如果该值为数组,则该数组必须至少包含一个字符串。
每个字符串必须是 RFC 8288,第 2.1 节中定义的单个链接关系类型,包括限制,即不应基于其他链接关系类型的存在或不存在来推断其他语义。
此属性是必需的。
“self” 链接(如 RFC 4287 第 4.2.7.2 节 中最初定义的)表示目标 URI 标识与链接上下文等效的资源。在 JSON 超级模式中, “self” 链接必须从实例中解析,因此 “hrefSchema” 必须不存在。
超级模式作者应使用 “templateRequired” 来确保 “self” 链接具有使用所需的所有实例数据。
超级模式实现必须认识到,具有 “self” 关系类型的链接(其上下文为整个当前实例文档)描述了用户代理如何与该实例文档所代表的资源进行交互。
RFC 6573 定义并注册了 “item” 和 “collection” 链接关系类型。JSON 超级模式对这些类型指示的集合资源施加了额外的语义。
实现必须将 “collection” 链接的目标和 “item” 链接的上下文识别为集合。
超媒体中一个众所周知的模式是使用集合资源来创建集合成员,并为其分配服务器分配的 URI。如果由 URI 方案指示的协议定义了适合于创建具有服务器分配的 URI 的资源的特定方法,则由这些链接关系类型标识的集合资源 MUST NOT 定义与创建集合成员的语义冲突的方法语义。集合资源 MAY 通过这种协议方法实现项目创建,而用户代理 MAY 假设任何此类操作(如果存在)具有项目创建语义。
由于这种方法对应于 JSON 超级模式的数据提交概念,因此链接的 "submissionSchema" 字段 SHOULD 与集合项目的表示的模式兼容,如 "item" 链接的目标资源或 "collection" 链接的上下文资源的 "self" 链接所示。
当没有注册关系(除了 "related")适用时,鼓励用户根据 RFC 8288 的第 2.1.2 节 的描述,自行创建自己的扩展关系类型。选择链接关系类型 URI 的最简单方法是使用已用于标识系统主要资源的 URI 方案,或者使用人类可读的、不可解析的 URI 方案,例如 RFC 4151 定义的 "tag"。
扩展关系类型 URI 不需要可解析,即使使用允许它的方案。
目标 URI 模板用于识别链接的目标,可能利用实例数据。此外,使用 "hrefSchema",此模板可以识别一组基于客户端输入的可能的目标资源。使用或不使用客户端输入解析 URI 模板的完整过程在 URI 模板 部分中介绍。
"href" 链接描述属性的值是用于确定相关资源目标 URI 的模板。实例属性的值 MUST 解析为相对于实例的基 URI 的 URI 引用。
此属性是必需的。
本节中的关键字用于解析超模式中涉及的所有 URI 模板:"base"、"anchor" 和 "href"。有关完整模板解析算法,请参见 URI 模板 部分。
请注意,在解析 "base" 模板时,解析开始的连接点是 "href" 或 "anchor" 关键字的连接点,这些关键字需要解析 "base" 模板,而不是 "base" 关键字本身的连接点。
"templatePointers" 链接描述属性的值 MUST 是一个对象。对象中的每个属性值 MUST 是一个有效的 JSON 指针,或者是一个有效的 相对 JSON 指针,它相对于解析模板的链接的连接点进行评估。
对于对象中与正在解析的模板中的变量名称匹配的每个属性名称,该属性的值会调整该变量的变量解析的起始位置。不匹配正在解析的模板中的模板变量名称的属性 MUST 被忽略。
此关键字的值 MUST 是一个数组,并且元素 MUST 是唯一的。每个元素 SHOULD 匹配链接的 URI 模板中的变量,不进行百分比编码。在完成整个 URI 模板解析过程后,如果此数组中存在的任何变量没有值,则 MUST NOT 使用该链接。
本节中的所有属性仅供参考。虽然 "title" 和 "description" 等关键字主要用于向用户展示链接,但那些预测链接交互或响应性质的关键字 MUST NOT 被视为权威。目标资源的运行时行为 MUST 在与 LDO 中的目标属性冲突时受到尊重。
此属性定义链接的标题。该值 MUST 是一个字符串。
用户代理 MAY 在向用户展示链接时使用此标题。
此属性提供除了标题中存在的额外信息。该值 MUST 是一个字符串。虽然标题最好简短,但描述可用于详细说明链接的目的和用途。
用户代理 MAY 在向用户展示链接时使用此描述。
此属性的值表示预期在获取此资源时返回的媒体类型 RFC 2046。此属性值 MAY 是一个媒体范围,使用与 RFC 7231,第 5.3.2 节 - HTTP "Accept" 标头 中定义的相同的模式。
此属性类似于其他链接序列化格式的 "type" 属性。用户代理 MAY 使用此信息来告知他们在跟随链接之前向用户呈现的界面,但 MUST NOT 使用此信息来解释结果数据。相反,用户代理 MUST 使用响应中给出的媒体类型进行运行时解释。有关 "targetMediaType" 误用的详细说明,请参见 "安全问题" 部分。
对于支持内容协商的协议,实现 MAY 选择使用 "headerSchema" 中的协议特定信息来描述可能的 target 媒体类型。如果同时存在协议特定信息和 "targetMediaType",则 "targetMediaType" 的值 MUST 与协议特定信息兼容,并且 SHOULD 指示在没有内容协商的情况下将返回的媒体类型。
当没有此类协议特定信息可用,或者实现无法识别所涉及的协议时,则该值 SHOULD 被视为 "application/json"。
此属性提供了一个预期的模式,用于描述链接目标的表示。根据协议,该模式可能或可能不描述对使用链接执行的任何特定操作的请求或响应。有关如何使用此关键字与 HTTP 的深入讨论,请参见 JSON 超级模式和 HTTP 部分。
此属性的值仅供参考。它表示预期通过与目标资源交互可以发现的信息,通常以协议特定的控制信息或元数据形式出现,例如作为对 HTTP HEAD 或 OPTIONS 请求的响应返回的标头。该协议由 "href" URI 方案确定,但请注意,资源不能保证通过此类协议访问。
此属性的值 SHOULD 是一个对象。此对象中的键 SHOULD 是控制数据字段名称的小写形式。每个值 SHOULD 是一个数组,以便统一处理多值字段。多个值 MUST 作为数组呈现,而不是作为单个字符串呈现。
控制信息不适合作为 JSON 对象表示的协议 MAY 由其他数据类型表示,例如数组。
无法理解为指示协议一部分的值 MUST 被 JSON 超级模式实现忽略。应用程序 MAY 使用此类值,但 MUST NOT 假设与其他实现的互操作性。
实现 MUST NOT 假设此对象中考虑了所有可发现的信息。客户端应用程序 MUST 正确处理与此属性的值相矛盾的运行时响应。
客户端应用程序 MUST NOT 假设实现将根据此属性的值自动执行任何操作。
有关使用此关键字与 HTTP 和类似协议的指南,请参见 "JSON 超级模式和 HTTP"。
有四种方法可以使用客户端输入与链接一起使用,每种方法都由单独的链接描述对象关键字解决。在执行操作时,用户代理 SHOULD 忽略与语义无关的模式。
"hrefSchema" 链接描述属性的值 MUST 是一个有效的 JSON 模式。此模式用于验证用户输入或其他用户代理数据,以填写 "href" 中的 URI 模板。
省略 "hrefSchema" 或将整个模式设置为 "false" 会阻止任何用户代理数据被接受。
将任何适用于特定变量的子模式设置为 JSON 字面值 "false" 会阻止任何用户代理数据被接受用于该单个变量。
对于可以从实例数据中解析的模板变量,如果实例数据相对于 "hrefSchema" 中的所有适用子模式有效,则 MUST 使用它来预先填充该变量的输入数据集。
请注意,即使数据是预先填充的实例,"hrefSchema" 中该变量的验证模式也不必与应用于实例数据位置的验证模式相同。这允许针对用户代理数据使用不同的验证规则,例如支持日期时间输入的拼写月份,但使用标准日期时间格式进行存储。
在接受输入后,可能会覆盖预先填充的实例数据,生成的 DataSet MUST 成功验证 "hrefSchema" 的值。如果它没有,则 MUST NOT 使用该链接。如果它有效,则 "URI 模板" 部分中给出的过程将使用此更新的 DataSet 继续。
[CREF2]与 "targetHints" 一样,此关键字有点未定义,以鼓励尝试和反馈,因为我们试图在灵活性和清晰度之间取得平衡。
如果存在,此属性是特定于协议的请求头或类似控制和元数据的模式。此对象的 value 必须是有效的 JSON Schema。协议由 "href" URI 方案确定,但请注意,资源不保证通过此类协议访问。此模式仅供参考;目标资源的行为不受其存在的影响。
此关键字的目的是宣传目标资源交互功能,并向用户代理和客户端应用程序指示哪些标头和标头值可能有用。用户代理和客户端应用程序可以使用此模式来验证相关标头,但不得假设缺少标头或值禁止使用。虽然模式作者可以将 "additionalProperties" 设置为 false,但不推荐这样做,并且不得阻止客户端应用程序或用户代理在发出请求时提供其他标头。
JSON 数据模型到标头的精确映射取决于协议。但是,在大多数情况下,此模式应该指定 "object" 类型,并且属性名称应该为控制数据字段名称的小写形式。与 "targetHints" 一样,value 应该被描述为数组,以允许存在多个 value,即使只期望一个 value。
有关使用此关键字与 HTTP 和类似协议的详细指南,请参阅 "JSON Hyper-Schema and HTTP" 部分。
"headerSchema" 适用于协议支持的任何请求方法或命令。在生成请求时,用户代理和客户端应用程序应该忽略与该请求无关的标头的模式。
在 JSON Hyper-Schema 中,"targetSchema" 提供对目标资源表示的非权威描述。客户端应用程序可以使用 "targetSchema" 来构建用于替换或修改表示的输入,或作为基于补丁媒体类型的补丁文档的基准表示。
或者,如果 "targetSchema" 不存在,或者客户端应用程序更喜欢只使用权威信息,它可以与目标资源交互以确认或发现其表示结构。
"targetSchema" 不打算描述链接操作响应,除非响应语义表明它是目标资源的表示。在所有情况下,响应本身指示的模式都是权威的。有关详细示例,请参阅 "JSON Hyper-Schema and HTTP"。
该 "submissionSchema" 和 "submissionMediaType" 关键字描述目标资源实现的处理功能的域。否则,如上所述,提交模式和媒体类型将被忽略,因为它们与不相关的操作无关。
如果存在,此属性指示客户端应用程序和用户代理应为 "submissionSchema" 描述的请求有效负载使用的媒体类型格式。
省略此关键字的行为与 "application/json" 的值相同。
请注意,"submissionMediaType" 和 "submissionSchema" 不限于 HTTP URI。 [CREF3]此语句可能会移动到示例结束的地方。
此属性包含一个模式,该模式定义了要根据 "submissionMediaType" 属性编码并发送到目标资源以进行处理的文档的结构。这可以被视为描述目标资源实现的处理功能的域。
这是一个与 "targetSchema" 属性不同的概念,该属性描述目标信息资源(包括在 PUT 请求中替换资源内容),而 "submissionSchema" 描述由资源评估的用户提交的请求数据。"submissionSchema" 旨在与有效负载不一定以目标表示形式定义的请求一起使用。
省略 "submissionSchema" 的行为与 "true" 的值相同。
在高层次上,符合规范的实现将满足以下要求。这些要求中的每一个都在各个关键字部分和关键字组概述中进行了更详细的介绍。
请注意,有关实现如何识别 "self"、"collection" 和 "item" 链接的要求在 链接关系类型 部分中得到了详细的介绍,这里不再重复。
虽然它不是实现的强制格式,但测试套件中使用的输出格式总结了在使用每个链接之前需要计算的内容
其他不参与生成上述信息的 LDO 关键字将与它们在为测试套件生成输出时出现的完全一样。除非特别相关,否则这些字段将不再在此处讨论。
在使用链接之前,必须通过将超模式应用于实例并找到所有适用的有效链接来发现这些链接。请注意,除了收集有效链接之外,任何 "base" 为了解析每个 LDO 的 URI 模板,还必须找到并通过对实现的 URI 模板解析过程最有用机制与 LDO 关联。
实现必须支持通过其附加指针或上下文指针查找链接,方法是执行查找或提供具有确定了这两个指针的所有链接的集合,以便用户代理可以自己实现查找。
在通过上下文指针执行查找时,附加到同一个数组的元素的链接必须按照它们附加到的数组元素的顺序返回。
三个超模式关键字是 URI 模板:"base"、"anchor" 和 "href"。每个都分别解析为 URI 引用,然后锚点或 href URI 引用相对于基准(本身需要解析为前面的基准,每个基准首先从 URI 模板解析为 URI 引用)解析。
所有三个关键字共享相同的算法来从实例数据中解析变量,该算法使用 "templatePointers" 和 "templateRequired" 关键字。在解析 "href" 时,它和任何解析为绝对 URI 所需的 "base" 模板,算法将被修改为根据 "hrefSchema" 关键字选择性地接受用户输入。
对于每个 URI 模板 (T),以下伪代码描述了将 T 解析为 URI 引用 (R) 的算法。为了这个算法
此算法应首先应用于 "href" 或 "anchor",然后根据需要应用于每个后续的 "base"。顺序很重要,因为无法始终判断模板是否将解析为完整 URI 或 URI 引用。
用英语,高层算法是
这是高层算法的伪代码。"T" 来自 LDO 中的 "href" 或 "anchor",或来自包含模式中的 "base"。每个步骤的伪代码如下。"initialTemplateKeyword" 指示启动该过程的两个关键字中的哪一个(因为 "base" 总是解析为完成解析其中一个关键字)。
templateData = populateDataFromInstance(T, ldo, instance) if initialTemplateKeyword == "href" and ldo.hrefSchema exists: inputData = acceptInput(ldo, instance, templateData) for varname in inputData: templateData[varname] = inputData[varname] for varname in ldo.templateRequired: if not exists templateData[varname] fatal("Missing required variable(s)") templateData = stringEncode(templateData) R = rfc6570ResolutionAlgorithm(T, templateData)
此步骤查看实例中的不同位置以获取变量值。对于每个变量
for varname in T: varname = rfc3986PercentDecode(varname) if varname in ldo.templatePointers: valuePointer = templatePointers[varname] if valuePointer is relative: valuePointer = resolveRelative(attachmentPointer, valuePointer) else valuePointer = attachmentPointer + "/" + varname value = instance.valueAt(valuePointer) if value is defined: templateData[varname] = value
此步骤比较复杂,因为要支持多种情况。某些变量将禁止输入,而某些变量将允许输入。有些变量将具有需要在输入界面中显示的初始值,而有些则没有。
"InputForm" 代表任何适合的输入机制。这可能是一个文字网页表单,也可能是一个更具程序性的结构,例如接受特定字段和数据类型的回调函数,并带有给定的初始值(如果有)。
form = new InputForm() for varname in T: useField = true useInitialData = true for schema in getApplicableSchemas(ldo.hrefSchema, "/" + varname): if schema is false: useField = false break if varname in templateData and not isValid(templateData[varname], schema)): useInitialData = false break if useField: if useInitialData: form.addInputFieldFor(varname, ldo.hrefSchema, templateData[varname]) else: form.addInputFieldFor(varname, ldo.hrefSchema) inputData = form.acceptInput() if not isValid(inputData, hrefSchema): fatal("Input invalid, link is not usable") return inputData:
本节很简单,将字面量转换为其名称作为字符串,并将数字转换为字符串,以最明显的方式进行,并在 URI 中按需进行百分比编码。
for varname in templateData: value = templateData[varname] if value is true: templateData[varname] = "true" else if value is false: templateData[varname] = "false" else if value is null: templateData[varname] = "null" else if value is a number: templateData[varname] = bestEffortOriginalJsonString(value) else: templateData[varname] = rfc3986PercentEncode(value)
在某些软件环境中,数字的原始 JSON 表示形式将不可用(无法区分 1.0 和 1),因此应使用任何合理的表示形式。模式和 API 作者应牢记这一点,如果精确的表示形式很重要,则使用其他类型(例如字符串或布尔值)。如果数字以字符串形式作为输入提供,则应使用作为输入使用的字符串。
对于给定的链接,实现 MUST 使所有目标属性关键字的值直接对用户代理可用。实现 MAY 提供额外的接口来使用此信息,如每个关键字部分所述。
对于给定的链接,实现 MUST 使每个输入模式关键字的值直接对用户代理可用。
为了鼓励 URI 模板解析过程的封装,实现 MAY 省略仅用于构建 URI 的 LDO 关键字。但是,实现 MUST 提供对链接关系类型的访问。
未识别的关键字 SHOULD 提供给用户代理,并且 MUST 忽略。
超模式实现 SHOULD 提供访问构建对目标资源的任何有效请求所需的所有信息。
LDO 可以表达执行链接上的任何操作所需的所有信息。本节解释了用户代理应检查哪些超模式字段,以从实例数据和客户端输入的任何组合构建请求。超模式实现本身并不期望构建和发送请求。
目标 URI 构建规则(包括用于接受输入的 "hrefSchema")对于所有可能的请求都相同。
不携带主体有效负载的请求不需要额外的关键字支持。
以目标表示形式作为有效负载的请求 SHOULD 使用 "targetSchema" 和 "targetMediaType" 关键字进行输入描述和有效负载验证。如果协议允许使用基于媒体类型修改后的表示形式的有效负载(例如补丁媒体类型)的操作,则 SHOULD 通过协议特定方式通过 "targetHints" 指示该媒体类型。
以不源自目标资源表示形式的有效负载的请求 SHOULD 使用 "submissionSchema" 和 "submissionMediaType" 关键字进行输入描述和有效负载验证。超媒体中使用的协议通常每个链接只支持一个此类非表示操作。
通过单个超媒体协议操作管道传输许多具有任意不同请求结构的应用程序操作的 RPC 系统超出了 JSON 超模式等超媒体格式的范围。
作为一种超媒体格式,JSON 超模式关注的是描述资源,包括详细描述其链接,以使所有有效请求都能实现。它并不关心直接描述这些请求的所有可能响应。
与任何超媒体系统一样,响应应该具有自描述性。在超模式的上下文中,这意味着每个响应 MUST 将其自己的超模式链接起来。虽然由目标资源表示组成的响应预期对 "targetSchema" 和 "targetMediaType" 有效,但这些关键字仅为建议性,如果与响应本身相矛盾,则 MUST 被忽略。
其他响应,包括错误响应、复杂重定向和处理状态表示,SHOULD 也链接到其自己的模式并使用适当的媒体类型(例如,"application/problem+json" 用于错误)。某些错误可能不会链接模式,因为它们是由不知道超模式的中介生成的,而不是由源生成的。
预计用户代理能够理解协议状态代码和响应媒体类型,足以处理常见情况,并向客户端应用程序提供足够的信息来处理特定于域的响应。
在设计时静态映射所有可能的响应及其模式超出了 JSON 超模式的范围,但可能属于构建在超模式之上的其他 JSON 模式词汇表的范围(参见 附录 A.3)。
根据链接的上下文发现链接,或使用链接的上下文来识别集合的要求,在与流式解析器一起使用时提出了独特的挑战。在没有处理整个模式和实例文档的情况下,无法权威地满足这些要求。
此类实现 MAY 选择根据迄今为止处理的数据返回非权威答案。在提供这种方法时,实现 MUST 清楚说明响应的性质,并且 MUST 提供一个选项来阻止并等待,直到所有数据都处理完毕,并且可以返回权威答案。
虽然 JSON 超模式是一种超媒体格式,因此与协议无关,但预计它最常见的用途将在 HTTP 系统中,或者使用像 CoAP 这样的协议的系统,这些协议明确地类似于 HTTP。
本节提供了有关如何使用每个常见的 HTTP 方法与链接以及集合资源如何对 HTTP POST 施加更多约束的指南。此外,还提供了有关提示 HTTP 响应头值和描述与给定资源相关的可能的 HTTP 请求头的指南。
JSON 模式核心规范的第 13 节 提供了有关在超媒体系统中将实例链接到其模式的指南。这可以通过网络可访问的模式来完成,也可以简单地识别预先打包在客户端应用程序中的模式。JSON 超模式有意不限制此机制,尽管 RECOMMENDED 使用核心规范中概述的技术,只要可能就使用这些技术。
链接描述对象不直接指示目标资源支持哪些操作(例如 HTTP 方法)。相反,操作应该主要从链接 关系类型 和 URI 方案推断出来。
这意味着,对于每个目标资源和链接关系类型对,模式作者 SHOULD 仅定义单个 LDO。虽然可以使用 "allow" 与 "targetHints" 来重复关系类型和目标对,并标记为允许的不同 HTTP 方法,但这样做 NOT RECOMMENDED,并且可能无法得到符合规范的实现的良好支持。
所有使用每个 HTTP 方法所需的信息都可以通过单个 LDO 传达,如本节所述。 "targetHints" 中的 "allow" 字段仅用于提示支持哪些操作,而不是单独定义每个操作。
但是请注意,资源始终可以在运行时拒绝操作,例如由于授权失败,或者由于控制操作可用性的其他应用程序状态。
"targetSchema" 描述链接目标端的资源,而 "targetMediaType" 定义该资源的媒体类型。对于 HTTP 链接,"headerSchema" 也可用于描述在 "Accept" 请求头中使用的有效值,这可以支持多种媒体类型或媒体范围。当两种指示目标媒体类型的方式都存在时,"targetMediaType" SHOULD 指示默认表示媒体类型,而 "headerSchema" 中 "accept" 的模式 SHOULD 包括默认媒体类型以及可以请求的任何备选媒体类型或媒体范围。
由于许多 HTTP 方法的语义是在目标资源方面定义的,因此 "targetSchema" 用于针对几种 HTTP 方法的请求和/或响应。特别是,"targetSchema" 建议客户端应用程序可以预期 HTTP GET 的响应,或者 "Content-Location" 头等于请求 URI 的任何响应,以及客户端应用程序在使用 HTTP PUT 请求创建或替换资源时应发送什么。这些关联由 RFC 7231,第 4.3.1 节 - "GET"、第 4.3.4 节 "PUT" 和第 3.1.4.2 节 "Content-Location" 定义。
根据 RFC 5789,HTTP PATCH 的请求结构由 "targetSchema" 和请求媒体类型组合确定,请求媒体类型由 "Accept-Patch" 头传达,该头可能包含在 "targetHints" 中。适合进行 PATCH 的媒体类型定义了表达对文档更改的语法,该语法可以应用于 "targetSchema" 描述的表示形式,以确定一组语法上有效的请求有效负载。通常,验证 PATCH 请求的最简单方法是将其应用并验证结果是否为正常表示形式。
JSON 超模式允许资源处理任意数据,除了或代替处理目标的表示形式。这些任意数据由 "submissionSchema" 和 "submissionMediaType" 关键字描述。在 HTTP 的情况下,POST 方法是唯一处理此类数据的 POST 方法。虽然 POST 与集合一起使用存在一定的约定,但 POST 请求的语义是由目标资源定义的,而不是 HTTP。
除了协议中立的 "submission*" 关键字之外(参见 第 9.3 节 以获取非 HTTP 示例),"Accept-Post" 头可用于指定必要的媒体类型,并且 MAY 通过 "targetHints" 字段进行宣传。 [CREF4]如果两者都使用会发生什么?此外,"submissionSchema" 必须支持,而 "targetHints" 至多为 SHOULD。但禁止在 "targetHints" 中使用 "Accept-Post" 似乎不正确。
除了 201 或 200(设置了 "Content-Location")之外,对 POST 的成功响应也没有 HTTP 定义的语义。与所有 HTTP 响应一样,响应中的任何表示都应该链接到其自己的超模式,以指示如何处理它。如 附录 A.2 中所述,将超链接与所有可能的运算响应连接起来超出了 JSON 超模式的范围。
HTTP 响应头信息的 JSON 序列化 SHOULD 遵循正在进行的工作中建立的指南 "HTTP 头字段值的 JSON 编码"。该文档示例中显示的方法 SHOULD 尽可能应用于其他类似结构的头。
所有可能的 HTTP 方法响应的标头都共享“headerSchema”。特别是,HEAD 响应中出现的标头和 OPTIONS 响应中出现的标头都可能出现。“headerSchema”不区分哪种方法响应包含哪个标头。
建议模式作者在适用时为以下类型的 HTTP 标头的值提供提示
一般来说,可能在不同时间具有不同值的标头不应包含在“targetHints”中。
例如,允许 HEAD、GET 和 POST 的 Allow 标头将显示如下
{ "targetHints": { "allow": ["HEAD", "GET", "POST"] } }
请注意,无论是否存在带有逗号分隔值的单行 Allow 标头,还是多行 Allow 标头,每行一个值,或者任何此类排列的组合,这都是以相同的方式表示的。与 HTTP 标头通常一样,逗号分隔的值和标头的多次出现将以相同的方式处理。
模式应该编写为描述遵循正在进行的工作中建立的指南的 JSON 序列化 "A JSON Encoding for HTTP Header Field Values" 尽可能将该文档示例中显示的方法应用于其他类似结构的标头。
建议模式作者在适用时描述以下类型的 HTTP 标头的可用用法
诸如缓存控制和条件请求标头之类的标头通常由中介而不是资源实现,因此通常不适合描述。虽然资源必须提供使用条件请求所需的信息,但此类标头和相关响应的运行时处理不是特定于资源的。
使用 HTTP 或明确类似于 HTTP 的协议(如 CoAP)时,这是通过将要创建的单个资源的表示 POST 到集合资源来完成的。识别集合和项目资源的过程在 第 6.2.3 节 中描述。
JSON 超级模式促进了 HTTP 内容协商,并允许主动和被动策略的混合。如上所述,超模式可以包含用于 HTTP 标头(如“Accept”、“Accept-Charset”、“Accept-Language”等)的模式,以及“headerSchema”关键字。用户代理或客户端应用程序可以使用此模式中的信息(例如,支持语言的枚举列表),而不是发出初始请求来启动被动协商过程。
通过这种方式,设置这些标头的主动内容协商技术可以从服务器关于哪些值可能的的信息中获悉,类似于在被动协商中检查备选方案列表。
对于允许将模式指定为媒体类型参数的媒体类型,“Accept”值在请求中发送或在“headerSchema”中公布,可以包括协商表示预期符合的模式的 URI。在内容协商中使用模式参数的一种可能用途是,如果资源随着时间的推移而符合几个不同的模式版本。客户端应用程序可以通过这种方式在“Accept”标头中指示它理解的版本。
本节展示了构造 URI 和 JSON 指针的关键字的使用方式。结果以测试套件中使用的格式显示。 [CREF6]需要发布该内容并链接它,但对于那些在这个阶段审查事物的人来说,它应该是相当清楚的。
大多数其他关键字要么是直观的(“title”和“description”),要么将验证应用于特定类型的输入、请求或响应,要么具有特定于协议的行为。展示 HTTP 用法的示例在 附录 中提供。
对于此示例,我们将假设一个示例 API,其文档化的入口点 URI 为 https://example.com/api,它是一个空 JSON 对象,并链接到一个模式。这里,入口点本身没有数据,仅用于提供一组初始链接
GET https://example.com/api HTTP/1.1 200 OK Content-Type: application/json Link: <https://schema.example.com/entry>; rel="describedBy" {}
链接的超模式定义了 API 的基本 URI,并提供两个链接:一个指向 API 文档的“about”链接,以及一个指示这是基本 URI 模式本身的“self”链接。
{ "$id": "https://schema.example.com/entry", "$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema", "base": "https://example.com/api/", "links": [ { "rel": "self", "href": "../api", }, { "rel": "about", "href": "docs" } ] }
这些是最简单的链接,只有关系类型和没有模板变量的“href”。它们解析如下
在“self”链接中,基本 URI 和“../api”href 中“api”的重复是由于 RFC 3986 URI 引用解析算法的怪癖。为了让相对 URI 引用在一般情况下都能正常工作,基本 URI 需要包含尾部斜杠。“about”链接及其“docs”href 显示了相对引用的常见情况,在本文档中的其他示例中也使用了它。
但是,如果 API 为其资源使用不带尾部斜杠的 URI,则无法提供仅删除尾部斜杠而不重复其上方的路径组件的相对引用。这使得入口点资源的情况(与基本 URI 仅在尾部斜杠方面有所不同)有些尴尬。
当然,资源 URI 可能包含尾部斜杠,但本示例旨在突出显示这种经常令人困惑的特殊情况。
[ { "contextUri": "https://example.com/api", "contextPointer": "", "rel": "self", "targetUri": "https://example.com/api", "attachmentPointer": "" }, { "contextUri": "https://example.com/api", "contextPointer": "", "rel": "about", "targetUri": "https://example.com/api/docs", "attachmentPointer": "" } ]
附加指针是根指针(对于实例为空对象,这唯一可能的情况)。上下文 URI 是默认值,即请求的文档。由于 application/json 不允许片段,因此上下文指针对于完全描述上下文是必需的。它的默认行为与附加指针相同。
让我们向系统中添加“事物”,从单个“事物”开始
{ "$id": "https://schema.example.com/thing", "$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema", "base": "https://example.com/api/", "type": "object", "required": ["data"], "properties": { "id": {"$ref": "#/$defs/id"}, "data": true }, "links": [ { "rel": "self", "href": "things/{id}", "templateRequired": ["id"], "targetSchema": {"$ref": "#"} } ], "$defs": { "id": { "type": "integer", "minimum": 1, "readOnly": true } } }
我们的“事物”有一个服务器分配的 id,这是构造“self”链接所必需的。它还有一个“data”字段,可以是任何类型。"$defs" 部分的原因将在下一个示例中明确。
请注意,“id”不是验证模式所必需的,但对于 self 链接是必需的。这是有道理的:只有在创建“事物”并服务器分配了 id 之后,“事物”才具有 URI。但是,您可以将此模式与仅包含 data 字段的实例一起使用,这允许您验证您即将创建的“事物”实例。
让我们向入口点模式添加一个链接,以便如果您能提供它的 id 作为输入,您可以直接跳转到特定“事物”。为了节省空间,仅显示新的 LDO。与“self”和“about”不同,没有关于假设事物的 IANA 注册关系,因此使用 "tag:" URI 方案 定义了扩展关系
{ "rel": "tag:rel.example.com,2017:thing", "href": "things/{id}", "hrefSchema": { "required": ["id"], "properties": { "id": {"$ref": "thing#/$defs/id"} } }, "targetSchema": {"$ref": "thing#"} }
此处的“href”值相同,但其他一切都不同。回想一下,实例是一个空对象,因此无法从实例数据中解析“id”。相反,它需要作为客户端输入。此 LDO 也可以使用“templateRequired”,但在“hrefSchema”中使用“required”时,它不是严格必需的。在“hrefSchema”中没有将“id”标记为必需的情况下提供“templateRequired”会导致错误,因为客户端输入是解析此链接的唯一可能来源。
本示例介绍了使用“submission”字段进行非表示输入,以及将它们与使用输入解析 URI 模板一起使用。与 HTML 表单(需要构造 URI 或发送有效负载,但不能同时执行这两个操作)不同,JSON 超级模式可以描述对同一链接上的同一操作进行这两种输入。
“submissionSchema”和“submissionMediaType”字段用于描述不是目标资源表示的有效负载。当与“http(s)://" URI 一起使用时,它们通常指的是 POST 请求有效负载,如 关于 HTTP 用法的附录 中所述。
在本例中,我们使用“mailto:” URI,根据 RFC 6068,第 3 节,它不提供任何检索资源的操作。它只能用于构建发送消息。由于没有可检索、可替换或可删除的目标资源的概念,“targetSchema”和“targetMediaType”不使用。非表示有效负载由“submissionSchema”和“submissionMediaType”描述。
我们使用“submissionMediaType”来指示 multipart/alternative 有效负载格式,提供相同数据的两个表示(HTML 和纯文本)。由于 multipart/alternative 消息是有序序列(最后一个部分是最优选的备选方案),因此我们在“submissionSchema”中将序列建模为数组。由于每个部分本身都是具有媒体类型的文档,因此我们将数组中的每个项目建模为字符串,使用“contentMediaType”来指示字符串中的格式。
请注意,诸如 multipart/form-data 之类的媒体类型将名称与每个部分关联,并且没有排序,应建模为 JSON 对象而不是数组。
请注意,某些行被换行以适应本文档的宽度限制。
{ "$id": "https://schema.example.com/interesting-stuff", "$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema", "required": ["stuffWorthEmailingAbout", "email", "title"], "properties": { "title": { "type": "string" }, "stuffWorthEmailingAbout": { "type": "string" }, "email": { "type": "string", "format": "email" }, "cc": false }, "links": [ { "rel": "author", "href": "mailto:{email}?subject={title}{&cc}", "templateRequired": ["email"], "hrefSchema": { "required": ["title"], "properties": { "title": { "type": "string" }, "cc": { "type": "string", "format": "email" }, "email": false } }, "submissionMediaType": "multipart/alternative; boundary=ab2", "submissionSchema": { "type": "array", "items": [ { "type": "string", "contentMediaType": "text/plain; charset=utf8" }, { "type": "string", "contentMediaType": "text/html" } ], "minItems": 2 } } ] }
对于 URI 参数,这三个参数分别展示了解析输入的不同方式
因此,给定从“https://example.com/api/stuff”检索到的以下实例
{ "title": "The Awesome Thing", "stuffWorthEmailingAbout": "Lots of text here...", "email": "[email protected]" }
在向客户端应用程序请求输入之前,我们可以部分解析该链接,如下所示。
{ "contextUri": "https://example.com/api/stuff", "contextPointer": "", "rel": "author", "hrefInputTemplates": [ "mailto:[email protected]?subject={title}{&cc}" ], "hrefPrepopulatedInput": { "title": "The Awesome Thing" }, "attachmentPointer": "" }
请注意,“href*”关键字代替了“targetUri”。这些是涵盖不同输入类型的三种可能的“targetUri”值。以下是每个示例
链接是从上下文资源到目标资源的类型化连接。旧的链接序列化支持“rev”关键字,该关键字接受链接关系类型作为“rel”,但反转了语义。这早已被弃用,因此 JSON 超级模式不支持它。相反,“anchor”更改上下文 URI 的能力可以用于反转链接的方向。它还可以用于描述两个资源之间的链接,这两个资源都不是当前资源。
例如,IANA 注册了一个“up”关系,但没有“down”。在 HTTP Link 标头中,可以将“down”实现为 "rev": "up"。
首先,让我们看看如何在 HTTP 中完成此操作,展示一个“self”链接和两个语义上相同的链接,一个使用“rev": "up",另一个使用“anchor”和“rel": "up”(由于格式限制而换行)。
GET https://example.com/api/trees/1/nodes/123 HTTP/1.1 200 OK Content-Type: application/json Link: <https://example.com/api/trees/1/nodes/123>; rel="self" Link: <https://example.com/api/trees/1/nodes/123>; rel="up"; anchor="https://example.com/api/trees/1/nodes/456" Link: <https://example.com/api/trees/1/nodes/456>; rev="up" { "id": 123, "treeId": 1, "childIds": [456] }
注意,"rel=up" 链接的目标 URI 与 "rel=self" 链接相同,并将 "anchor"(标识链接的上下文)设置为子级的 URI。当存在 "self" 链接时,工具可以轻松地检测到这种反向链接。
以下超模式应用于上面响应中的实例,将生成相同的 "self" 链接和带有 "anchor" 的 "up" 链接。它还展示了模板化 "base" URI 的使用,以及 "templatePointers" 中的绝对和相对 JSON 指针。
{ "$id": "https://schema.example.com/tree-node", "$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema", "base": "trees/{treeId}/", "properties": { "id": {"type": "integer"}, "treeId": {"type": "integer"}, "childIds": { "type": "array", "items": { "type": "integer", "links": [ { "anchor": "nodes/{thisNodeId}", "rel": "up", "href": "nodes/{childId}", "templatePointers": { "thisNodeId": "/id", "childId": "0" } } ] } } }, "links": [ { "rel": "self", "href": "nodes/{id}" } ] }
"base" 模板对目标 ("href") 和上下文 ("anchor") URI 的评估相同。
注意两种不同类型的 templatePointers 的使用。"thisNodeId" 映射到绝对 JSON 指针 "/id",而 "childId" 映射到相对指针 "0",它表示当前项目的价值。绝对 JSON 指针不支持任何类型的通配符,因此没有办法在没有相对 JSON 指针的情况下指定像 "当前项目" 这样的概念。
在许多系统中,单个资源被分组到集合中。这些集合通常还提供了一种使用服务器分配的标识符创建单个项目资源的方法。
对于本示例,我们将重新使用前面部分所示的单个事物模式。为了方便起见,这里重复了它,并添加了一个 "collection" 链接,其中包含一个 "targetSchema" 引用,指向我们将在接下来介绍的集合模式。
{ "$id": "https://schema.example.com/thing", "$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema", "base": "https://example.com/api/", "type": "object", "required": ["data"], "properties": { "id": {"$ref": "#/$defs/id"}, "data": true }, "links": [ { "rel": "self", "href": "things/{id}", "templateRequired": ["id"], "targetSchema": {"$ref": "#"} }, { "rel": "collection", "href": "/things", "targetSchema": {"$ref": "thing-collection#"}, "submissionSchema": {"$ref": "#"} } ], "$defs": { "id": { "type": "integer", "minimum": 1, "readOnly": true } } }
"collection" 链接对所有项目都是相同的,因此没有 URI 模板变量。"submissionSchema" 是项目本身的模式。如 第 6.2.3 节 所述,如果 "collection" 链接支持提交机制(HTTP 中的 POST),则它必须实现项目创建语义。因此,"submissionSchema" 是通过此链接创建 "thing" 的模式。
现在我们想描述 "thing" 的集合。此模式描述了一个集合,其中每个项目表示都与单个 "thing" 表示相同。虽然许多集合表示只包含项目表示的子集,但本示例使用全部来最大限度地减少涉及的模式数量。实际的集合项目以数组形式出现在对象中,因为我们将在下一个示例中向该对象添加更多字段。
{ "$id": "https://schema.example.com/thing-collection", "$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/hyper-schema", "base": "https://example.com/api/", "type": "object", "required": ["elements"], "properties": { "elements": { "type": "array", "items": { "allOf": [{"$ref": "thing#"}], "links": [ { "anchorPointer": "", "rel": "item", "href": "things/{id}", "templateRequired": ["id"], "targetSchema": {"$ref": "thing#"} } ] } } }, "links": [ { "rel": "self", "href": "things", "targetSchema": {"$ref": "#"}, "submissionSchema": {"$ref": "thing"} } ] }
这是一个简单的两个元素集合实例
{ "elements": [ {"id": 12345, "data": {}}, {"id": 67890, "data": {}} ] }
以下是应用于此实例的所有链接,包括在引用的单个 "thing" 模式中定义的那些链接
[ { "contextUri": "https://example.com/api/things", "contextPointer": "", "rel": "self", "targetUri": "https://example.com/api/things", "attachmentPointer": "" }, { "contextUri": "https://example.com/api/things", "contextPointer": "/elements/0", "rel": "self", "targetUri": "https://example.com/api/things/12345", "attachmentPointer": "/elements/0" }, { "contextUri": "https://example.com/api/things", "contextPointer": "/elements/1", "rel": "self", "targetUri": "https://example.com/api/things/67890", "attachmentPointer": "/elements/1" }, { "contextUri": "https://example.com/api/things", "contextPointer": "", "rel": "item", "targetUri": "https://example.com/api/things/12345", "attachmentPointer": "/elements/0" }, { "contextUri": "https://example.com/api/things", "contextPointer": "", "rel": "item", "targetUri": "https://example.com/api/things/67890", "attachmentPointer": "/elements/1" }, { "contextUri": "https://example.com/api/things", "contextPointer": "/elements/0", "rel": "collection", "targetUri": "https://example.com/api/things", "attachmentPointer": "/elements/0" }, { "contextUri": "https://example.com/api/things", "contextPointer": "/elements/1", "rel": "collection", "targetUri": "https://example.com/api/things", "attachmentPointer": "/elements/1" } ]
在所有情况下,上下文 URI 都显示为媒体类型 application/json 的实例,该实例不支持片段。如果实例媒体类型是 application/instance+json,它支持 JSON 指针片段,那么上下文 URI 将包含与上下文指针字段相同的片段。对于 application/json 和没有片段的其他媒体类型,考虑上下文指针和上下文 URI 非常重要。
有三个 "self" 链接,一个用于集合,一个用于 "elements" 数组中的每个项目。项目 "self" 链接是在使用 "$ref" 引用的单个 "thing" 模式中定义的。这三个链接可以通过它们的上下文或附件指针来区分。我们将在 第 9.5.2 节 中重新讨论集合的 "self" 链接的 "submissionSchema"。
有两个 "item" 链接,一个用于 "elements" 数组中的每个项目。与 "self" 链接不同,这些链接仅在集合模式中定义。它们中的每一个都与具有相同附件指针的相应 "self" 链接具有相同的目标 URI。但是,每个链接都具有不同的上下文指针。"self" 链接的上下文是 "elements" 中的条目,而 "item" 链接的上下文始终是整个集合,而与特定项目无关。
最后,有两个 "collection" 链接,一个用于 "elements" 中的每个项目。在单个项目模式中,这些链接会生成以项目资源作为上下文的链接。当从集合模式中引用时,上下文是 "elements" 数组中相关 "thing" 的位置,而不是该 "thing" 自己的单独资源 URI。
"collection" 链接具有相同目标 URI,因为只有一个相关的集合 URI。虽然在计算完整的构造链接集时,计算这两个链接可能看起来没有用,但当按需构造链接时,这种安排意味着无论你正在处理哪个 "elements" 条目,都可以在手边找到一个 "collection" 链接定义。
这里我们在集合中添加了分页。有一个 "meta" 部分用于保存有关当前页、下一页和上一页的信息。大部分模式与上一节中的相同,已省略。仅显示了新的字段以及新的或(就主要的 "self" 链接而言)更改的链接。
{ "properties": { "elements": { ... }, "meta": { "type": "object", "properties": { "prev": {"$ref": "#/$defs/pagination"}, "current": {"$ref": "#/$defs/pagination"}, "next": {"$ref": "#/$defs/pagination"} } } }, "links": [ { "rel": "self", "href": "things{?offset,limit}", "templateRequired": ["offset", "limit"], "templatePointers": { "offset": "/meta/current/offset", "limit": "/meta/current/limit" }, "targetSchema": {"$ref": "#"} }, { "rel": "prev", "href": "things{?offset,limit}", "templateRequired": ["offset", "limit"], "templatePointers": { "offset": "/meta/prev/offset", "limit": "/meta/prev/limit" }, "targetSchema": {"$ref": "#"} }, { "rel": "next", "href": "things{?offset,limit}", "templateRequired": ["offset", "limit"], "templatePointers": { "offset": "/meta/next/offset", "limit": "/meta/next/limit" }, "targetSchema": {"$ref": "#"} } ], "$defs": { "pagination": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0, "default": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 } } } } }
注意,"self" 链接包含生成精确表示的分页查询,而不是指向允许通过输入选择页面的集合的通用链接。
给定此实例
{ "elements": [ {"id": 12345, "data": {}}, {"id": 67890, "data": {}} ], "meta": { "current": { "offset": 0, "limit": 2 }, "next": { "offset": 3, "limit": 2 } } }
以下是应用于此实例的所有链接,这些链接要么没有出现在上一个示例中,要么在添加了分页后已更改。
[ { "contextUri": "https://example.com/api/things", "contextPointer": "", "rel": "self", "targetUri": "https://example.com/api/things?offset=0&limit=2", "attachmentPointer": "" }, { "contextUri": "https://example.com/api/things", "contextPointer": "", "rel": "next", "targetUri": "https://example.com/api/things?offset=3&limit=2", "attachmentPointer": "" } ]
请注意,输出中没有 "prev" 链接,因为我们正在查看第一页。"meta" 下没有 "prev" 字段,加上 "prev" 链接的 "templateRequired" 值,意味着该链接不能与这个特定实例一起使用。
让我们向入口点模式 (第 9.1 节) 添加此集合的链接,包括分页输入,以便允许客户端应用程序直接跳转到特定页面。回想一下,入口点模式仅包含链接,因此我们仅显示新添加的链接
{ "rel": "tag:rel.example.com,2017:thing-collection", "href": "/things{?offset,limit}", "hrefSchema": { "$ref": "thing-collection#/$defs/pagination" }, "submissionSchema": { "$ref": "thing#" }, "targetSchema": { "$ref": "thing-collection#" } }
现在我们看到分页参数被接受为输入,因此我们可以跳转到集合中的任何页面。链接关系类型是一个自定义类型,因为通用的 "collection" 链接只能与项目作为其上下文一起使用,而不能与入口点或其他资源一起使用。
当我们没有任何 "thing" 时,我们没有任何具有相关 "collection" 链接的资源。因此,我们不能使用 "collection" 链接的提交关键字来创建第一个 "thing";超模式必须相对于实例进行评估。由于集合实例中的 "elements" 数组将为空,因此它也不能为我们提供 "collection" 链接。
但是,我们的入口点链接可以将我们带到空的集合,并且我们可以使用超模式中 "item" 链接的存在来识别它是一个集合。由于 "item" 链接的上下文是集合,我们只需查找具有相同上下文的 "self" 链接,然后就可以将其视为创建操作的集合。
可以假设,我们入口点模式中的自定义链接关系类型足以确保我们找到了正确的集合。能够识别该自定义链接关系类型的客户端应用程序可能知道它可以立即假设目标是一个集合,但通用用户代理无法做到这一点。尽管我们的示例中存在 "-collection" 后缀,但通用用户代理无法知道该子字符串是否表示超媒体资源集合,还是其他类型的集合。
一旦我们识别出 "self" 链接是针对正确的集合,我们就可以使用它的 "submissionSchema" 和/或 "submissionMediaType" 关键字来执行项目创建操作。[CREF8]如果集合未过滤且未分页,这将完美运行。但是,通常应将 POST 发送到将包含已创建资源的集合,并且 "self" 链接必须包括任何过滤器、分页或其他查询参数。即使生成的项目与过滤器不匹配或未出现在该页面中,将 POST 发送到此类 "self" 链接是否仍然有效?有关进一步讨论,请参阅 GitHub 问题 #421。 [CREF9]超模式草案 04 定义了一个 "create" 链接关系,它以模式而不是实例作为其上下文。这与基于实例的链接模型不符,并且错误地将操作名称用作链接关系类型。但是,从模式到集合实例定义更正确设计的链接可能是解决此问题的一种可能方法。同样,有关更多详细信息,请参阅 GitHub 问题 #421。
JSON 超模式定义了 JSON 模式核心的词汇表,并关注其中列出的所有安全注意事项。作为链接序列化格式,RFC 8288 Web 链接 的安全注意事项也适用,并进行适当的调整(例如,"anchor" 作为 LDO 关键字,而不是 HTTP Link 标头属性)。
如 第 6.5 节 所述,所有描述目标资源的 LDO 关键字都是建议性的,不得替代目标资源在响应操作时提供的权威信息。目标资源响应应指示它们自己的超模式,这是权威性的。
如果目标响应中的超模式与(通过 "$id")当前 LDO 中的超模式匹配,则目标属性可能被视为权威性的。[CREF10]需要添加一些关于 "$id" 欺骗风险的内容,但鉴于规范的其他部分不鼓励始终重新下载链接的模式,风险缓解选项尚不清楚。
用户代理或客户端应用程序不得使用 "targetSchema" 的值来帮助解释在响应链接后接收的数据,因为这会使 "安全" 数据容易被重新解释。
在选择如何解释数据时,服务器提供的类型信息(或从文件名推断,或任何其他常用方法)必须是唯一考虑因素,并且链接的 "targetMediaType" 属性不得使用。用户代理可以使用此信息来确定如何表示链接或在哪里显示它(例如悬停文本、在新标签页中打开)。如果用户代理决定将链接传递给外部程序,他们应首先验证数据是否属于通常传递给该外部程序的类型。
这是为了防止对 "安全" 数据进行重新解释,类似于对 "targetSchema" 的预防措施。
在“targetHints”中传递的协议元数据值不应被视为权威。基于对元数据值的错误假设可能适用的协议定义的任何安全注意事项仍然适用。
即使没有直接适用的协议安全注意事项,实现也必须准备好处理与链接的“targetHints”值不匹配的响应。
当使用“self”链接关系表示对象的完整表示时,如果目标 URI 不等效于或不是用于请求包含具有“self”链接的目标 URI 的资源表示的 URI 的子路径,则用户代理不应将表示视为目标 URI 所表示的资源的权威表示。 [CREF11]本段中“子路径”选项的本意已不再完全清楚。它可能是为了允许集合中嵌入项表示的“self”链接,这些链接通常具有作为该集合 URI 的子路径的目标 URI,被视为权威。但是,这只是一个常见的設計慣例,似乎并没有基于 RFC 3986 或其他有关 URI 使用的任何指南。有关进一步的讨论,请参见 GitHub 问题 #485。
感谢 Gary Court、Francis Galiegue、Kris Zyp 和 Geraint Luff 对 JSON Schema 初始草案的贡献。
感谢 Jason Desrosiers、Daniel Perrett、Erik Wilde、Ben Hutton、Evgeny Poberezkin、Brad Bowman、Gowry Sankar、Donald Pipowitch、Dave Finlay 和 Denis Laxalde 对文档的提交和补丁。
[json-schema] | Wright, A. 和 H. Andrews,“JSON Schema: 用于描述 JSON 文档的媒体类型”,Internet-Draft draft-handrews-json-schema-02,2017 年 11 月。 |
[json-schema-validation] | Wright, A.、Andrews, H. 和 G. Luff,“JSON Schema 验证:用于 JSON 结构验证的词汇表”,Internet-Draft draft-handrews-json-schema-validation-02,2017 年 11 月。 |
[relative-json-pointer] | Luff, G. 和 H. Andrews,“相对 JSON 指针”,Internet-Draft draft-handrews-relative-json-pointer-02,2018 年 1 月。 |
[RFC2119] | Bradner, S.,“RFC 中用于指示需求级别的关键词”,BCP 14,RFC 2119,DOI 10.17487/RFC2119,1997 年 3 月。 |
[RFC3986] | Berners-Lee, T.、Fielding, R. 和 L. Masinter,“统一资源标识符 (URI):通用语法”,STD 66,RFC 3986,DOI 10.17487/RFC3986,2005 年 1 月。 |
[RFC4287] | Nottingham, M. 和 R. Sayre,“Atom 合成格式”,RFC 4287,DOI 10.17487/RFC4287,2005 年 12 月。 |
[RFC6570] | Gregorio, J.、Fielding, R.、Hadley, M.、Nottingham, M. 和 D. Orchard,“URI 模板”,RFC 6570,DOI 10.17487/RFC6570,2012 年 3 月。 |
[RFC6573] | Amundsen, M.,“项目和集合链接关系”,RFC 6573,DOI 10.17487/RFC6573,2012 年 4 月。 |
[RFC6901] | Bryan, P.、Zyp, K. 和 M. Nottingham,“JavaScript 对象符号 (JSON) 指针”,RFC 6901,DOI 10.17487/RFC6901,2013 年 4 月。 |
[RFC8288] | Nottingham, M.,“Web 链接”,RFC 8288,DOI 10.17487/RFC8288,2017 年 10 月。 |
[I-D.reschke-http-jfv] | Reschke, J.,“HTTP 头字段值的 JSON 编码”,Internet-Draft draft-reschke-http-jfv-06,2017 年 6 月。 |
[RFC2046] | Freed, N. 和 N. Borenstein,“多用途互联网邮件扩展 (MIME) 第二部分:媒体类型”,RFC 2046,DOI 10.17487/RFC2046,1996 年 11 月。 |
[RFC4151] | Kindberg, T. 和 S. Hawke,“'tag' URI 方案”,RFC 4151,DOI 10.17487/RFC4151,2005 年 10 月。 |
[RFC5789] | Dusseault, L. 和 J. Snell,“HTTP 的 PATCH 方法”,RFC 5789,DOI 10.17487/RFC5789,2010 年 3 月。 |
[RFC6068] | Duerst, M.、Masinter, L. 和 J. Zawinski,“'mailto' URI 方案”,RFC 6068,DOI 10.17487/RFC6068,2010 年 10 月。 |
[RFC7230] | Fielding, R. 和 J. Reschke,“超文本传输协议 (HTTP/1.1):消息语法和路由”,RFC 7230,DOI 10.17487/RFC7230,2014 年 6 月。 |
[RFC7231] | Fielding, R. 和 J. Reschke,“超文本传输协议 (HTTP/1.1):语义和内容”,RFC 7231,DOI 10.17487/RFC7231,2014 年 6 月。 |
[RFC7807] | Nottingham, M. 和 E. Wilde,“HTTP API 的问题详细信息”,RFC 7807,DOI 10.17487/RFC7807,2016 年 3 月。 |
遵循 REST 架构风格约束的超媒体 API 能够创建通用用户代理。这种用户代理没有应用程序特定的知识。相反,它理解预定义的媒体类型、URI 方案、协议和链接关系,通常通过识别这些关系并协调使用实现对它们的支持的现有软件来实现。然后可以在这种用户代理之上构建客户端应用程序,专注于它们自己的语义和逻辑,而不是交互机制。
超级模式只关注一次一个资源和一组关联链接。就像 Web 浏览器一次只处理一个 HTML 页面,而没有关于该页面如何作为“站点”的一部分运作的概念一样,支持超级模式的用户代理一次只处理一个资源,而没有关于该资源如何融入 API 的概念。
因此,超级模式适用于 API 中使用,但不适用于将 API 描述为完整的实体。没有办法描述 API 范围内的概念,而不是资源和链接范围,这些描述超出了 JSON 超级模式的范围。
由于给定的 JSON 超级模式在某个时间点只与单个资源一起使用,因此它没有版本控制的固有概念。但是,给定的资源可以随着时间的推移更改它使用的模式或模式,这些模式的 URI 可以用来指示版本信息。当与支持使用媒体类型参数指示模式的媒体类型一起使用时,这些版本化的模式 URI 可以用于内容协商。
资源可以表明它是多个模式的实例,这允许同时支持多个兼容版本。然后,客户端应用程序可以使用它识别的超级模式,并忽略更新或旧版本。
由于超级模式一次表示一个资源,因此它不提供对使用链接执行的协议操作的所有可能响应的枚举。每个响应,包括错误,都被视为自己的(可能匿名)资源,并且应该标识它自己的超级模式,并可选地使用适当的媒体类型,例如 RFC 7807 的“application/problem+json”,以允许用户代理或客户端应用程序解释除协议自身的狀態報告之外提供的任何信息。
可以在没有实例数据的情况下静态分析一组超级模式,以生成文档或代码等输出。但是,在没有运行时实例数据的情况下,无法访问验证和超级模式的全部功能集。
这是一个有意设计的选择,为超媒体系统提供最大的运行时灵活性。JSON Schema 作为媒体类型允许为静态分析和内容生成建立额外的词汇表,这些词汇表在本规范中没有涉及。此外,如果设计时描述是一个目标,单个系统可以将它们的用法限制在可以在静态分析的子集上。 [CREF12]已经提出了用于 API 文档和其他目的的词汇表,欢迎在 https://github.com/json-schema-org/json-schema-vocabularies 处进行贡献
[CREF13]在离开 Internet-Draft 状态之前删除本节。