互联网工程任务组 | H. 安德鲁斯,编 |
互联网草案 | |
预期状态:信息性 | A. 赖特,编 |
过期时间: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 文档进行注释的 JSON 模式词汇表,以及通过超媒体环境(如 HTTP)处理和操作远程 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 模式的情况下使用,并且可以通过将规范链接描述模式作为使用链接的数据结构的模式来引用,从而声明使用此格式。规范链接描述模式的 URI 为:<https://json-schema.fullstack.org.cn/draft/2019-09/links#>.
JSON 超级模式实现可以自由地以任何格式提供输出。但是,在一致性测试套件中定义了一种特定格式,该格式也用于说明 “实施要求” 中的要点,并显示 示例 生成的输出。建议实现能够以这种格式生成输出以方便测试。描述推荐输出格式的 JSON 模式的 URI 为 <https://json-schema.fullstack.org.cn/draft/2019-09/output/hyper-schema#>.
为了纠正错误,可以在规范草案之间发布更新的词汇表和元模式 URI。实现应考虑在本次规范草案之后和下一次规范草案之前发布的日期为的 URI,以指示与这里列出的 URI 相同的语法和语义。
来自所有适用于实例中位置的模式的超级模式关键字(如 JSON 模式核心的第 3.1 节 所定义)可用于该实例。
当多个子模式适用于给定的子实例时,所有 “link” 数组都必须以任何顺序组合成单个集合。结果集中每个对象都必须保留其自己的适用的 “base” 值列表(按解析顺序),这些值来自同一模式以及任何父模式。
与所有 JSON 模式关键字一样,本节中描述的所有关键字都是可选的。最小的有效 JSON 超级模式是空白对象。
如果存在,此关键字必须首先 作为 URI 模板解析,然后必须作为 URI 引用相对于实例的当前 URI 基准进行解析。结果必须设置为处理包含 “base” 的子模式及其所有子模式时实例的新的 URI 基准。
当为与 “anchor” 一起使用而解析时,解析 “base” 模板的过程可能与为与 “href” 一起使用而解析时不同,这将在 URI 模板部分中详细说明。
模式的 “links” 属性用于将链接描述对象与实例关联。此属性的值必须是一个数组,数组中的项必须是链接描述对象,如下所定义。
链接描述对象 (LDO) 是 RFC 8288,第 2 节 中定义的抽象链接模型的序列化。如该文档所述,链接由上下文、关系类型、目标以及可选的目标属性组成。JSON 超级模式的 LDO 提供所有这些内容,以及使用 JSON 模式来描述以各种方式与链接一起使用的输入的其他功能。
由于使用 URI 模板来标识链接上下文和目标,以及在标识目标时可选地进一步使用客户端输入,因此 LDO 是一个链接模板,当与 JSON 实例文档一起使用时,它可能会解析为多个链接。
LDO 的特定用途,通常涉及跨协议的请求和响应,称为操作。对于许多协议,在任何给定链接上都可以执行多种操作。协议由目标的 URI 方案指示。请注意,并非所有 URI 方案都指示可以用于通信的协议,即使使用指示此类协议的 URI 方案的资源也不一定可以通过该协议获得。
链接描述对象必须是一个对象,并且 “href” 和 “rel” 属性必须存在。每个关键字在本节中都有简要介绍,并在本文档后面提供更多使用说明和综合示例。
在 JSON 超级模式中,链接的上下文资源默认情况下是附加到它的子实例(如 JSON 模式核心规范的第 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 指针。
虽然可以使用 “anchor” 关键字来设置具有已知 URI 的备用上下文,但由于缺乏针对 application/json 的片段标识符语法,因此通常无法使用 URI 来更改 JSON 实例中的上下文。
即使在定义 JSON 指针作为片段标识符语法的 “+json” 媒体类型中,如果默认上下文嵌套在数组中,也无法获取默认上下文在该数组中的方位索引,以便构建指向该同一嵌套 JSON 对象中另一个属性的指针。这将在示例中演示。
处理此关键字的结果应该是 URI 片段(如果实例的媒体类型允许此类片段)。否则,它必须是字符串编码的 JSON 指针。
链接的关系类型标识其语义。它是传达应用程序如何与资源交互的主要方式。
关系定义通常不依赖于媒体类型,鼓励用户使用最合适的现有接受的关系定义。
此属性的值必须是字符串或字符串数组。如果值为数组,则必须包含至少一个字符串。
每个字符串必须是 RFC 8288,第 2.1 节中定义的单个链接关系类型,包括限制,即不应根据另一个链接关系类型的存在或不存在来推断附加语义。
此属性是必需的。
如 RFC 4287 的第 4.2.7.2 节 最初定义, “self” 链接指示目标 URI 标识与链接上下文等效的资源。在 JSON 超级模式中, “self” 链接必须从实例中解析,因此 “hrefSchema” 必须不存在。
超级模式作者应使用 “templateRequired” 来确保 “self” 链接具有使用所需的所有实例数据。
超级模式实现必须认识到,具有整个当前实例文档作为上下文的具有关系类型 “self” 的链接描述了用户代理如何与该实例文档表示的资源进行交互。
RFC 6573 定义并注册了 “item” 和 “collection” 链接关系类型。JSON 超级模式对由这些类型指示的集合资源强加了额外的语义。
实现必须将 “collection” 链接的目标和 “item” 链接的上下文识别为集合。
超媒体中的一种著名设计模式是使用集合资源创建集合的成员并为其提供服务器分配的 URI。如果由 URI 方案指示的协议定义了适合于创建具有服务器分配的 URI 的资源的特定方法,则由这些链接关系类型标识的集合资源必须不要定义与创建集合成员的语义冲突的该方法的语义。集合资源可以实现通过此类协议方法进行的项目创建,用户代理可以假设,如果存在任何此类操作,则它具有项目创建语义。
由于此方法对应于 JSON 超级模式的数据提交概念,因此链接的 "submissionSchema" 字段应该与集合项表示的模式兼容,如“item”链接的目标资源或“collection”链接的上下文资源的“self”链接所指示。
当没有注册的关系(除了“related”)适用时,鼓励用户根据 RFC 8288 第 2.1.2 节 的描述,自行创建自己的扩展关系类型。选择链接关系类型 URI 的最简单方法是使用已经用于标识系统主要资源的 URI 方案,或者使用人类可读的、不可解析的 URI 方案,例如 RFC 4151 定义的“tag”。
扩展关系类型 URI 无需可解析,即使使用允许解析的方案也是如此。
目标 URI 模板用于标识链接的目标,并可能使用实例数据。此外,使用 "hrefSchema",此模板可以根据客户端输入标识一组可能的目标资源。在“URI 模板”部分中介绍了使用或不使用客户端输入解析 URI 模板的完整过程。
“href”链接描述属性的值是用于确定相关资源的目标 URI 的模板。实例属性的值必须解析为针对实例基本 URI 的 URI 引用。
此属性是必需的。
本节中的关键字用于解析与超模式相关的所有 URI 模板:“base”、“anchor”和“href”。有关完整的模板解析算法,请参见“URI 模板”部分。
请注意,在解析“base”模板时,解析开始的附加点是正在解析的“href”或“anchor”关键字的附加点,它需要解析“base”模板,而不是“base”关键字本身的附加点。
“templatePointers”链接描述属性的值必须是一个对象。对象中的每个属性值必须是一个有效的 JSON 指针,或是一个有效的 相对 JSON 指针,该指针相对于正在解析该模板的链接的附加点进行评估。
对于对象中与正在解析的模板中的变量名称匹配的每个属性名称,该属性的值将调整该变量的变量解析的起始位置。与正在解析的模板中的模板变量名称不匹配的属性必须被忽略。
此关键字的值必须是一个数组,并且元素必须是唯一的。每个元素应该与链接的 URI 模板中的变量匹配,不进行百分比编码。在完成整个 URI 模板解析过程后,如果此数组中存在的任何变量都没有值,则必须不使用该链接。
本节中的所有属性仅供参考。虽然诸如“title”和“description”之类的关键字主要用于向用户展示链接,但那些预测链接交互或响应性质的关键字不应该被视为权威性的。只要目标资源的运行时行为与 LDO 中的目标属性发生冲突,就必须遵守该行为。
此属性定义链接的标题。该值必须是一个字符串。
用户代理可以利用此标题在向用户展示链接时使用。
此属性提供除标题中存在的以外的其他信息。该值必须是一个字符串。虽然标题最好简短,但描述可以用来更详细地介绍链接的目的和用法。
用户代理可以利用此描述在向用户展示链接时使用。
此属性的值表示获取此资源时预期返回的媒体类型 RFC 2046。此属性值可以是一个媒体范围,使用与 RFC 7231 第 5.3.2 节 - HTTP“Accept”头 中定义的相同的模式。
此属性类似于其他链接序列化格式的“type”属性。用户代理可以利用此信息在链接被跟随之前告知他们向用户展示的界面,但必须不要利用此信息来解释结果数据。相反,用户代理必须使用响应中给出的媒体类型来进行运行时解释。有关“targetMediaType”误用的详细说明,请参见关于 “安全问题” 的部分。
对于支持内容协商的协议,实现可以选择使用 "headerSchema" 中的协议特定信息来描述可能的目标媒体类型。如果协议特定信息和“targetMediaType”都存在,则“targetMediaType”的值必须与协议特定信息兼容,并且应该指示在没有内容协商的情况下将返回的媒体类型。
当不存在此类协议特定信息,或者当实现无法识别所涉及的协议时,该值应该被认为是“application/json”。
此属性提供一个预期描述链接目标表示的模式。根据协议的不同,该模式可能描述或可能不描述针对使用链接执行的任何特定操作的请求或响应。有关此关键字如何与 HTTP 一起使用的深入讨论,请参见“JSON 超级模式和 HTTP”部分。
此属性的值仅供参考。它表示预期通过与目标资源交互可以发现的信息,通常以协议特定控制信息或元数据(例如响应 HTTP HEAD 或 OPTIONS 请求返回的标头)的形式出现。该协议由“href”URI 方案确定,但请注意,资源并不保证可以通过此类协议访问。
此属性的值应该是一个对象。此对象的键应该是控制数据字段名称的小写形式。每个值应该是一个数组,以便统一处理多值字段。多个值必须表示为数组,而不是单个字符串。
具有不适合作为 JSON 对象表示的控制信息的协议可以用其他数据类型(例如数组)来表示。
无法理解为指示协议的一部分的值必须被 JSON 超级模式实现忽略。应用程序可以利用此类值,但必须不要假设与其他实现的互操作性。
实现必须不要假设此对象中已说明所有可发现的信息。客户端应用程序必须正确处理与此属性的值相矛盾的运行时响应。
客户端应用程序必须不要假设实现将根据此属性的值自动采取任何操作。
有关将此关键字与 HTTP 和类似协议一起使用的指导,请参见“JSON 超级模式和 HTTP”。
有四种方法可以将客户端输入与链接一起使用,每种方法都由一个单独的链接描述对象关键字解决。在执行操作时,用户代理应该忽略与它们的语义无关的模式。
“hrefSchema”链接描述属性的值必须是一个有效的 JSON 模式。此模式用于验证用户输入或其他用户代理数据,以填充 “href” 中的 URI 模板。
省略“hrefSchema”或将整个模式设置为“false”将阻止任何用户代理数据被接受。
将适用于特定变量的任何子模式设置为 JSON 文字值“false”将阻止任何用户代理数据被接受以用于该单个变量。
对于可以从实例数据解析的模板变量,如果实例数据针对“hrefSchema”中所有适用的子模式有效,则必须使用它来预填充该变量的输入数据集。
请注意,即使从实例预填充数据,"hrefSchema" 中针对该变量的验证模式也不必与适用于实例数据位置的验证模式相同。这允许针对用户代理数据设置不同的验证规则,例如支持拼写出来的月份用于日期时间输入,但使用标准日期时间格式进行存储。
在接受输入后,可能会覆盖预填充的实例数据,结果数据集必须成功验证针对“hrefSchema”的值。如果它无效,则必须不使用该链接。如果它有效,则“URI 模板”部分中给出的过程将继续使用此更新的数据集。
[CREF2]与“targetHints”一样,此关键字在一定程度上未指定,以鼓励实验和反馈,因为我们试图在灵活性与清晰度之间取得平衡。
如果存在,此属性是协议特定请求标头或类似控制和元数据的模式。该对象的 value 必须是一个有效的 JSON 模式。该协议由“href”URI 方案确定,但请注意,资源并不保证可以通过此类协议访问。该模式仅供参考;目标资源的行为不受其存在的影响。
此关键字的目的是宣传目标资源交互功能,并向用户代理和客户端应用程序指示哪些标头和标头值可能有用。用户代理和客户端应用程序可以使用该模式验证相关的标头,但必须不要假设缺少标头或值是不允许使用的。虽然模式作者可以将“additionalProperties”设置为 false,但不推荐这样做,并且必须不要阻止客户端应用程序或用户代理在发出请求时提供其他标头。
将 JSON 数据模型精确映射到标头是协议相关的。但是,在大多数情况下,此模式应该指定“object”类型,属性名称应该是控制数据字段名称的小写形式。与“targetHints”一样,值应该被描述为数组,以允许多个值,即使只期望一个值也是如此。
有关将此关键字与 HTTP 和类似协议一起使用的详细指南,请参见“JSON 超级模式和 HTTP”部分。
“headerSchema”适用于协议支持的任何请求方法或命令。在生成请求时,用户代理和客户端应用程序应该忽略与该请求无关的标头的模式。
在 JSON 超级模式中,"targetSchema" 提供了对目标资源表示的非权威描述。客户端应用程序可以使用 “targetSchema” 来构建输入,用于替换或修改表示,或作为构建基于补丁媒体类型的补丁文档的基准表示。
或者,如果 “targetSchema” 不存在,或者客户端应用程序更倾向于只使用权威信息,它可以与目标资源交互以确认或发现其表示结构。
“targetSchema” 不用于描述链接操作响应,除非响应语义表明它是一个目标资源的表示。在所有情况下,响应本身指示的模式是权威的。有关详细示例,请参阅 "JSON 超级模式和 HTTP"。
该 "submissionSchema" 和 "submissionMediaType" 关键字描述目标资源实现的处理函数的域。否则,如上所述,提交模式和媒体类型将被忽略,因为它们与不相关的操作无关。
如果存在,此属性指示客户端应用程序和用户代理应为由 "submissionSchema" 描述的请求有效负载使用的媒体类型格式。
省略此关键字的行为与 application/json 值相同。
请注意,“submissionMediaType” 和 “submissionSchema” 不限于 HTTP URI。 [CREF3]此语句可能会移到示例结束的位置。
此属性包含一个模式,该模式定义了根据 “submissionMediaType” 属性编码并发送到目标资源以进行处理的文档的可接受结构。这可以被视为描述目标资源实现的处理函数的域。
这是一个与 "targetSchema" 属性不同的概念,后者描述目标信息资源(包括在 PUT 请求中替换资源内容),而 “submissionSchema” 描述用户提交的请求数据,该数据将由资源进行评估。“submissionSchema” 旨在与有效负载未根据目标表示定义的请求一起使用。
省略 “submissionSchema” 的行为与 “true” 值相同。
在高层次上,符合要求的实现将满足以下要求。每个要求在各个关键字部分和关键字组概述中都有更详细的说明。
请注意,围绕实现如何必须识别 “self”、“collection” 和 “item” 链接的要求在 链接关系类型 部分中得到全面涵盖,在此不再重复。
虽然它不是实现的强制格式,但在测试套件中使用的输出格式总结了每个链接在可以使用之前需要计算的内容
未参与生成上述信息的其他 LDO 关键字将在生成测试套件的输出时完全按原样包含。除非特别相关,否则这些字段将不再讨论。
在使用链接之前,必须通过将超级模式应用于实例并找到所有适用的有效链接来发现它们。请注意,除了收集有效的链接外,还需要找到用于解析每个 LDO 的 URI 模板的任何 "base" 值,并通过对实现的 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” 代表任何适合的输入机制。这可能是一个文字 Web 表单,也可能是一个更程序化的构造,例如一个接受特定字段和数据类型的回调函数,以及给定的初始值(如果有)。
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 作者应牢记这一点,如果确切的表示很重要,则使用其他类型(例如字符串或布尔值)。如果数字以字符串的形式提供作为输入,则应使用作为输入的字符串。
对于给定的链接,实现必须使所有目标属性关键字的值直接对用户代理可用。实现可以提供用于使用此信息的额外接口,如每个关键字部分所述。
对于给定的链接,实现必须使每个输入模式关键字的值直接对用户代理可用。
为了鼓励 URI 模板解析过程的封装,实现可以省略仅用于构建 URI 的 LDO 关键字。但是,实现必须提供对链接关系类型的访问。
应使用户代理能够使用无法识别的关键字,否则必须忽略这些关键字。
超级模式实现应提供访问构建对目标资源的任何有效请求所需的所有信息。
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 系统中,或使用与 HTTP 明确类似的协议(如 CoAP)的系统中。
本节提供有关如何使用每个常见的 HTTP 方法与链接以及集合资源如何对 HTTP POST 强加额外约束的指导。此外,还提供了有关提示 HTTP 响应标头值和描述与给定资源相关的可能的 HTTP 请求标头的指导。
JSON 模式核心规范的第 13 节 提供了有关在超媒体系统中将实例链接到其模式的指导。这可以通过网络可访问的模式完成,或者可以简单地识别预先打包在客户端应用程序中的模式。JSON 超模式有意不约束这种机制,尽管 RECOMMENDED 使用核心规范中概述的技术,只要有可能。
链接描述对象不直接指示目标资源支持哪些操作,例如 HTTP 方法。相反,操作应该主要从链接 关系类型 和 URI 方案推断出来。
这意味着,对于每个目标资源和链接关系类型对,模式作者 SHOULD 仅定义单个 LDO。虽然可以使用“allow”与“targetHints”一起重复一个关系类型和目标对,并使用标记为允许的不同 HTTP 方法,但不 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 请求的语义由目标资源而不是 HTTP 定义。
除了协议中立的“submission*”关键字之外(参见 第 9.3 节 获取非 HTTP 示例),还可以使用“Accept-Post”标头指定必要的媒体类型,并且 MAY 通过“targetHints”字段进行宣传。 [CREF4]如果两者都被使用会发生什么?此外,“submissionSchema”是 MUST 支持的,而“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”中没有区分哪种方法响应包含哪个标头。
RECOMMENDED 模式作者在适用时提供以下类型 HTTP 标头的值的提示
通常,在不同时间可能具有不同值的标头 SHOULD NOT 包含在“targetHints”中。
例如,允许 HEAD、GET 和 POST 的 Allow 标头将显示如下
{ "targetHints": { "allow": ["HEAD", "GET", "POST"] } }
请注意,这无论是有一个带有逗号分隔值的单行 Allow 标头、多行上的多个 Allow 标头,每个标头都包含一个值,还是这些安排的任何组合,其表示方式都相同。与 HTTP 标头通常一样,逗号分隔的值和标头的多个出现方式将以相同的方式对待。
模式 SHOULD 写成描述遵循正在进行的工作中制定的指南的 JSON 序列化 "HTTP 标头字段值的 JSON 编码"。该文档示例中显示的方法 SHOULD 尽可能应用于其他类似结构的标头。
RECOMMENDED 模式作者在适用时描述以下类型 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,并提供了两个链接:“about”链接指向 API 文档,以及“self”链接指示这是基本 URI 的模式。
{ "$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”链接中“api”在基本 URI 和“../api”href 中的重复是由于 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 不允许片段,因此上下文指针是完全描述上下文的必要条件。它的默认行为与附件指针相同。
让我们向系统添加“things”,从单个“thing”开始
{ "$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 } } }
我们的“thing”具有服务器分配的 ID,这是构建“self”链接所必需的。它还有一个“data”字段,可以是任何类型。"$defs"部分的原因将在下一个示例中说明。
请注意,“id”不是验证模式所必需的,但“self”链接所必需。这很有道理:一个“thing”只有在创建后并且服务器分配了 ID 时才具有 URI。但是,您可以将此模式与仅包含数据字段的实例一起使用,这允许您验证您即将创建的“thing”实例。
让我们向我们的入口点模式添加一个链接,这样您就可以直接跳转到特定的事物,前提是您能够提供它的 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 表单不同,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 链接报头中,您可以将“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”链接的 target URI 与“rel=self”链接的 target URI 相同,并将“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}" } ] }
对目标 (“href”) 和上下文 (“anchor”) URI 的“base”模板评估是相同的。
请注意使用的两种不同的 templatePointers 类型。“thisNodeId”映射到一个绝对 JSON 指针,“/id”,而“childId”映射到一个相对指针,“0”,它指示当前项目的 value。绝对 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),则它 MUST 实现项目创建语义。因此,“submissionSchema”是通过此链接创建“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": {}} ] }
以下是适用于此实例的所有链接,包括在引用的单个“事物”模式中定义的链接
[ { "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" } ]
在所有情况下,都会显示媒体类型 application/json 实例的上下文 URI,它不支持片段。如果实例媒体类型为 application/instance+json,它支持 JSON 指针片段,那么上下文 URI 将包含与上下文指针字段相同的片段。对于 application/json 和其他没有片段的媒体类型,考虑上下文指针和上下文 URI 非常重要。
有三个“self”链接,一个用于集合,一个用于“elements”数组中的每个项目。项目“self”链接是在使用“$ref”引用的单个“事物”模式中定义的。这三个链接可以通过它们的上下文或附件指针来区分。我们将在第 9.5.2 节中重新讨论集合“self”链接的“submissionSchema”。
有两个“item”链接,每个“elements”数组中的一个项目。与“self”链接不同,这些链接只在集合模式中定义。它们中的每一个都有与具有相同附件指针的相应“self”链接相同的目标 URI。但是,每个都有不同的上下文指针。“self”链接的上下文是“elements”中的条目,而“item”链接的上下文始终是整个集合,与特定项目无关。
最后,有两个“collection”链接,每个“elements”中的一个项目。在单个项目模式中,这些链接会生成以项目资源作为上下文的链接。当从集合模式引用时,上下文是“elements”数组中相关“事物”的位置,而不是该“事物”自身的独立资源 URI。
集合链接具有相同目标 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”链接只能与项目作为其上下文一起使用,而不能与入口点或其他资源一起使用。
当我们没有任何“事物”时,我们没有任何具有相关“collection”链接的资源。因此,我们无法使用“collection”链接的提交关键字来创建第一个“事物”;超模式必须针对实例进行评估。由于集合实例中的“elements”数组将为空,因此它也无法为我们提供集合链接。
但是,我们的入口点链接可以将我们带到空集合,并且我们可以使用超模式中“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 链接标头属性)。
如第 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 模式:描述 JSON 文档的媒体类型",Internet 草案 draft-handrews-json-schema-02,2017 年 11 月。 |
[json-schema-validation] | Wright, A.、Andrews, H. 和 G. Luff,"JSON 模式验证:JSON 结构验证词汇",Internet 草案 draft-handrews-json-schema-validation-02,2017 年 11 月。 |
[relative-json-pointer] | Luff, G. 和 H. Andrews,"相对 JSON 指针",Internet 草案 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.,"网络链接",RFC 8288,DOI 10.17487/RFC8288,2017 年 10 月。 |
[I-D.reschke-http-jfv] | Reschke, J.,"用于 HTTP 头部字段值的 JSON 编码",互联网草案 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 方案、协议和链接关系,通常是通过识别这些关系并协调使用实现对它们的支持的现有软件来实现的。然后,可以在这种用户代理的基础上构建客户端应用程序,专注于自己的语义和逻辑,而不是交互机制。
超级模式一次只关注一个资源和一组关联的链接。就像网页浏览器一次只处理一个 HTML 页面一样,没有关于该页面如何作为“网站”的一部分运作的概念,超级模式感知的用户代理一次处理一个资源,没有任何关于该资源如何融入 API 的概念。
因此,超级模式适合在 API 中使用,但不适合作为独立实体描述 API。没有办法描述 API 范围内的概念,而不是资源和链接范围内的概念,而此类描述超出了 JSON 超级模式的范围。
由于给定的 JSON 超级模式在单个时间点与单个资源一起使用,因此它本身没有版本控制的概念。但是,给定的资源可以随着时间的推移改变它使用的模式或模式,并且这些模式的 URI 可以用来指示版本信息。当与支持使用媒体类型参数指示模式的媒体类型一起使用时,这些版本化的模式 URI 可以用于内容协商。
一个资源可以表明它是一个或多个模式的实例,这允许同时支持多个兼容版本。然后,客户端应用程序可以使用它识别的超级模式,并忽略更新或旧的版本。
由于超级模式一次表示单个资源,因此它不提供对使用链接执行的协议操作的所有可能响应的枚举。每个响应,包括错误,都被视为其自己的(可能是匿名的)资源,并且应该识别其自己的超级模式,并选择性地使用适当的媒体类型,例如 RFC 7807 的“application/problem+json”,以允许用户代理或客户端应用程序解释在协议自己的状态报告之外提供的任何信息。
可以对一组超级模式进行静态分析,而无需实例数据,以生成输出,例如文档或代码。但是,如果没有运行时实例数据,就无法访问验证和超级模式的全部功能集。
这是有意设计的,目的是为超媒体系统提供最大的运行时灵活性。JSON 模式作为一种媒体类型,允许为静态分析和内容生成建立额外的词汇表,这些词汇表在本规范中未涉及。此外,如果设计时描述是目标,则各个系统可以将其使用限制为可以在静态分析的子集中进行分析。 [CREF12]已经提出了用于 API 文档和其他目的的词汇表,欢迎在 https://github.com/json-schema-org/json-schema-vocabularies 贡献。