互联网工程任务组 | H. Andrews,编辑 |
互联网草案 | Cloudflare, Inc. |
预期状态:信息性 | A. Wright,编辑 |
失效日期:2018 年 7 月 23 日 | 2018 年 1 月 19 日 |
JSON 超级模式:用于 JSON 超媒体注释的词汇
draft-handrews-json-schema-hyperschema-01
JSON Schema 是一种基于 JSON 的格式,用于使用各种词汇描述 JSON 数据。本文件指定了一种词汇,用于使用超链接注释 JSON 文档。这些超链接包含描述如何通过超媒体环境(如 HTTP)操纵和交互远程资源的属性,以及根据实例值确定链接是否可用。本文件中描述的超链接序列化格式也可独立于 JSON Schema 使用。
本草案的问题列表可以在 <https://github.com/json-schema-org/json-schema-spec/issues> 中找到。
有关更多信息,请参见 <https://json-schema.fullstack.org.cn/>。
要提供反馈,请使用此问题跟踪器、主页上列出的通信方式或电子邮件联系文档编辑。
本互联网草案完全符合 BCP 78 和 BCP 79 的规定提交。
互联网草案是互联网工程任务组 (IETF) 的工作文档。请注意,其他组织也可能以互联网草案的形式分发工作文档。当前互联网草案的列表位于 http://datatracker.ietf.org/drafts/current/。
互联网草案是有效期最长为六个月的草案文档,随时可能被更新、替换或被其他文档废弃。不适合将互联网草案作为参考材料或引用,除非作为“正在进行中的工作”。
本互联网草案将于 2018 年 7 月 23 日失效。
版权所有 (c) 2018 IETF 信托和被识别为文档作者的人员。保留所有权利。
本文件受 BCP 78 和 IETF 信托的 IETF 文档相关法律条款 (http://trustee.ietf.org/license-info) 的约束,这些条款在本文件发布之日生效。请仔细阅读这些文档,因为它们描述了您对本文件的权利和限制。从本文件摘录的代码组件必须包含简化 BSD 许可证文本,如信托法律条款第 4.e 节中所述,并且按简化 BSD 许可证中所述的方式提供,不提供任何担保。
JSON 超级模式是 JSON Schema 词汇,用于使用超链接和指令注释 JSON 文档,以通过超媒体环境(如 HTTP)处理和操纵远程 JSON 资源。
术语 JSON 超级模式用于指代使用这些关键字的 JSON Schema。术语“超级模式”本身指代本规范范围内的 JSON 超级模式。
引入的主要用于指定链接的机制是链接描述对象 (LDO),它是 RFC 8288,第 2 节 [RFC8288] 中定义的抽象链接模型的序列化。
本规范将使用 JSON Schema 核心 [json-schema] 和 JSON Schema 验证 [json-schema-validation] 规范中定义的概念、语法和术语。建议读者拥有这些规范的副本。
本文件中出现的“必须”、“禁止”、“必需”、“应”、“不应”、“建议”、“不建议”、“推荐”、“可”和“可选”等关键术语应按 RFC 2119 [RFC2119] 中的描述进行解释。
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 节 [RFC3986] 的基本 URI 为“https://api.example.com/”,那么“https://api.example.com/thing/1234”就是生成的链接的目标 URI。
术语“模式”、“实例”和“元模式”应按 JSON Schema 核心规范 [json-schema] 中的定义进行解释。
术语“适用”和“附加”应按 JSON Schema 验证规范第 3 节 [json-schema-validation] 中的定义进行解释。
术语“链接”、“链接上下文”(或“上下文”)、“链接目标”(或“目标”)和“目标属性”应按 RFC 8288 第 2 节 [RFC8288] 中的定义进行解释。
术语“用户代理”应按 RFC 7230 第 2.1 节 [RFC7230] 中的定义进行解释,并将其推广应用于超媒体系统中可能使用的任何协议,而不是专门作为 HTTP 客户端。
本规范定义了以下术语
JSON 超级模式实现能够接收超级模式、实例以及某些情况下的客户端输入,并生成一组完全解析的有效链接。如 RFC 8288,第 2 节 [RFC8288] 所定义,一个链接包含上下文、类型化关系、目标,以及可选的额外目标属性。
关系类型和目标属性直接取自每个链接的链接描述对象。上下文和目标标识符通过 URI 模板、实例数据以及(在目标标识符的情况下)客户端输入的组合构建。
目标始终由 URI 完整标识。由于缺少针对 application/json 和许多可与 JSON 超级模式一起使用的其他媒体类型的 URI 片段标识符语法,上下文可能仅由 URI 部分标识。在这种情况下,剩余的标识将作为 JSON 指针提供。
JSON 超级模式文档中对几种 IANA 注册的链接关系类型赋予了特定语义。“self” 链接用于与实例文档所代表的资源进行交互,而“collection” 和“item” 链接则标识可以假定集合特定语义的资源。
JSON 超级模式元模式的当前 URI 为 <https://json-schema.fullstack.org.cn/draft-07/hyper-schema#>.
链接描述格式 [ldo] 可在没有 JSON 模式的情况下使用,通过将规范链接描述模式引用为使用链接的数据结构的模式,即可声明该格式的使用。规范链接描述模式的 URI 为:<https://json-schema.fullstack.org.cn/draft-07/links#>.
JSON 超级模式实现可以自由地以任何格式提供输出。但是,在一致性测试套件中定义了一种特定格式,该格式也用于说明 “实现需求” [implementation] 中的要点,并展示由 示例 [examples] 生成的输出。建议实现能够以这种格式生成输出,以方便测试。描述推荐输出格式的 JSON 模式的 URI 为 <https://json-schema.fullstack.org.cn/draft-07/hyper-schema-output#>.
所有适用于实例中某个位置的模式中的超级模式关键字(如 JSON 模式验证规范第 3 节 [json-schema-validation] 所定义)都可用于该实例。
当多个子模式适用于给定的子实例时,所有“link”数组都必须合并成一个集合,顺序任意。结果集合中的每个对象都必须保留其自己的适用于“base”值列表,按解析顺序排列,来自同一个模式和任何父模式。
与所有 JSON 模式关键字一样,本节中描述的所有关键字都是可选的。最小的有效 JSON 超级模式是空对象。
如果存在,此关键字必须首先 解析为 URI 模板 [uriTemplating],然后必须解析为针对实例的当前 URI 基地址的 URI 引用。结果必须在处理包含“base”的子模式及其所有子模式时,设置为实例的新 URI 基地址。
解析“base”模板的过程可能不同,具体取决于它是针对与“anchor”一起使用而解析,还是针对与“href”一起使用而解析,有关详细说明,请参阅 URI 模板部分。
模式的“links”属性用于将链接描述对象与实例关联。此属性的值必须是一个数组,数组中的项目必须是链接描述对象,如下所定义。
链接描述对象 (LDO) 是 RFC 8288,第 2 节 [RFC8288] 中定义的抽象链接模型的序列化。如该文档所述,一个链接包含上下文、关系类型、目标,以及可选的目标属性。JSON 超级模式的 LDO 提供了所有这些,以及使用 JSON 模式描述在各种情况下使用链接时的输入的其他功能。
由于使用 URI 模板来标识链接上下文和目标,以及在标识目标时可选地进一步使用客户端输入,因此 LDO 是一个链接模板,在与 JSON 实例文档一起使用时,可能会解析为多个链接。
LDO 的特定用法,通常涉及跨协议的请求和响应,被称为操作。对于许多协议,任何给定链接都可能执行多个操作。协议由目标的 URI 方案指示。请注意,并非所有 URI 方案都表示可用于通信的协议,即使具有表示此类协议的 URI 方案的资源,也不一定需要通过该协议提供。
链接描述对象必须是一个对象,并且必须存在 “href” [href] 和 “rel” [rel] 属性。每个关键字在本节中简要介绍,并在本文档后面的部分提供额外的用法说明和全面示例。
在 JSON 超级模式中,链接的上下文资源默认情况下是与其关联的子实例(如 JSON 模式验证规范第 3 节 [json-schema-validation] 所定义)。这通常不是整个实例文档。可以使用本节中的关键字更改此默认上下文。
根据实例的媒体类型,可能无法或无法将 URI 分配给确切的默认上下文资源。特别是 application/json 没有定义 URI 片段解析语法,因此普通 JSON 文档中的属性或数组元素无法由 URI 完全标识。如果无法生成完整的 URI,则应通过实例文档的 URI 以及单独的纯字符串 JSON 指针来传达上下文的 position。
实现必须能够构建链接上下文的 URI,以及(如果需要完全标识)按 RFC 6901,第 5 节 [RFC6901] 的字符串表示形式的 JSON 指针,以代替 URI 片段。在 URI 模板 [uriTemplating] 部分中给出了根据 URI 模板构建 URI 的过程。
此属性设置链接的上下文 URI。该属性的值为 URI 模板 [RFC6570],并且必须针对实例的基 URI 解析得到的 URI 引用 [RFC3986]。
URI 是使用与 “href” [href] 属性所述相同的过程,从提供的 URI 模板计算得出的,区别是 “hrefSchema” [hrefSchema] 必须不存在。与目标 URI 不同,上下文 URI 不接受用户输入。
此属性更改实例中被视为链接上下文资源的点。该属性的值必须是一个有效的 JSON 指针(以 JSON 字符串表示形式),或是一个有效的 相对 JSON 指针 [relative-json-pointer],它相对于默认上下文进行评估。
虽然可以使用 “anchor” [anchor] 关键字设置具有已知 URI 的备用上下文,但由于缺少针对 application/json 的片段标识符语法,因此通常无法使用 URI 在 JSON 实例中更改上下文。
即使在定义 JSON 指针作为片段标识符语法的“+json”媒体类型中,如果默认上下文嵌套在数组中,也不可能获得默认上下文的 position 在该数组中的索引,以构建指向同一嵌套 JSON 对象中另一个属性的指针。这将在示例中演示。
处理此关键字的结果应为 URI 片段(如果实例的媒体类型允许此类片段)。否则,它必须是一个字符串编码的 JSON 指针。
链接的关系类型标识其语义。它是传达应用程序如何与资源交互的主要方式。
关系定义通常不依赖于媒体类型,鼓励用户利用最合适的现有已接受关系定义。
此属性的值必须是一个字符串,并且必须是 RFC 8288,第 2.1 节中定义的单个链接关系类型。
此属性是必需的。
如 RFC 4287,第 4.2.7.2 节 [RFC4287] 最初定义,一个“self”链接表示目标 URI 标识与链接上下文等效的资源。在 JSON 超级模式中,“self”链接必须可以从实例解析,因此“hrefSchema”必须不存在。
超级模式作者应使用“templateRequired”来确保“self”链接具有使用所需的所有实例数据。
超级模式实现必须认识到,具有“self”关系类型的链接(其上下文为整个当前实例文档)描述了用户代理如何与该实例文档所代表的资源进行交互。
RFC 6573 [RFC6573] 定义并注册了“item”和“collection”链接关系类型。JSON 超级模式对这些类型所指示的集合资源施加了额外的语义。
实现必须将“collection”链接的目标和“item”链接的上下文识别为集合。
超媒体中一个众所周知的模式是使用集合资源来创建集合成员并为其分配服务器端 URI。如果 URI 方案指示的协议定义了适用于创建具有服务器端 URI 的资源的特定方法,那么由这些链接关系类型标识的集合资源,**必须**不定义与创建集合成员的语义冲突的方法的语义。集合资源**可以**通过这种协议方法实现项目创建,用户代理**可以**假定任何此类操作(如果存在)具有项目创建语义。
由于这种方法将对应于 JSON 超级模式的数据提交概念,因此链接的"submissionSchema" [submissionSchema]字段**应该**与集合项表示的模式兼容,如“item”链接的目标资源或“collection”链接的上下文资源的“self”链接所示。
当没有注册的关系(除了“related”)适用时,鼓励用户根据RFC 8288 的第 2.1.2 节 [RFC8288]的描述,自己铸造扩展关系类型。选择链接关系类型 URI 最简单的方法是使用已用于标识系统主要资源的 URI 方案,或使用人可读的、不可解析的 URI 方案,例如RFC 4151 定义的“tag” [RFC4151]。
即使使用允许解析的方案,扩展关系类型 URI 也不必可解析。
目标 URI 模板用于标识链接的目标,可能利用实例数据。此外,使用"hrefSchema" [hrefSchema],此模板可以标识一组基于客户端输入的可使用目标资源。使用或不使用客户端输入解析 URI 模板的完整过程在URI 模板化 [uriTemplating]部分中介绍。
"href" 链接描述属性的值是一个模板,用于确定相关资源的目标 URI。实例属性的值**必须**解析为相对于实例的基 URI 的URI 引用 [RFC3986]。
此属性是**必需的**。
本节中的关键字用于解析涉及超模式的所有 URI 模板:“base”、“anchor”和“href”。有关完整的模板解析算法,请参阅URI 模板化 [uriTemplating]部分。
请注意,解析“base”模板时,解析开始的附加点是解析需要“base”模板的“href”或“anchor”关键字的附加点,而不是“base”关键字本身的附加点。
"templatePointers" 链接描述属性的值**必须**是一个对象。对象中的每个属性值**必须**是一个有效的JSON 指针 [RFC6901],或一个有效的相对 JSON 指针 [relative-json-pointer],该指针相对于为其解析模板的链接的附加点进行评估。
对于与正在解析的模板中的变量名称匹配的对象中的每个属性名称,该属性的值会调整该变量的变量解析起始位置。与正在解析的模板中的模板变量名称不匹配的属性**必须**被忽略。
此关键字的值**必须**是一个数组,并且元素**必须**是唯一的。每个元素**应该**匹配链接的 URI 模板中的变量,不进行百分比编码。完成整个 URI 模板解析过程后,如果此数组中存在的任何变量没有值,则**必须**不使用该链接。
本节中的所有属性仅供参考。虽然诸如“title”和“description”之类的关键字主要用于向用户展示链接,但那些预测链接交互或响应性质的关键字**必须**不被认为是权威的。无论何时与 LDO 中的目标属性发生冲突,都**必须**尊重目标资源的运行时行为。
此属性定义了链接的标题。该值**必须**是一个字符串。
用户代理**可以**在向用户呈现链接时使用此标题。
此属性提供了超出标题中内容的更多信息。该值**必须**是一个字符串。虽然标题最好简短,但描述可以用于更详细地说明链接的目的和用法。
用户代理**可以**在向用户呈现链接时使用此描述。
此属性的值表示预期在获取此资源时返回的媒体类型RFC 2046 [RFC2046]。此属性值**可以**是媒体范围,使用与RFC 7231 第 5.3.2 节 - HTTP “Accept” 标头 [RFC7231]中定义的相同模式。
此属性类似于其他链接序列化格式的“type”属性。用户代理**可以**使用此信息在跟踪链接之前通知他们向用户呈现的界面,但**必须**不使用此信息来解释结果数据。相反,用户代理**必须**使用响应提供的媒体类型进行运行时解释。有关“targetMediaType”滥用的详细说明,请参阅有关“安全问题” [security]的部分。
对于支持内容协商的协议,实现**可以**选择使用"headerSchema" [headerSchema]中的协议特定信息来描述可能的 targetMediaType。如果协议特定信息和“targetMediaType”都存在,则“targetMediaType”的值**必须**与协议特定信息兼容,并且**应该**指示在没有内容协商的情况下返回的媒体类型。
当没有此类协议特定信息可用,或者当实现无法识别所涉及的协议时,则该值**应该**被认为是“application/json”。
此属性提供了一个预期用于描述链接目标表示的模式。根据协议,模式可能描述也可能不描述对使用链接执行的任何特定操作的请求或响应。有关此关键字如何与 HTTP 一起使用的深入讨论,请参阅JSON 超级模式和 HTTP [HTTP]部分。
此属性的值仅供参考。它代表预期通过与目标资源交互发现的信息,通常以协议特定的控制信息或元数据的形式出现,例如在响应 HTTP HEAD 或 OPTIONS 请求时返回的标头。该协议由“href”URI 方案确定,但请注意,资源不能保证通过此类协议访问。
此属性的值**应该**是一个对象。此对象中的键**应该**是控制数据字段名称的小写形式。每个值**应该**是一个数组,以便统一处理多值字段。多个值**必须**作为数组呈现,而不是作为单个字符串呈现。
不适合以 JSON 对象表示的控制信息的协议**可以**由其他数据类型(例如数组)表示。
无法理解为指示协议的一部分的值**必须**被 JSON 超级模式实现忽略。应用程序**可以**使用此类值,但**必须**不假定与其他实现的互操作性。
实现**必须**不假定此对象中已考虑所有可发现的信息。客户端应用程序**必须**适当地处理与此属性值的矛盾的运行时响应。
客户端应用程序**必须**不假定实现将基于此属性的值自动采取任何操作。
有关使用此关键字与 HTTP 和类似协议的指南,请参阅“JSON 超级模式和 HTTP” [HTTP]。
有四种方法可以使用客户端输入与链接一起使用,每种方法都由单独的链接描述对象关键字解决。执行操作时,用户代理**应该**忽略与他们的语义无关的模式。
"hrefSchema" 链接描述属性的值**必须**是一个有效的 JSON 模式。此模式用于验证用户输入或其他用户代理数据,以填充“href” [href]中的 URI 模板。
省略“hrefSchema”或将整个模式设置为“false”将阻止任何用户代理数据被接受。
将应用于特定变量的任何子模式设置为 JSON 文字值“false”将阻止接受该单个变量的任何用户代理数据。
对于可以从实例数据中解析的模板变量,如果实例数据相对于“hrefSchema”中的所有适用子模式有效,则**必须**使用它来预填充该变量的输入数据集。
请注意,即使数据是从实例中预填充的,“hrefSchema”中该变量的验证模式也不必与应用于实例数据位置的验证模式相同。这允许针对用户代理数据使用不同的验证规则,例如支持日期时间输入的拼写月份,但使用标准日期时间格式进行存储。
接受输入后,可能会覆盖预填充的实例数据,结果数据集**必须**成功通过“hrefSchema”的值验证。如果它没有通过验证,则**必须**不使用该链接。如果它有效,则“URI 模板化”部分中给出的过程将继续使用此更新的数据集。
[CREF2]与“targetHints”一样,此关键字有些未定义,以鼓励尝试和反馈,因为我们试图在灵活性与清晰度之间取得平衡。
如果存在,此属性是协议特定请求标头或类似控制和元数据的模式。此对象的价值**必须**是一个有效的 JSON 模式。该协议由“href”URI 方案确定,但请注意,资源不能保证通过此类协议访问。该模式仅供参考;目标资源的行为不受其存在的影响。
此关键词的目的是宣传目标资源交互功能,并向用户代理和客户端应用程序指示哪些头文件和头文件值可能有用。用户代理和客户端应用程序**可以**使用该模式验证相关头文件,但**不得**假设缺少头文件或值被禁止使用。虽然模式作者**可以**将“additionalProperties”设置为 false,但这**不建议**,**不得**阻止客户端应用程序或用户代理在发出请求时提供其他头文件。
JSON 数据模型到头文件的精确映射取决于协议。但是,在大多数情况下,此模式**应该**指定类型为“object”,属性名称**应该**为控制数据字段名称的小写形式。有关将此关键字与 HTTP 和类似协议一起使用的详细指南,请参阅“JSON 超级模式和 HTTP” [HTTP] 部分。
“headerSchema”适用于协议支持的任何请求方法或命令。在生成请求时,用户代理和客户端应用程序**应该**忽略与该请求无关的头文件的模式。
在 JSON 超级模式中,“targetSchema” [targetSchema] 提供了对目标资源表示的非权威性描述。客户端应用程序可以使用“targetSchema”来构建用于替换或修改表示的输入,或者作为基于修补程序媒体类型的修补程序文档的基准表示。
或者,如果“targetSchema”不存在,或者客户端应用程序更喜欢只使用权威性信息,则它可以与目标资源交互以确认或发现其表示结构。
“targetSchema”并非旨在描述链接操作响应,除非响应语义表明它是目标资源的表示。在所有情况下,响应本身指示的模式都是权威性的。有关详细示例,请参阅“JSON 超级模式和 HTTP” [HTTP]。
该“submissionSchema” [submissionSchema] 和 “submissionMediaType” [submissionMediaType] 关键词描述了目标资源实现的处理函数的域。否则,如上所述,对于不相关的操作,提交模式和媒体类型将被忽略。
如果存在,此属性指示客户端应用程序和用户代理应为“submissionSchema” [submissionSchema] 描述的请求有效负载使用的媒体类型格式。
省略此关键词的行为与值 application/json 相同。
请注意,“submissionMediaType”和“submissionSchema”不受限于 HTTP URI。 [CREF3]此语句可能会移至示例结束的位置。
此属性包含一个模式,该模式定义了根据“submissionMediaType”属性编码并发送到目标资源以进行处理的文档的可接受结构。这可以被视为描述目标资源实现的处理函数的域。
这是一个与“targetSchema” [targetSchema] 属性不同的概念,“targetSchema” [targetSchema] 属性描述了目标信息资源(包括在 PUT 请求中替换资源内容),而“submissionSchema”描述了用户提交的请求数据,该数据将由资源进行评估。“submissionSchema”旨在与有效负载不一定要根据目标表示定义的请求一起使用。
省略“submissionSchema”的行为与值“true”相同。
在高级别上,符合标准的实现将满足以下要求。这些要求中的每一个都将在各个关键字部分和关键字组概述中详细介绍。
请注意,有关实现如何**必须**识别“self”、“collection”和“item”链接的要求在链接关系类型 [relationType] 部分中已详细介绍,此处不再重复。
虽然它不是实现的强制格式,但测试套件中使用的输出格式总结了在使用每个链接之前需要为每个链接计算的内容
在生成测试套件输出时,不参与生成上述信息的其他 LDO 关键词将按原样包含。除非特别相关,否则此处不会进一步讨论这些字段。
在使用链接之前,必须通过将超级模式应用于实例并找到所有适用的有效链接来发现它们。请注意,除了收集有效链接之外,还必须找到任何用于解析每个 LDO 的 URI 模板所需的“base” [base] 值,并通过对实现的 URI 模板解析过程最有效的方式将其与 LDO 关联起来。
And 实现**必须**支持通过其附件指针或上下文指针查找链接,方法是执行查找或提供已确定这两个指针的所有链接集,以便用户代理可以自行实现查找。
当通过上下文指针执行查找时,附加到同一数组中的元素的链接**必须**以与附加它们的数组元素相同的顺序返回。
三个超级模式关键词是URI 模板 [RFC6570]:“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: temlateData[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”,对于所有可能的请求都是相同的。
不携带正文负载的请求不需要额外的关键字支持。
以目标表示形式作为负载的请求应使用“targetSchema”和“targetMediaType”关键字进行输入描述和负载验证。如果协议允许使用基于表示形式的负载进行操作,该表示形式已由媒体类型(例如修补程序媒体类型)修改,则应通过“targetHints”以协议特定方式指示此类媒体类型。
以不源自目标资源表示形式的负载进行的请求应使用“submissionSchema”和“submissionMediaType”关键字进行输入描述和负载验证。超媒体中使用的协议通常每个链接只支持一个这样的非表示操作。
通过单个超媒体协议操作将许多应用程序操作与任意不同的请求结构进行管道传输的 RPC 系统超出了 JSON 超模式等超媒体格式的范围。
作为超媒体格式,JSON 超模式侧重于描述资源,包括以足够的细节描述其链接,以进行所有有效的请求。它不关注直接描述这些请求的所有可能的响应。
与任何超媒体系统一样,响应预计是自描述的。在超模式的上下文中,这意味着每个响应必须链接其自己的超模式。虽然由目标资源表示组成的响应预计针对“targetSchema”和“targetMediaType”有效,但这些关键字仅供参考,如果与响应本身矛盾,则必须忽略。
其他响应,包括错误响应、复杂重定向和处理状态表示,也应该链接到它们自己的模式并使用适当的媒体类型(例如 "application/problem+json" [RFC7807] 用于错误)。某些错误可能不会链接模式,因为它们是由不知道超模式的中介生成的,而不是由来源生成的。
预计用户代理能够理解协议状态代码和响应媒体类型,足以处理常见情况,并向客户端应用程序提供足够的信息来处理特定于域的响应。
在设计时静态映射所有可能的响应及其模式超出了 JSON 超模式的范围,但可能在其他基于超模式的 JSON 模式词汇表的范围内(见 附录 A.3)。
基于其上下文发现链接或使用链接的上下文识别集合的要求在与流式解析器一起使用时提出了独特的挑战。在没有处理整个模式和实例文档的情况下,不可能权威地满足这些要求。
此类实现可以选择基于迄今为止处理的数据返回非权威答案。在提供此方法时,实现必须明确说明响应的性质,并且必须提供一个选项来阻塞并等待直到所有数据都被处理并且可以返回权威答案。
虽然 JSON 超模式是一种超媒体格式,因此与协议无关,但预计其最常见的用途将在 HTTP 系统或使用与 HTTP 明确类似的协议(如 CoAP)的系统中。
本节提供有关如何使用每个常见的 HTTP 方法与链接以及集合资源如何对 HTTP POST 强加额外约束的指导。此外,还提供了关于提示 HTTP 响应标头值和描述可能与给定资源相关的 HTTP 请求标头的指导。
JSON 模式核心规范的第 11 节 [json-schema] 提供了有关在超媒体系统中将实例链接到其模式的指导。这可以通过网络可访问的模式来完成,或者可以简单地识别预先打包在客户端应用程序中的模式。JSON 超模式有意不限制这种机制,尽管建议尽可能使用核心规范中概述的技术。
链接描述对象不会直接指示目标资源支持哪些操作,例如 HTTP 方法。相反,操作应主要从链接 关系类型 [rel] 和 URI 方案推断。
这意味着对于每个目标资源和链接关系类型对,模式作者应仅定义单个 LDO。虽然可以使用“allow”与“targetHints”一起重复一个关系类型和目标对,并标记为允许的不同 HTTP 方法,但这是不建议的,并且可能不被符合标准的实现良好支持。
在本节中解释,所有使用每个 HTTP 方法所需的信息都可以包含在单个 LDO 中。“targetHints”中的“allow”字段仅用于提示支持哪些操作,而不是单独定义每个操作。
但是请注意,资源可能始终在运行时拒绝操作,例如由于授权失败,或者由于其他控制操作可用性的应用程序状态。
"targetSchema" 描述链接目标端上的资源,而“targetMediaType”定义该资源的媒体类型。对于 HTTP 链接,“headerSchema”还可以用于描述用于“Accept”请求标头的有效值,该标头可以支持多种媒体类型或媒体范围。当两种指示目标媒体类型的方式都存在时,“targetMediaType”应指示默认表示媒体类型,而“headerSchema”中“accept”的模式应包括默认媒体类型以及可以请求的任何替代媒体类型或媒体范围。
由于许多 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” [RFC7231] 定义。
根据 RFC 5789 [RFC5789],HTTP PATCH 的请求结构由“targetSchema”和请求媒体类型的组合确定,该请求媒体类型由“Accept-Patch”标头传达,该标头可能包含在“targetHints”中。适合修补的媒体类型定义了用于表达对文档更改的语法,可以将该语法应用于“targetSchema”描述的表示形式,以确定语法上有效的请求负载集。通常,验证 PATCH 请求最简单的方法是应用它并将结果验证为正常的表示形式。
JSON 超模式允许资源处理任意数据,除了或代替使用目标的表示形式。此任意数据由“submissionSchema”和“submissionMediaType”关键字描述。在 HTTP 的情况下,POST 方法是唯一处理此类数据的 POST 方法。虽然在使用 POST 与集合方面存在某些约定,但 POST 请求的语义是由目标资源定义的,而不是 HTTP 定义的。
除了与协议无关的“submission*”关键字(参见 第 9.3 节 了解非 HTTP 示例)之外,“Accept-Post”标头还可用于指定必要的媒体类型,并且可能通过“targetHints”字段进行广告。 [CREF4]如果两者都被使用会发生什么?此外,“submissionSchema”是必须支持的,而“targetHints”最多是 SHOULD。但是禁止在“targetHints”中使用“Accept-Post”似乎不正确。
除了 201 或带有“Content-Location”设置的 200 之外的 POST 的成功响应同样没有 HTTP 定义的语义。与所有 HTTP 响应一样,响应中的任何表示形式都应链接到其自己的超模式,以指示如何处理它。如 附录 A.2 所述,将超链接与所有可能的运算响应连接起来不在 JSON 超模式的范围内。
HTTP 响应标头信息的 JSON 序列化应遵循正在进行的工作 "HTTP 标头字段值的 JSON 编码" [I-D.reschke-http-jfv] 中建立的指南。应尽可能将该文档示例中所示的方法应用于其他类似结构的标头。
所有可能的 HTTP 方法响应的标头都共享“headerSchema”。特别是,出现在 HEAD 响应中的标头和出现在 OPTIONS 响应中的标头都可以出现。在“headerSchema”中没有区分哪个方法响应包含哪个标头。
建议模式作者在适用时为以下类型的 HTTP 标头的值提供提示
一般来说,在不同时间可能具有不同值的标头不应包含在“targetHints”中。
应编写模式来描述遵循正在进行的工作中建立的准则的 JSON 序列化 "HTTP 标头字段值的 JSON 编码" [I-D.reschke-http-jfv] 该文档示例中显示的方法应尽可能应用于其他结构相似的标头。
建议模式作者在适用时描述以下类型的 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 用法的示例可在 附录 [HTTP] 中找到。
对于此示例,我们将假设一个具有记录的入口点 URI https://example.com 的示例 API,它是一个带有指向模式链接的空 JSON 对象。在这里,入口点本身没有数据,并且仅存在于提供一组初始链接
GET https://api.example.com HTTP/1.1 200 OK Content-Type: application/json Link: <https://schema.example.com/entry> rel=describedBy {}
链接的超级模式定义了 API 的基本 URI,并提供了两个链接:指向 API 文档的“about”链接,以及指示这是基本 URI 的模式的“self”链接。在这种情况下,基本 URI 也是入口点 URI。
{ "$id": "https://schema.example.com/entry", "$schema": "https://json-schema.fullstack.org.cn/draft-07/hyper-schema#", "base": "https://api.example.com/", "links": [ { "rel": "self", "href": "" }, { "rel": "about", "href": "/docs" } ] }
这些是最简单的链接,只有关系类型和“href”,没有模板变量。它们解析如下
[ { "contextUri": "https://api.example.com", "contextPointer": "", "rel": "self", "targetUri": "https://api.example.com", "attachmentPointer": "" }, { "contextUri": "https://api.example.com", "contextPointer": "", "rel": "about", "targetUri": "https://api.example.com/docs", "attachmentPointer": "" } ]
附件指针是根指针(对于实例为空对象,这是唯一可能的情况)。上下文 URI 是默认值,即请求的文档。由于 application/json 不允许片段,因此上下文指针对于完整描述上下文是必要的。它的默认行为是与附件指针相同。
让我们将“事物”添加到我们的系统中,从单个事物开始
{ "$id": "https://schema.example.com/thing", "$schema": "https://json-schema.fullstack.org.cn/draft-07/hyper-schema#", "base": "https://api.example.com/", "type": "object", "required": ["data"], "properties": { "id": {"$ref": "#/definitions/id"}, "data": true }, "links": [ { "rel": "self", "href": "things/{id}", "templateRequired": ["id"], "targetSchema": {"$ref": "#"} } ], "definitions": { "id": { "type": "integer", "minimum": 1, "readOnly": true } } }
我们的“事物”有一个服务器分配的 ID,这是构建“self”链接所必需的。它还有一个“data”字段,可以是任何类型。在下一个示例中,我们将说明“definitions”部分的原因。
请注意,“id”不是验证模式所必需的,但它是“self”链接所必需的。这很有道理:只有在“事物”被创建并且服务器分配了 ID 后,它才具有 URI。但是,您可以将此模式与仅包含 data 字段的实例一起使用,这允许您验证您即将创建的“事物”实例。
让我们在入口点模式中添加一个链接,以便您可以直接跳转到特定事物,前提是您能够提供它的 ID 作为输入。为了节省空间,只显示新的 LDO。与“self”和“about”不同,关于假设事物的 IANA 注册关系,因此使用 "tag:" URI 方案 [RFC4151] 定义扩展关系
{ "rel": "tag:rel.example.com,2017:thing", "href": "things/{id}", "hrefSchema": { "required": ["id"], "properties": { "id": {"$ref": "thing#/definitions/id"} } }, "targetSchema": {"$ref": "thing#"} }
此处的“href”值相同,但其他所有内容都不同。回想一下,实例是一个空对象,因此无法从实例数据中解析“id”。相反,它需要作为客户端输入。此 LDO 也可以使用“templateRequired”,但使用“hrefSchema”中的“required”,它不是严格必要的。在“hrefSchema”中未将“id”标记为必需的情况下提供“templateRequired”会导致错误,因为客户端输入是解析此链接的唯一可能来源。
此示例涵盖了使用“submission”字段进行非表示输入,以及将它们与使用输入解析 URI 模板一起使用。与需要构建 URI 或发送有效负载,但不允许同时进行两者操作的 HTML 表单不同,JSON 超级模式可以描述对相同链接上的相同操作的两种类型的输入。
“submissionSchema”和“submissionMediaType”字段用于描述不是目标资源表示的有效负载。当与“http(s)://" URI 一起使用时,它们通常指的是 POST 请求有效负载,如 HTTP 用法附录 [HTTP] 中所见。
在本例中,我们使用“mailto:” URI,它根据 RFC 6068,第 3 节 [RFC6068],不提供任何用于检索资源的操作。它只能用于构建发送消息。由于没有可检索、可替换或可删除的目标资源的概念,“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-07/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://api.example.com/stuff”检索到的以下实例
{ "title": "The Awesome Thing", "stuffWorthEmailingAbout": "Lots of text here...", "email": "[email protected]" }
我们可以在向客户端应用程序请求输入之前,部分解析链接,如下所示。
{ "contextUri": "https://api.example.com/stuff", "contextPointer": "", "rel": "author", "hrefInputTemplates": [ "mailto:[email protected]?subject={title}{&cc}" ], "hrefPrepopulatedInput": { "title": "The Really 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://api.example.com/trees/1/nodes/123 HTTP/1.1 200 OK Content-Type: application/json Link: <https://api.example.com/trees/1/nodes/123> rel=self Link: <https://api.example.com/trees/1/nodes/123> rel=up anchor=<https://api.example.com/trees/1/nodes/456> Link: <https://api.example.com/trees/1/nodes/456> rev=up { "id": 123, "treeId": 1, "childIds": [456] }
请注意,“rel=up”链接具有与“rel=self”链接相同的目标 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-07/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-07/hyper-schema#", "base": "https://api.example.com/", "type": "object", "required": ["data"], "properties": { "id": {"$ref": "#/definitions/id"}, "data": true }, "links": [ { "rel": "self", "href": "things/{id}", "templateRequired": ["id"], "targetSchema": {"$ref": "#"} }, { "rel": "collection", "href": "/things", "targetSchema": {"$ref": "thing-collection#"}, "submissionSchema": {"$ref": "#"} } ], "definitions": { "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-07/hyper-schema#", "base": "https://api.example.com/", "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://api.example.com/things", "contextPointer": "", "rel": "self", "targetUri": "https://api.example.com/things", "attachmentPointer": "" }, { "contextUri": "https://api.example.com/things", "contextPointer": "/elements/0", "rel": "self", "targetUri": "https://api.example.com/things/12345", "attachmentPointer": "/elements/0" }, { "contextUri": "https://api.example.com/things", "contextPointer": "/elements/1", "rel": "self", "targetUri": "https://api.example.com/things/67890", "attachmentPointer": "/elements/1" }, { "contextUri": "https://api.example.com/things", "contextPointer": "", "rel": "item", "targetUri": "https://api.example.com/things/12345", "attachmentPointer": "/elements/0" }, { "contextUri": "https://api.example.com/things", "contextPointer": "", "rel": "item", "targetUri": "https://api.example.com/things/67890", "attachmentPointer": "/elements/1" }, { "contextUri": "https://api.example.com/things", "contextPointer": "/elements/0", "rel": "collection", "targetUri": "https://api.example.com/things", "attachmentPointer": "/elements/0" }, { "contextUri": "https://api.example.com/things", "contextPointer": "/elements/1", "rel": "collection", "targetUri": "https://api.example.com/things", "attachmentPointer": "/elements/1" } ]
在所有情况下,都显示了媒体类型 application/json 的实例的上下文 URI,它不支持片段。 如果实例媒体类型为 application/instance+json,它支持 JSON 指针片段,则上下文 URI 将包含与上下文指针字段相同的片段。 对于 application/json 和没有片段的其他媒体类型,考虑上下文指针和上下文 URI 非常重要。
有三个“self”链接,一个用于集合,一个用于“elements”数组中的每个项目。 项目“self”链接是在单个“thing”方案中定义的,该方案用“$ref”引用。 这三个链接可以通过它们的上下文或附件指针来区分。 我们将在 第 9.5.2 节 中重新讨论集合的“self”链接的“submissionSchema”。
有两个“item”链接,一个用于“elements”数组中的每个项目。 与“self”链接不同,这些链接只在集合方案中定义。 它们中的每一个都与具有相同附件指针的相应“self”链接具有相同的目标 URI。 但是,每个链接都有不同的上下文指针。 “self”链接的上下文是“elements”中的条目,而“item”链接的上下文始终是整个集合,无论具体项目是什么。
最后,有两个“collection”链接,一个用于“elements”中的每个项目。 在单个项目方案中,这些链接会生成以项目资源作为上下文的链接。 当从集合方案中引用时,上下文是“elements”数组中相关“thing”的位置,而不是该“thing”自己的独立资源 URI。
集合链接的目标 URI 相同,因为只有一个相关的集合 URI。 虽然在完整的已构建链接集中计算这两个链接可能看起来没有用,但在按需构建链接时,这种安排意味着无论您正在处理哪个“elements”条目,都存在一个触手可及的“collection”链接定义。
这里我们在集合中添加了分页。 有一个“meta”部分来保存有关当前页、下一页和上一页的信息。 大多数方案与上一节中的方案相同,已被省略。 仅显示完整的新字段和新的或(对于主“self”链接)已更改的链接。
{ "properties": { "elements": { ... }, "meta": { "type": "object", "properties": { "prev": {"$ref": "#/definitions/pagination"}, "current": {"$ref": "#/definitions/pagination"}, "next": {"$ref": "#/definitions/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": "#"} } ], "definitions": { "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://api.example.com/things", "contextPointer": "", "rel": "self", "targetUri": "https://api.example.com/things?offset=20,limit=2", "attachmentPointer": "" }, { "contextUri": "https://api.example.com/things", "contextPointer": "", "rel": "next", "targetUri": "https://api.example.com/things?offset=22,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#/definitions/pagination" }, "submissionSchema": { "$ref": "thing#" }, "targetSchema": { "$ref": "thing-collection#" } }
现在我们看到分页参数被接受为输入,因此我们可以跳转到集合中的任何页面。 链接关系类型是一个自定义类型,因为通用“collection”链接只能与项目作为其上下文一起使用,而不能与入口点或其他资源一起使用。
当我们没有任何“thing”时,我们就没有任何具有相关“collection”链接的资源。 因此,我们无法使用“collection”链接的提交关键字来创建第一个“thing”; 超级方案必须相对于实例进行评估。 由于集合实例中的“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 Linking [RFC8288] 的安全注意事项也适用,并进行了适当的调整(例如,将“anchor”作为 LDO 关键字,而不是作为 HTTP 链接标头属性)。
如 第 6.5 节 中所述,描述目标资源的所有 LDO 关键字均为建议性的,**不得** 用作对目标资源响应操作时提供的权威信息的替代。 目标资源响应 **应该** 指示它们自己的超级方案,这是权威性的。
如果目标响应中的超级方案与找到当前 LDO 的超级方案(按“$id”)匹配,则目标属性 **可以** 被视为权威性的。 [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 方案的初始草案所做的工作。
感谢 Jason Desrosiers、Daniel Perrett、Erik Wilde、Ben Hutton、Evgeny Poberezkin、Brad Bowman、Gowry Sankar、Donald Pipowitch、Dave Finlay 和 Denis Laxalde 为文档提交的内容和补丁。
[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 月。 |
[relative-json-pointer] | Luff, G. 和 H. Andrews,"相对 JSON 指针",互联网草案 draft-handrews-relative-json-pointer-01,2018 年 1 月。 |
[json-schema] | Wright, A. 和 H. Andrews,"JSON Schema:用于描述 JSON 文档的媒体类型",互联网草案 draft-handrews-json-schema-00,2017 年 11 月。 |
[json-schema-validation] | Wright, A.,Andrews, H. 和 G. Luff,"JSON Schema 验证:用于 JSON 结构验证的词汇表",互联网草案 draft-handrews-json-schema-validation-00,2017 年 11 月。 |
[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 月。 |
[I-D.reschke-http-jfv] | Reschke, J.,"HTTP 头字段值的 JSON 编码",互联网草案 draft-reschke-http-jfv-06,2017 年 6 月。 |
遵循 REST 架构风格约束的超媒体 API 允许创建通用用户代理。这种用户代理没有特定于应用程序的知识。相反,它理解预定义的媒体类型、URI 方案、协议和链接关系,通常是通过识别这些内容并协调对实现对其支持的现有软件的使用来实现的。然后,可以基于这种用户代理构建客户端应用程序,专注于它们自己的语义和逻辑,而不是交互机制。
超级模式一次只关心一个资源和一组关联的链接。就像 Web 浏览器一次只处理一个 HTML 页面一样,没有关于该页面如何作为“站点”的一部分运行的概念,同样,支持超级模式的用户代理一次处理一个资源,没有任何关于该资源如何融入 API 的概念。
因此,超级模式适合在 API 内使用,但不适合将 API 描述为它们本身的完整实体。无法描述 API 范围的概念,而不是资源和链接范围,并且这种描述超出了 JSON 超级模式的边界。
由于给定的 JSON 超级模式在单个时间点与单个资源一起使用,因此它没有关于版本控制的内在概念。但是,给定资源可以随时间更改其使用的模式或模式,并且这些模式的 URI 可用于指示版本信息。当与支持使用媒体类型参数指示模式的媒体类型一起使用时,这些版本化模式 URI 可用于内容协商。
资源可以表明它是多个模式的实例,这允许同时支持多个兼容版本。然后,客户端应用程序可以使用它识别的超级模式,并忽略更新或更旧的版本。
因为超级模式一次代表一个资源,所以它不提供对使用链接执行的协议操作的所有可能响应的枚举。每个响应(包括错误)都被视为其自己的(可能是匿名的)资源,并且应该识别其自己的超级模式,并可选地使用适当的媒体类型,例如 RFC 7807 的“application/problem+json” [RFC7807],以允许用户代理或客户端应用程序解释协议自身状态报告之外提供的任何信息。
可以在没有实例数据的情况下静态分析一组超级模式,以生成文档或代码等输出。但是,没有运行时实例数据,就无法访问验证和超级模式的全部功能集。
这是一个有意设计的选择,为超媒体系统提供最大的运行时灵活性。JSON Schema 作为一种媒体类型,允许为静态分析和内容生成建立额外的词汇表,本规范未对此进行处理。此外,各个系统可能会将它们的用法限制为可以在静态分析中分析的子集,如果完全设计时描述是一个目标的话。 [CREF12]已经提出了用于 API 文档和其他目的的词汇表,欢迎在 https://github.com/json-schema-org/json-schema-vocabularies 上贡献