GraphQL
当前工作草案
简介
这是 GraphQL 的规范,GraphQL 是一种查询语言和执行引擎,最初于 2012 年在 Facebook 创建,用于描述客户端-服务器应用程序的数据模型的功能和需求。这项开放标准的开发始于 2015 年。本规范于 2017 年根据 OWFa 1.0 获得许可。GraphQL 基金会于 2019 年成立,作为支持 GraphQL 生态系统的组织的中心点,GraphQL 规范项目也于 2019 年作为 Joint Development Foundation Projects, LLC, GraphQL Series 成立。
如果您的组织从 GraphQL 中受益,请考虑成为会员并帮助我们维持支持我们中立生态系统健康发展的活动。
GraphQL 规范项目已经发展,并可能在本规范的未来版本中继续发展。GraphQL 规范的先前版本可以在与其发布标签匹配的永久链接中找到。最新的工作草案版本可以在https://spec.graphql.net.cn/draft找到。
版权声明
版权所有 © 2015-2018, Facebook, Inc.
版权所有 © 2019-至今, GraphQL 贡献者
这些材料按“原样”提供。各方明确声明不承担任何保证(明示、暗示或其他),包括与材料相关的适销性、非侵权性、特定用途适用性或所有权的暗示保证。实施者和用户承担实施或以其他方式使用材料的全部风险。在任何情况下,各方均不对任何其他方因利润损失或任何形式的间接、特殊、附带或后果性损害承担责任,无论其性质如何,无论其因何种诉讼原因而起,无论其与本交付物或其管辖协议有关,无论是基于违约、侵权行为(包括疏忽)或其他原因,也无论其他成员是否已被告知发生此类损害的可能性。
许可
GraphQL 规范项目由Joint Development Foundation提供。工作组当前的章程,其中包括管辖所有工作组交付物(包括规范、源代码和数据集)的知识产权政策,可以在https://technical-charter.graphql.org找到。
目前,管辖 GraphQL 规范项目交付物的许可是
交付物 | 许可 |
---|---|
规范 | 开放网络基金会协议 1.0(专利和版权授权) |
源代码 | MIT 许可证 |
数据集 | CC0 1.0 |
一致性
GraphQL 的一致性实现必须满足所有规范性要求。一致性要求在本文件中通过描述性断言和具有明确定义含义的关键词来描述。
本文档规范性部分中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 IETF RFC 2119 中描述的方式解释。这些关键词可能以小写形式出现,但在未明确声明为非规范性时仍保留其含义。
GraphQL 的一致性实现可以提供额外的功能,但不允许在明确禁止或以其他方式导致不一致的情况下提供。
一致性算法
以祈使语法表达的算法步骤(例如,“返回调用解析器的结果”)应与包含它的算法具有相同的要求级别。在算法步骤中引用的任何算法(例如,“让 completedResult 成为调用 CompleteValue() 的结果”)应被解释为至少具有与包含该步骤的算法相同的要求级别。
以算法形式表达的一致性要求可以通过本规范的任何实现方式来满足,只要感知结果是等效的即可。本文档中描述的算法旨在易于理解。鼓励实施者包含等效但优化的实现。
有关算法定义和本文档中使用的其他符号约定的更多详细信息,请参见附录 A。
非规范性部分
本文档的所有内容都是规范性的,除非明确声明为非规范性的部分。
本文档中的示例是非规范性的,旨在帮助理解引入的概念和规范部分的行为。示例要么在散文中明确引入(例如“例如”),要么在示例或反例块中单独列出,如下所示
Example № 1This is an example of a non-normative example.
Counter Example № 2This is an example of a non-normative counter-example.
本文档中的注释是非规范性的,旨在阐明意图,引起对潜在的边缘情况和陷阱的注意,并回答实施过程中出现的常见问题。注释要么在散文中明确引入(例如“注意:”)要么在注释块中单独列出,如下所示
1概述
GraphQL 是一种查询语言,旨在通过提供直观且灵活的语法和系统来构建客户端应用程序,以描述其数据需求和交互。
例如,这个 GraphQL 请求将从 Facebook 的 GraphQL 实现中接收 id 为 4 的用户的名称。
Example № 3{
user(id: 4) {
name
}
}
这将产生结果数据(以 JSON 格式)
Example № 4{
"user": {
"name": "Mark Zuckerberg"
}
}
GraphQL 不是一种能够进行任意计算的编程语言,而是一种用于向应用程序服务发出请求的语言,这些应用程序服务的功能在本规范中定义。GraphQL 不强制要求实现它的应用程序服务使用特定的编程语言或存储系统。相反,应用程序服务采用其功能并将其映射到 GraphQL 编码的统一语言、类型系统和理念。这为产品开发提供了一个统一的界面,并为工具构建提供了一个强大的平台。
GraphQL 有许多设计原则
- 以产品为中心:GraphQL 毫不掩饰地由视图和编写视图的前端工程师的需求驱动。GraphQL 从他们的思维方式和需求开始,并构建必要的语言和运行时来实现这一点。
- 分层:当今大多数产品开发都涉及视图层次结构的创建和操作。为了与这些应用程序的结构保持一致,GraphQL 请求本身也是分层结构的。请求的形状与其响应中的数据一样。这是客户端描述数据需求的自然方式。
- 强类型:每个 GraphQL 服务都定义一个特定于应用程序的类型系统。请求在该类型系统的上下文中执行。给定一个 GraphQL 操作,工具可以确保它在执行之前(即在开发时)在语法上是正确的并且在该类型系统中是有效的,并且服务可以对响应的形状和性质做出某些保证。
- 客户端指定的响应:通过其类型系统,GraphQL 服务发布其客户端被允许消费的功能。客户端负责准确指定它将如何消费这些发布的功能。这些请求在字段级别粒度上指定。在大多数没有 GraphQL 的客户端-服务器应用程序中,服务决定从其各种端点返回的数据形状。另一方面,GraphQL 响应只包含客户端要求的内容,不多也不少。
- 内省:GraphQL 是内省的。GraphQL 服务的类型系统可以通过 GraphQL 语言本身进行查询,正如本规范中将描述的那样。GraphQL 内省为构建通用工具和客户端软件库提供了一个强大的平台。
由于这些原则,GraphQL 是一个强大且高效的环境,用于构建客户端应用程序。针对工作的 GraphQL 服务(在高质量工具的支持下)构建应用程序的产品开发人员和设计师可以快速变得高效,而无需阅读大量的文档,也几乎无需或根本无需正式培训。为了实现这种体验,必须有人构建这些服务和工具。
以下正式规范可作为这些构建者的参考。它描述了语言及其文法、类型系统和用于查询它的内省系统,以及执行和验证引擎及其算法。本规范的目标是为 GraphQL 工具、客户端库和服务实现的生态系统(跨越组织和平台)提供基础和框架,该生态系统尚待构建。我们期待与社区合作来实现这一目标。
2语言
客户端使用 GraphQL 查询语言向 GraphQL 服务发出请求。我们将这些请求源称为文档。文档可能包含操作(查询、变更和订阅)以及片段,片段是公共的组合单元,允许数据需求重用。
GraphQL 文档被定义为语法文法,其中终结符是记号(不可分割的词法单元)。这些记号在词法文法中定义,词法文法匹配源字符的模式。在本文档中,语法文法产生式用冒号 :
区分,而词法文法产生式用双冒号 ::
区分。
GraphQL 文档的源文本必须是 SourceCharacter 的序列。字符序列必须由 Token 和 Ignored 词法文法的序列描述。词法记号序列(省略 Ignored)必须由单个 Document 语法文法描述。
词法分析和语法解析
GraphQL 文档的源文本首先转换为词法记号 Token 和忽略的记号 Ignored 的序列。源文本从左到右扫描,重复获取词法文法产生式允许的下一个可能的代码点序列作为下一个记号。然后从左到右扫描这个词法记号序列,以根据 Document 语法文法生成抽象语法树 (AST)。
本文档中的词法文法产生式使用向前看限制来消除歧义并确保单个有效的词法分析。词法记号只有在其后没有跟随其向前看限制中的字符时才有效。
例如,IntValue 具有限制 Digit,因此不能后跟 Digit。因此,序列 123 不能表示记号 (12, 3),因为 12 后跟 Digit 3,因此必须只表示单个记号。在字符之间使用 WhiteSpace 或其他 Ignored 来表示多个记号。
2.1源文本
GraphQL 文档从源文本解释,源文本是 SourceCharacter 的序列,每个 SourceCharacter 都是一个 Unicode 标量值,它可以是 U+0000 到 U+D7FF 或 U+E000 到 U+10FFFF 的任何 Unicode 代码点(在本规范的大部分内容中非正式地称为“字符”)。
GraphQL 文档可能仅以 ASCII 范围表示,以便尽可能广泛地与尽可能多的现有工具、语言和序列化格式兼容,并避免文本编辑器和源代码管理中的显示问题。非 ASCII Unicode 标量值可能出现在 StringValue 和 Comment 中。
2.1.1空白字符
空白字符用于提高源文本的可读性,并充当记号之间的分隔符,并且任何数量的空白字符都可能出现在任何记号之前或之后。记号之间的空白字符对于 GraphQL 文档的语义含义并不重要,但是空白字符可能出现在 String 或 Comment 记号中。
2.1.2行终止符
与空白字符一样,行终止符用于提高源文本的可读性并分隔词法记号,任何数量的行终止符都可能出现在任何其他记号之前或之后,并且对 GraphQL 文档的语义含义没有意义。
2.1.3注释
GraphQL 源文档可能包含单行注释,以 # 标记开头。
注释可能包含除 LineTerminator 之外的任何 SourceCharacter,因此注释始终由以 # 字符开头的所有 SourceCharacter 组成,直到但不包括 LineTerminator(或源的结尾)。
注释是 Ignored 的,就像空白字符一样,并且可以出现在任何记号之后,或在 LineTerminator 之前,并且对 GraphQL 文档的语义含义没有意义。
2.1.4无关紧要的逗号
与空白字符和行终止符类似,逗号 (,) 用于提高源文本的可读性并分隔词法记号,但在 GraphQL 文档中,它们在语法和语义上都是无关紧要的。
非重要的逗号字符确保逗号的缺失或存在不会有意义地改变文档的解释语法,因为这可能是其他语言中常见的用户错误。它还允许风格上使用尾随逗号或行终止符作为列表分隔符,这两种方式通常都希望用于源代码的可读性和可维护性。
2.1.5词法记号
GraphQL 文档由几种不可分割的词法记号组成,这些记号在此处通过源 Unicode 字符的模式在词法文法中定义。词法记号可以由 Ignored 记号分隔。
记号稍后用作 GraphQL 语法文法规则中的终结符。
2.1.6忽略的记号
Ignored 记号用于提高可读性并在词法记号之间提供分隔,但在语法上是无关紧要的,并且在语法文法产生式中未被引用。
任何数量的 Ignored 都可能出现在每个词法记号之前和之后。源文档的任何忽略区域都不重要,但是出现在 Ignored 中的 SourceCharacter 也可能以重要的方式出现在词法 Token 中,例如 StringValue 可能包含空白字符。没有 Ignored 可以出现在 Token 内部,例如,在定义 FloatValue 的字符之间不允许有空白字符。
字节顺序标记
字节顺序标记是一个特殊的 Unicode 代码点,可能出现在文件开头,程序可以使用它来确定文本流是 Unicode,以及使用了哪种特定的编码。由于文件经常被连接,字节顺序标记可能出现在任何词法记号之前或之后,并且是 Ignored 的。
2.1.7标点符号
! | $ | & | ( | ) | ... | : | = | @ | [ | ] | { | | | } |
GraphQL 文档包含标点符号以描述结构。GraphQL 是一种数据描述语言,而不是一种编程语言,因此 GraphQL 缺少通常用于描述数学表达式的标点符号。
2.1.8名称
A | B | C | D | E | F | G | H | I | J | K | L | M |
N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
a | b | c | d | e | f | g | h | i | j | k | l | m |
n | o | p | q | r | s | t | u | v | w | x | y | z |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
GraphQL 文档充满了命名事物:操作、字段、参数、类型、指令、片段和变量。所有名称都必须遵循相同的语法形式。
GraphQL 中的名称区分大小写。也就是说,name
、Name
和 NAME
都指不同的名称。下划线很重要,这意味着 other_name
和 othername
是两个不同的名称。
Name 不能后跟 NameContinue。换句话说,Name 记号始终是最长的可能有效序列。源字符 a1 不能解释为两个记号,因为 a 后跟 NameContinue 1。
保留名称
2.2文档
GraphQL 文档描述了 GraphQL 服务或客户端操作的完整文件或请求字符串。文档包含多个定义,可以是可执行的,也可以代表 GraphQL 类型系统。
只有当文档是 ExecutableDocument 并且至少包含一个 OperationDefinition 时,GraphQL 服务才能执行文档。包含 TypeSystemDefinitionOrExtension 的文档不得执行;接收包含这些文档的 GraphQL 执行服务应返回描述性错误。
仅寻求执行 GraphQL 请求而不构建新的 GraphQL 模式的 GraphQL 服务可以选择仅允许 ExecutableDocument。
不包含 OperationDefinition 或包含 TypeSystemDefinitionOrExtension 的文档仍然可以被解析和验证,以允许客户端工具表示可能出现在许多单个文件中的许多 GraphQL 用法。
如果文档仅包含一个操作,则该操作可以是未命名的。如果该操作是没有变量或指令的查询,那么它也可以用简写形式表示,省略 query 关键字和操作名称。否则,如果 GraphQL 文档包含多个操作,则每个操作都必须命名。当向 GraphQL 服务提交包含多个操作的文档时,还必须提供要执行的所需操作的名称。
2.3操作
query | mutation | subscription |
GraphQL 模型有三种类型的操作
- query – 只读获取。
- mutation – 写入后跟获取。
- subscription – 一个长期存在的请求,它获取数据以响应一段时间内的一系列事件。
每个操作都由一个可选的操作名称和一个选择集表示。
例如,此 mutation 操作可能“点赞”一个故事,然后检索新的点赞数
Example № 5mutation {
likeStory(storyID: 12345) {
story {
likeCount
}
}
}
查询简写
如果一个文档仅包含一个操作,且该操作是一个查询,它没有定义变量,也没有应用任何指令,那么该操作可以用简写形式表示,其中省略了 query 关键字和操作名称。
例如,这个未命名的查询操作是通过查询简写编写的。
Example № 6{
field
}
2.4选择集
一个操作选择它需要的信息集,并将准确地接收该信息,不多也不少,从而避免过度获取和获取不足数据。
选择集 定义了针对对象、联合或接口类型的一组有序的选择(字段、片段展开和内联片段)。
Example № 7{
id
firstName
lastName
}
在这个查询操作中,id
、firstName
和 lastName
字段构成了一个 选择集。选择集也可以包含片段引用。
2.5字段
选择集 主要由字段组成。一个字段描述了在选择集中可请求的离散信息片段之一。
一些字段描述了复杂的数据或与其他数据的关系。为了进一步探索这些数据,一个字段本身可能包含一个选择集,从而允许深度嵌套的请求。所有 GraphQL 操作都必须将其选择细化到返回标量值的字段,以确保明确定义的响应形状。
例如,此操作选择了复杂数据和关系的字段,一直到标量值。
Example № 8{
me {
id
firstName
lastName
birthday {
month
day
}
friends {
name
}
}
}
操作的顶层 选择集 中的字段通常表示一些全局可访问的信息,供您的应用程序及其当前查看者使用。这些顶层字段的一些典型示例包括对当前登录查看器的引用,或访问由唯一标识符引用的某些类型的数据。
Example № 9# `me` could represent the currently logged in viewer.
{
me {
name
}
}
# `user` represents one of many users in a graph of data, referred to by a
# unique identifier.
{
user(id: 4) {
name
}
}
2.6参数
字段在概念上是返回值的函数,偶尔接受改变其行为的参数。这些参数通常直接映射到 GraphQL 服务实现中的函数参数。
在这个例子中,我们想要查询一个特定的用户(通过 id
参数请求)及其特定 size
的个人资料图片
Example № 10{
user(id: 4) {
id
name
profilePic(size: 100)
}
}
一个给定的字段可以存在多个参数
Example № 11{
user(id: 4) {
id
name
profilePic(width: 100, height: 50)
}
}
参数是无序的
可以以任何语法顺序提供参数,并保持相同的语义含义。
这两个操作在语义上是相同的
Example № 12{
picture(width: 200, height: 100)
}
Example № 13{
picture(height: 100, width: 200)
}
2.7字段别名
默认情况下,响应对象中字段的响应键将使用该字段的名称。但是,您可以通过指定别名来定义不同的响应键。
在这个例子中,我们可以获取两个不同尺寸的个人资料图片,并确保生成的响应对象不会有重复的键
Example № 14{
user(id: 4) {
id
name
smallPic: profilePic(size: 64)
bigPic: profilePic(size: 1024)
}
}
返回结果
Example № 15{
"user": {
"id": 4,
"name": "Mark Zuckerberg",
"smallPic": "https://cdn.site.io/pic-4-64.jpg",
"bigPic": "https://cdn.site.io/pic-4-1024.jpg"
}
}
操作顶层的字段也可以被赋予别名
Example № 16{
zuck: user(id: 4) {
id
name
}
}
返回结果
Example № 17{
"zuck": {
"id": 4,
"name": "Mark Zuckerberg"
}
}
2.8片段
片段是 GraphQL 中组合的主要单元。
片段允许重用常用的重复字段选择,减少文档中的重复文本。当针对接口或联合进行查询时,内联片段可以直接在选择中使用,以对类型条件进行条件化。
例如,如果我们想要获取一些关于共同好友以及某个用户的朋友的通用信息
Example № 18query noFragments {
user(id: 4) {
friends(first: 10) {
id
name
profilePic(size: 50)
}
mutualFriends(first: 10) {
id
name
profilePic(size: 50)
}
}
}
重复的字段可以提取到一个片段中,并由父片段或操作组合。
Example № 19query withFragments {
user(id: 4) {
friends(first: 10) {
...friendFields
}
mutualFriends(first: 10) {
...friendFields
}
}
}
fragment friendFields on User {
id
name
profilePic(size: 50)
}
片段通过使用展开运算符 (...
) 来使用。片段选择的所有字段都将被添加到与片段调用相同级别的字段选择中。这通过多个级别的片段展开发生。
例如
Example № 20query withNestedFragments {
user(id: 4) {
friends(first: 10) {
...friendFields
}
mutualFriends(first: 10) {
...friendFields
}
}
}
fragment friendFields on User {
id
name
...standardProfilePic
}
fragment standardProfilePic on User {
profilePic(size: 50)
}
操作 noFragments
、withFragments
和 withNestedFragments
都产生相同的响应对象。
2.8.1类型条件
片段必须指定它们应用的类型。在这个例子中,friendFields
可以在查询 User
的上下文中使用。
片段不能在任何输入值(标量、枚举或输入对象)上指定。
片段可以在对象类型、接口和联合上指定。
只有当片段正在操作的对象的具体类型与片段的类型匹配时,片段内的选择才会返回值。
例如,在这个使用 Facebook 数据模型的操作中
Example № 21query FragmentTyping {
profiles(handles: ["zuck", "coca-cola"]) {
handle
...userFragment
...pageFragment
}
}
fragment userFragment on User {
friends {
count
}
}
fragment pageFragment on Page {
likers {
count
}
}
profiles
根字段返回一个列表,其中每个元素可以是 Page
或 User
。当 profiles
结果中的对象是 User
时,friends
将会存在,而 likers
将不会存在。相反,当结果是 Page
时,likers
将会存在,而 friends
将不会存在。
Example № 22{
"profiles": [
{
"handle": "zuck",
"friends": { "count": 1234 }
},
{
"handle": "coca-cola",
"likers": { "count": 90234512 }
}
]
}
2.8.2内联片段
片段也可以在 选择集 中内联定义。这对于基于类型条件有条件地包含字段或将指令应用于选择集很有用。
标准片段包含的这个特性在上面的 query FragmentTyping
示例中得到了演示。我们可以使用内联片段来完成同样的事情。
Example № 23query inlineFragmentTyping {
profiles(handles: ["zuck", "coca-cola"]) {
handle
... on User {
friends {
count
}
}
... on Page {
likers {
count
}
}
}
}
内联片段也可以用于将指令应用于一组字段。如果省略了 TypeCondition,则内联片段被认为与封闭上下文的类型相同。
Example № 24query inlineFragmentNoType($expandedInfo: Boolean) {
user(handle: "zuck") {
id
name
... @include(if: $expandedInfo) {
firstName
lastName
birthday
}
}
}
2.9输入值
字段和指令参数接受各种字面量原始类型的输入值;输入值可以是标量、枚举值、列表或输入对象。
如果未定义为常量(例如,在 DefaultValue 中),输入值可以指定为变量。列表和输入对象也可以包含变量(除非定义为常量)。
2.9.1整数值
IntValue 被指定为没有小数点或指数,但可能是负数(例如 -123)。它不能有任何前导 0。
IntValue 之后不能跟 Digit。换句话说,IntValue 标记始终是最长的可能有效序列。源字符 12 不能被解释为两个标记,因为 1 后面跟着 Digit 2。这也意味着源 00 是无效的,因为它既不能被解释为单个标记,也不能被解释为两个 0 标记。
IntValue 之后不能跟 . 或 NameStart。如果后面跟着 . 或 ExponentIndicator,则该标记必须仅被解释为可能的 FloatValue。不能跟任何其他 NameStart 字符。例如,序列 0x123
和 123L
没有有效的词法表示。
2.9.2浮点数值
e | E |
+ | - |
FloatValue 包括小数点(例如 1.0)或指数(例如 1e50)或两者(例如 6.0221413e23),并且可能是负数。与 IntValue 一样,它也不能有任何前导 0。
FloatValue 之后不能跟 Digit。换句话说,FloatValue 标记始终是最长的可能有效序列。源字符 1.23 不能被解释为两个标记,因为 1.2 后面跟着 Digit 3。
FloatValue 之后不能跟 .。例如,序列 1.23.4 不能被解释为两个标记 (1.2, 3.4)。
FloatValue 之后不能跟 NameStart。例如,序列 0x1.2p3
没有有效的词法表示。
2.9.3布尔值
true | false |
关键字 true
和 false
代表两个布尔值。
2.9.4字符串值
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
A | B | C | D | E | F | ||||
a | b | c | d | e | f |
" | \ | / | b | f | n | r | t |
StringValue 被评估为一个 Unicode 文本 值,即一个 Unicode 标量值 序列,通过使用下面定义的静态语义解释所有转义序列。在词法标记之间忽略的空格和其他字符在字符串值中是有意义的。
空字符串 "" 之后不能跟另一个 ",否则它将被解释为块字符串的开始。例如,源 """""" 只能被解释为单个空块字符串,而不是三个空字符串。
转义序列
在单引号 StringValue 中,任何 Unicode 标量值 都可以使用转义序列表示。GraphQL 字符串允许 C 风格的转义序列(例如 \n
)和两种形式的 Unicode 转义序列:一种是固定宽度的 4 位十六进制数字(例如 \u000A
),另一种是可变宽度的,最适合表示 补充字符,例如 Emoji(例如 \u{1F4A9}
)。
Unicode 转义序列编码的十六进制数必须描述一个 Unicode 标量值,否则必须导致解析错误。例如,源 "\uDEAD"
和 "\u{110000}"
都不应被视为有效的 StringValue。
转义序列仅在单引号字符串中才有意义。在块字符串中,它们只是字符序列(例如 """\n"""
表示 Unicode 文本 [U+005C, U+006E])。在注释中,转义序列不是重要的字符序列。它们不能出现在 GraphQL 文档的其他地方。
由于 StringCharacter 不能直接包含某些代码点(例如,LineTerminator),因此必须使用转义序列来表示它们。所有其他转义序列都是可选的,并且允许在字符串中使用未转义的非 ASCII Unicode 字符。如果在仅支持 ASCII 的系统中使用 GraphQL,则可以使用转义序列来表示 ASCII 范围之外的所有 Unicode 字符。
出于历史原因,补充字符 可以通过两个固定宽度的 unicode 转义序列来转义,形成一个 代理对。例如,输入 "\uD83D\uDCA9"
是一个有效的 StringValue,它表示与 "\u{1F4A9}"
相同的 Unicode 文本。虽然允许这种遗留形式,但应避免使用它,因为可变宽度的 unicode 转义序列是编码此类代码点的更清晰方式。
当生成 StringValue 时,实现应使用转义序列来表示不可打印的控制字符(U+0000 到 U+001F 和 U+007F 到 U+009F)。其他转义序列不是必需的,但是实现可以使用转义序列来表示任何其他范围的代码点(例如,当生成仅 ASCII 输出时)。如果实现选择转义 补充字符,则应仅使用可变宽度的 unicode 转义序列。
块字符串
块字符串是用三引号 ("""
) 包裹的字符序列。空格、行终止符、引号和反斜杠字符都可以不转义地使用,以启用原文文本。字符都必须是有效的 SourceCharacter。
由于块字符串表示自由格式文本,通常用于缩进位置,因此块字符串的值语义排除了统一的缩进以及通过 BlockStringValue() 实现的空白初始行和尾随行。
例如,以下包含块字符串的操作
Example № 25mutation {
sendEmail(message: """
Hello,
World!
Yours,
GraphQL.
""")
}
与标准引号字符串相同
Example № 26mutation {
sendEmail(message: "Hello,\n World!\n\nYours,\n GraphQL.")
}
由于块字符串值会去除前导和尾随空行,因此对于给定的值,没有单一规范的打印块字符串。因为块字符串通常表示自由格式文本,所以如果它们以空行开始和结束,则认为更容易阅读。
Example № 27"""
This starts with and ends with an empty line,
which makes it easier to read.
"""
Counter Example № 28"""This does not start with or end with any empty lines,
which makes it a little harder to read."""
静态语义
StringValue 描述了一个 Unicode 文本 值,它是一个 Unicode 标量值 的序列。
这些语义描述了如何将 StringValue 语法应用于源文本,以评估 Unicode 文本。在此评估期间遇到的错误被认为是未能将 StringValue 语法应用于源,并且必须导致解析错误。
- 设 value 为 EscapedUnicode 中 HexDigit 序列表示的十六进制值。
- 断言 value 在 Unicode 标量值 范围内(≥ 0x0000 且 ≤ 0xD7FF 或 ≥ 0xE000 且 ≤ 0x10FFFF)。
- 返回 Unicode 标量值 value。
- 设 leadingValue 为第一个 HexDigit 序列表示的十六进制值。
- 设 trailingValue 为第二个 HexDigit 序列表示的十六进制值。
- 如果 leadingValue ≥ 0xD800 且 ≤ 0xDBFF(一个 前导代理)
- 断言 trailingValue ≥ 0xDC00 且 ≤ 0xDFFF(一个 后尾代理)。
- 返回 (leadingValue - 0xD800) × 0x400 + (trailingValue - 0xDC00) + 0x10000。
- 否则
- 断言 leadingValue 在 Unicode 标量值 范围内。
- 断言 trailingValue 在 Unicode 标量值 范围内。
- 返回 Unicode 标量值 leadingValue,后跟 Unicode 标量值 trailingValue 的序列。
转义字符 | 标量值 | 字符名称 |
---|---|---|
" | U+0022 | 双引号 |
\ | U+005C | 反斜线 |
/ | U+002F | 正斜线 |
b | U+0008 | 退格 |
f | U+000C | 换页 |
n | U+000A | 换行(新行) |
r | U+000D | 回车 |
t | U+0009 | 水平制表符 |
- 设 rawValue 为通过连接所有 BlockStringCharacter 的评估结果(可能是一个空序列)得到的 Unicode 文本。
- 返回 BlockStringValue(rawValue) 的结果。
- 设 lines 为通过 LineTerminator 分割 rawValue 的结果。
- 设 commonIndent 为 null。
- 对于 lines 中的每个 line
- 如果 line 是 lines 中的第一个项目,则继续到下一个 line。
- 设 length 为 line 中的字符数。
- 设 indent 为 line 中前导连续 WhiteSpace 字符的数量。
- 如果 indent 小于 length
- 如果 commonIndent 为 null 或 indent 小于 commonIndent
- 设 commonIndent 为 indent。
- 如果 commonIndent 为 null 或 indent 小于 commonIndent
- 如果 commonIndent 不为 null
- 对于 lines 中的每个 line
- 如果 line 是 lines 中的第一个项目,则继续到下一行。
- 从 line 的开头移除 commonIndent 个字符。
- 对于 lines 中的每个 line
- 当 lines 中的第一个项目 line 仅包含 WhiteSpace 时
- 从 lines 中移除第一个项目。
- 当 lines 中的最后一个项目 line 仅包含 WhiteSpace 时
- 从 lines 中移除最后一个项目。
- 设 formatted 为空字符序列。
- 对于 lines 中的每个 line
- 如果 line 是 lines 中的第一个项目
- 将 line 附加到 formatted。
- 否则
- 将换行符 (U+000A) 附加到 formatted。
- 将 line 附加到 formatted。
- 如果 line 是 lines 中的第一个项目
- 返回 formatted。
2.9.5空值
空值用关键字 null 表示。
GraphQL 有两种语义上不同的方式来表示缺少值的情况
- 显式提供字面量值:null。
- 隐式地完全不提供值。
例如,这两个字段调用类似,但并不完全相同
Example № 29{
field(arg: null)
field
}
第一种形式显式地为参数“arg”提供了 null 值,而第二种形式隐式地没有为参数“arg”提供值。这两种形式可能被解释为不同的含义。例如,一种表示删除字段,另一种表示不修改字段。这两种形式都不能用于期望非空(Non-Null)类型的输入。
2.9.6枚举值
枚举值以不带引号的名称表示(例如 MOBILE_WEB
)。建议枚举值使用“全大写”形式。枚举值仅在精确的枚举类型已知的情况下使用。因此,在字面量中不需要提供枚举类型名称。
2.9.7列表值
列表是包含在方括号 [ ]
中的有序值序列。列表字面量的值可以是任何值字面量或变量(例如 [1, 2, 3]
)。
逗号在整个 GraphQL 中是可选的,因此允许尾随逗号,重复的逗号不表示缺少值。
语义
2.9.8输入对象值
输入对象字面量值是包含在花括号 { }
中的键值输入值的无序列表。对象字面量的值可以是任何输入值字面量或变量(例如 { name: "Hello world", score: 1.0 }
)。我们将输入对象的字面表示形式称为“对象字面量”。
输入对象字段是无序的
输入对象字段可以以任何语法顺序提供,并保持相同的语义含义。
这两个操作在语义上是相同的
Example № 30{
nearestThing(location: { lon: 12.43, lat: -53.211 })
}
Example № 31{
nearestThing(location: { lat: -53.211, lon: 12.43 })
}
语义
- 设 inputObject 为一个新的不包含任何字段的输入对象值。
- 对于 ObjectFieldlist 中的每个 field
- 返回 inputObject。
2.10变量
GraphQL 操作可以使用变量进行参数化,从而最大限度地提高重用性,并避免客户端在运行时进行代价高昂的字符串构建。
如果未定义为常量(例如,在 DefaultValue 中),则可以为输入值提供 Variable。
变量必须在操作的顶部定义,并在该操作的整个执行过程中都处于作用域内。这些变量的值作为请求的一部分提供给 GraphQL 服务,以便在执行期间可以进行替换。
在此示例中,我们希望根据特定设备的大小获取配置文件图片大小
Example № 32query getZuckProfile($devicePicSize: Int) {
user(id: 4) {
id
name
profilePic(size: $devicePicSize)
}
}
如果为变量的值提供 JSON,我们可以请求尺寸为 60
的 profilePic
Example № 33{
"devicePicSize": 60
}
片段中的变量使用
变量可以在片段中使用。变量在给定的操作中具有全局作用域,因此在片段中使用的变量必须在任何传递性地使用该片段的顶层操作中声明。如果变量在片段中被引用,但包含该片段的操作未定义该变量,则该操作无效(请参阅 所有变量使用已定义)。
2.11类型引用
GraphQL 描述了参数和变量期望的数据类型。输入类型可以是另一种输入类型的列表,或者任何其他输入类型的非空变体。
语义
2.12指令
指令提供了一种在 GraphQL 文档中描述替代运行时执行和类型验证行为的方法。
在某些情况下,您需要提供选项来更改 GraphQL 的执行行为,而字段参数不足以满足需求,例如有条件地包含或跳过字段。指令通过向执行器描述附加信息来提供此功能。
指令具有名称以及参数列表,这些参数列表可以接受任何输入类型的值。
指令可用于描述类型、字段、片段和操作的附加信息。
随着 GraphQL 的未来版本采用新的可配置执行功能,它们可能会通过指令公开。GraphQL 服务和工具也可能提供超出此处描述的任何其他 自定义指令。
指令顺序很重要
可以以特定的语法顺序提供指令,这可能具有语义解释。
这两个类型定义可能具有不同的语义含义
Example № 34type Person
@addExternalFields(source: "profiles")
@excludeField(name: "photo") {
name: String
}
Example № 35type Person
@excludeField(name: "photo")
@addExternalFields(source: "profiles") {
name: String
}
3类型系统
GraphQL 类型系统描述了 GraphQL 服务的功能,用于确定请求的操作是否有效,保证响应结果的类型,并描述变量的输入类型以确定在请求时提供的值是否有效。
GraphQL 语言包含一个 IDL,用于描述 GraphQL 服务的类型系统。工具可以使用此定义语言来提供诸如客户端代码生成或服务引导等实用程序。
仅寻求执行 GraphQL 请求而不构建新的 GraphQL 模式的 GraphQL 工具或服务可以选择不允许 TypeSystemDefinition。仅寻求生成模式而不执行请求的工具可以选择仅允许 TypeSystemDocument,而不允许 ExecutableDefinition 或 TypeSystemExtension,但如果存在这些定义,则应提供描述性错误。
3.1类型系统扩展
类型系统扩展用于表示从先前的类型系统扩展而来的 GraphQL 类型系统。例如,本地服务可以使用它来表示 GraphQL 客户端仅在本地访问的数据,或者 GraphQL 服务可以使用它来表示自身是另一个 GraphQL 服务的扩展。
仅寻求生成和扩展模式而不执行请求的工具可以选择仅允许 TypeSystemExtensionDocument,而不允许 ExecutableDefinition,但如果存在这些定义,则应提供描述性错误。
3.2描述
文档是 GraphQL 类型系统的首要特性。为了确保 GraphQL 服务的文档与其功能保持一致,GraphQL 定义的描述与其定义一起提供,并通过内省可用。
为了使 GraphQL 服务设计者能够轻松地发布 GraphQL 服务的文档及其功能,GraphQL 描述使用 Markdown 语法定义(如 CommonMark 所指定)。在类型系统定义语言中,这些描述字符串(通常是 BlockString)紧接在它们描述的定义之前。
GraphQL 模式和所有其他可以描述的定义(例如,类型、字段、参数等)都应提供 Description,除非它们被认为是自我描述的。
例如,这个简单的 GraphQL 模式被很好地描述了
Example № 36"""
A simple GraphQL schema which is well described.
"""
schema {
query: Query
}
"""
Root type for all your query operations
"""
type Query {
"""
Translates a string from a given language into a different language.
"""
translate(
"The original language that `text` is provided in."
fromLanguage: Language
"The translated language to be returned."
toLanguage: Language
"The text to be translated."
text: String
): String
}
"""
The set of languages supported by `translate`.
"""
enum Language {
"English"
EN
"French"
FR
"Chinese"
CH
}
3.3模式
GraphQL 服务的集体类型系统功能被称为该服务的“模式”。模式是根据其支持的类型和指令以及每种操作类型(查询、变更和订阅)的 根操作类型 定义的;这决定了这些操作在类型系统中开始的位置。
GraphQL 模式本身必须在内部有效。本节描述了与此验证过程相关的规则。
GraphQL 模式中的所有类型都必须具有唯一的名称。任何两个提供的类型都不能具有相同的名称。任何提供的类型都不能具有与任何内置类型(包括标量和内省类型)冲突的名称。
GraphQL 模式中的所有指令都必须具有唯一的名称。
模式中定义的所有类型和指令都不能以 "__"(两个下划线)开头,因为这由 GraphQL 的内省系统专门使用。
3.3.1根操作类型
模式为它支持的每种操作类型定义了初始 根操作类型:查询、变更和订阅;这决定了这些操作在类型系统中开始的位置。
查询 根操作类型 必须提供,并且必须是对象类型。
变更 根操作类型 是可选的;如果未提供,则服务不支持变更。如果提供了,则必须是对象类型。
类似地,订阅 根操作类型 也是可选的;如果未提供,则服务不支持订阅。如果提供了,则必须是对象类型。
如果提供了 查询、变更 和 订阅 根类型,则它们必须都是不同的类型。
查询 根操作类型 上的字段指示在 GraphQL 查询操作的顶层可用的字段。
例如,只有当 查询 根操作类型 具有名为“myName”的字段时,以下示例操作才有效
Example № 37query {
myName
}
is only valid when the query root operation type has a field named “myName”
Example № 38type Query {
myName: String
}
类似地,只有当 变更 根操作类型 具有名为“setName”的字段时,以下变更才有效。
Example № 39mutation {
setName(name: "Zuck") {
newName
}
}
在使用类型系统定义语言时,文档必须最多包含一个 schema 定义。
在此示例中,GraphQL 模式定义了查询和变更 根操作类型
Example № 40schema {
query: MyQueryRootType
mutation: MyMutationRootType
}
type MyQueryRootType {
someField: String
}
type MyMutationRootType {
setSomeField(to: String): String
}
默认根操作类型名称
每个 查询、变更 和 订阅 根操作类型 的 默认根类型名称 分别为 "Query"、"Mutation" 和 "Subscription"。
当每个 根操作类型 使用其各自的 默认根类型名称 且没有其他类型使用任何 默认根类型名称 时,类型系统定义语言可以省略模式定义。
同样,当使用类型系统定义语言表示 GraphQL 模式时,如果每个 根操作类型 使用其各自的 默认根类型名称 且没有其他类型使用任何 默认根类型名称 时,应省略模式定义。
此示例描述了一个有效的完整 GraphQL 模式,尽管没有显式包含 schema 定义。"Query" 类型被假定为模式的 查询 根操作类型。
Example № 41type Query {
someField: String
}
此示例描述了一个没有 变更 根操作类型 的有效 GraphQL 模式,即使它包含一个名为 "Mutation" 的类型。必须包含模式定义,否则 "Mutation" 类型将被错误地假定为模式的 变更 根操作类型。
Example № 42schema {
query: Query
}
type Query {
latestVirus: Virus
}
type Virus {
name: String
mutations: [Mutation]
}
type Mutation {
name: String
}
3.3.2模式扩展
模式扩展用于表示从先前的模式扩展而来的模式。例如,GraphQL 服务可以使用它来向现有模式添加其他操作类型或其他指令。
模式验证
如果模式扩展定义不正确,则可能无效。
- 模式必须已定义。
- 任何提供的不可重复指令都不能已应用于先前的模式。
3.4类型
任何 GraphQL 模式的基本单元是类型。GraphQL 中有六种命名的类型定义和两种包装类型。
最基本的类型是 Scalar
。标量表示原始值,例如字符串或整数。通常,标量字段的可能响应是可枚举的。GraphQL 在这些情况下提供 Enum
类型,其中类型指定了有效响应的空间。
标量和枚举构成响应树的叶子;中间层是 Object
类型,它定义了一组字段,其中每个字段都是系统中的另一种类型,从而允许定义任意类型层次结构。
GraphQL 支持两种抽象类型:接口和联合。
Interface
定义字段列表;实现此接口的 Object
类型和其他接口类型保证实现这些字段。每当字段声明它将返回接口类型时,它将在执行期间返回有效的实现对象类型。
Union
定义可能的类型列表;与接口类似,每当类型系统声明将返回联合时,将返回其中一种可能的类型。
最后,通常将复杂结构作为 GraphQL 字段参数或变量的输入非常有用;Input Object
类型允许模式精确定义期望的数据。
3.4.1包装类型
到目前为止,所有类型都假定为既可为空又为单数:例如,标量字符串返回空值或单个字符串。
GraphQL 模式可以描述字段表示另一种类型的列表;为此提供了 List
类型,并包装了另一种类型。
类似地,Non-Null
类型包装了另一种类型,并表示结果值永远不会是 null(并且 字段错误 不会导致 null 值)。
这两种类型称为“包装类型”;非包装类型称为“命名类型”。包装类型具有一个底层命名类型,通过不断解包类型直到找到命名类型为止。
3.4.2输入和输出类型
类型在整个 GraphQL 中用于描述作为参数和变量的输入接受的值,以及字段输出的值。这两种用途将类型分为输入类型和输出类型。某些类型的类型,例如标量类型和枚举类型,既可以用作输入类型,也可以用作输出类型;其他类型的类型只能在其中一种中使用。输入对象类型只能用作输入类型。对象、接口和联合类型只能用作输出类型。列表类型和非空类型可以用作输入类型或输出类型,具体取决于包装的类型如何使用。
- 如果 type 是列表类型或非空类型
- 设 unwrappedType 为 type 的解包类型。
- 返回 IsInputType(unwrappedType)。
- 如果 type 是标量、枚举或输入对象类型
- 返回 true。
- 返回 false。
- 如果 type 是列表类型或非空类型
- 设 unwrappedType 为 type 的解包类型。
- 返回 IsOutputType(unwrappedType)。
- 如果 type 是标量、对象、接口、联合或枚举类型
- 返回 true。
- 返回 false。
3.4.3类型扩展
类型扩展用于表示从先前的类型扩展而来的 GraphQL 类型。例如,本地服务可以使用它来表示 GraphQL 客户端仅在本地访问的其他字段。
3.5标量
标量类型表示 GraphQL 类型系统中的原始叶子值。GraphQL 响应采用分层树的形式;此树的叶子通常是 GraphQL 标量类型(但也可能是枚举类型或 null 值)。
GraphQL 提供了许多内置标量,这些标量在下面的章节中进行了完整定义,但是类型系统也可以添加其他自定义标量以引入其他语义含义。
内置标量
GraphQL 指定了一组定义明确的基本标量类型:Int、Float、String、Boolean 和 ID。GraphQL 框架应支持所有这些类型,并且提供这些名称的 GraphQL 服务必须遵守本文档中描述的行为。例如,服务不得包含名为 Int 的类型,并将其用于表示 64 位数字、国际化信息或本文档中定义以外的任何其他内容。
从 __Schema
内省类型返回类型集时,必须包含所有引用的内置标量。如果内置标量类型在模式中的任何位置都没有被引用(该类型没有字段、参数或输入字段),则不得包含它。
当使用类型系统定义语言表示 GraphQL 模式时,为了简洁起见,必须省略所有内置标量。
自定义标量
除了内置标量之外,GraphQL 服务还可以使用自定义标量类型。例如,GraphQL 服务可以定义一个名为 UUID
的标量,该标量虽然序列化为字符串,但符合 RFC 4122。当查询 UUID
类型的字段时,您可以依赖使用符合 RFC 4122 的解析器解析结果的能力。另一个可能有用的自定义标量的示例是 URL
,它序列化为字符串,但服务保证它是有效的 URL。
在定义自定义标量时,GraphQL 服务应通过 @specifiedBy
指令或 specifiedByURL
内省字段提供 标量规范 URL。此 URL 必须链接到标量的数据格式、序列化和强制转换规则的人类可读规范。
例如,提供 UUID
标量的 GraphQL 服务可以链接到 RFC 4122,或定义该 RFC 的合理子集的某些自定义文档。如果存在 标量规范 URL,则知道它的系统和工具应符合其描述的规则。
Example № 43scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
scalar DateTime
@specifiedBy(url: "https://scalars.graphql.org/andimarek/date-time")
自定义 标量规范 URL 应提供单一、稳定的格式,以避免歧义。如果链接的规范正在变动,服务应链接到固定版本,而不是可能更改的资源。
自定义 标量规范 URL 一旦定义就不应更改。这样做可能会扰乱工具,或者可能在链接规范的内容中引入破坏性更改。
内置标量类型不得提供 标量规范 URL,因为它们由此文档指定。
结果强制和序列化
GraphQL 服务在准备给定标量类型的字段时,必须遵守标量类型描述的约定,要么强制转换值,要么在无法强制转换值或强制转换可能导致数据丢失时生成 字段错误。
GraphQL 服务可以决定允许将不同的内部类型强制转换为预期的返回类型。例如,当强制转换 Int 类型的字段时,布尔值 true 可能会产生 1,或者字符串值 "123" 可能会被解析为十进制 123。但是,如果内部类型强制转换无法在不丢失信息的情况下合理执行,则必须引发 字段错误。
由于客户端无法观察到这种强制转换行为,因此强制转换的具体规则留给实现来决定。唯一的要求是服务必须产生符合预期标量类型的值。
GraphQL 标量根据正在使用的序列化格式进行序列化。对于每种给定的标量类型,可能存在最合适的序列化原语,并且服务应在适当的情况下生成每种原语。
有关常见 JSON 和其他格式中标量的序列化的更多详细信息,请参阅 序列化格式。
输入强制
如果 GraphQL 服务期望将标量类型作为参数的输入,则强制转换是可观察的,并且规则必须明确定义。如果输入值与强制转换规则不匹配,则必须引发 请求错误(输入值在执行开始前进行验证)。
GraphQL 具有不同的常量字面量来表示整数和浮点输入值,并且强制转换规则可能因遇到的输入值类型而异。GraphQL 可以通过变量进行参数化,变量的值通常在通过 HTTP 等传输方式发送时进行序列化。由于某些常见的序列化格式(例如 JSON)无法区分整数值和浮点值,因此如果它们具有空的小数部分(例如 1.0
),则被解释为整数输入值,否则被解释为浮点输入值。
对于以下所有类型,除了 Non-Null 类型之外,如果提供了显式值 null,则输入强制转换的结果为 null。
3.5.1Int
Int 标量类型表示有符号 32 位数值非分数的值。支持 32 位整数或数字类型的响应格式应使用该类型来表示此标量。
结果强制
返回 Int 类型的字段期望遇到 32 位整数内部值。
GraphQL 服务可以在合理且不丢失信息的情况下将非整数内部值强制转换为整数,否则它们必须引发 字段错误。例如,这可能包括为浮点数 1.0
返回 1
,或为字符串 "123"
返回 123
。在强制转换可能丢失数据的情况下,引发字段错误更为合适。例如,浮点数 1.2
应引发字段错误,而不是截断为 1
。
如果整数内部值表示的值小于 -231 或大于或等于 231,则应引发 字段错误。
输入强制
当预期作为输入类型时,仅接受整数输入值。所有其他输入值,包括具有数字内容的字符串,都必须引发请求错误,指示类型不正确。如果整数输入值表示的值小于 -231 或大于或等于 231,则应引发 请求错误。
3.5.2Float
Float 标量类型表示由 IEEE 754 指定的有符号双精度有限值。支持适当双精度数字类型的响应格式应使用该类型来表示此标量。
结果强制
返回 Float 类型的字段期望遇到双精度浮点内部值。
GraphQL 服务可以在合理且不丢失信息的情况下将非浮点内部值强制转换为 Float,否则它们必须引发 字段错误。例如,这可能包括为整数 1
返回 1.0
,或为字符串 "123"
返回 123.0
。
非有限浮点内部值(NaN 和 Infinity)无法强制转换为 Float,并且必须引发 字段错误。
输入强制
当预期作为输入类型时,整数和浮点输入值都被接受。整数输入值通过添加空的小数部分强制转换为 Float,例如,整数输入值 1
转换为 1.0
。所有其他输入值,包括具有数字内容的字符串,都必须引发 请求错误,指示类型不正确。如果输入值以其他方式表示 IEEE 754 有限值无法表示的值(例如 NaN、Infinity 或超出可用精度的值),则必须引发 请求错误。
3.5.3String
String 标量类型表示文本数据,表示为 Unicode 代码点序列。String 类型最常被 GraphQL 用于表示自由格式的人类可读文本。String 在内部如何编码(例如 UTF-8)留给服务实现来决定。所有响应序列化格式都必须支持字符串表示形式(例如,JSON Unicode 字符串),并且必须使用该表示形式来序列化此类型。
结果强制
返回 String 类型的字段期望遇到 Unicode 字符串值。
GraphQL 服务可以在合理且不丢失信息的情况下将非字符串原始值强制转换为 String,否则它们必须引发 字段错误。例如,这可能包括为布尔值 true 返回字符串 "true"
,或为整数 1
返回字符串 "1"
。
输入强制
当预期作为输入类型时,仅接受有效的 Unicode 字符串输入值。所有其他输入值都必须引发 请求错误,指示类型不正确。
3.5.4Boolean
Boolean 标量类型表示 true
或 false
。响应格式应使用内置布尔类型(如果支持);否则,应使用整数 1
和 0
的表示形式。
结果强制
返回 Boolean 类型的字段期望遇到布尔内部值。
GraphQL 服务可以在合理且不丢失信息的情况下将非布尔原始值强制转换为 Boolean,否则它们必须引发 字段错误。例如,这可能包括为非零数字返回 true
。
输入强制
当预期作为输入类型时,仅接受布尔输入值。所有其他输入值都必须引发 请求错误,指示类型不正确。
3.5.5ID
ID 标量类型表示唯一标识符,通常用于重新获取对象或作为缓存的键。ID 类型的序列化方式与 String 相同;但是,它不应是人类可读的。虽然它通常是数字,但必须始终序列化为 String。
结果强制
GraphQL 对 ID 格式不可知,并序列化为字符串,以确保 ID 可能表示的多种格式之间的一致性,从小的自增数字到大的 128 位随机数,到 base64 编码值,或像 GUID 这样的字符串值。
GraphQL 服务应根据他们期望的 ID 格式进行适当的强制转换。当无法进行强制转换时,他们必须引发 字段错误。
输入强制
当预期作为输入类型时,任何字符串(例如 "4"
)或整数(例如 4
或 -4
)输入值都应强制转换为 ID,以适应给定的 GraphQL 服务期望的 ID 格式。任何其他输入值,包括浮点输入值(例如 4.0
),都必须引发 请求错误,指示类型不正确。
3.5.6标量扩展
标量类型扩展用于表示已从先前标量类型扩展而来的标量类型。例如,GraphQL 工具或服务可以使用它来向现有标量添加指令。
类型验证
如果标量类型扩展定义不正确,则可能无效。
- 命名类型必须已定义,并且必须是标量类型。
- 提供的任何不可重复指令都不得已应用于先前的标量类型。
3.6对象
GraphQL 操作是分层和组合的,描述了一个信息树。虽然标量类型描述了这些分层操作的叶子值,但对象描述了中间级别。
GraphQL 对象表示命名字段的列表,每个字段产生特定类型的值。对象值应序列化为有序映射,其中选定的字段名称(或别名)是键,评估字段的结果是值,按它们在 选择集 中出现的顺序排序。
在对象类型中定义的所有字段都不得具有以 "__" (两个下划线) 开头的名称,因为这专门用于 GraphQL 的内省系统。
例如,类型 Person
可以描述为
Example № 44type Person {
name: String
age: Int
picture: Url
}
其中 name
是一个将产生 String 值的字段,age
是一个将产生 Int 值的字段,picture
是一个将产生 Url
值的字段。
对象值的查询必须选择至少一个字段。这种字段的选择将产生一个有序映射,其中包含精确的对象查询子集,该子集应以它们被查询的顺序表示。只有在对象类型上声明的字段才能在该对象上有效查询。
例如,选择 Person
的所有字段
Example № 45{
name
age
picture
}
将产生对象
Example № 46{
"name": "Mark Zuckerberg",
"age": 30,
"picture": "http://some.cdn/picture.jpg"
}
而选择字段的子集
Example № 47{
age
name
}
必须仅产生该子集
Example № 48{
"age": 30,
"name": "Mark Zuckerberg"
}
对象类型的字段可以是标量、枚举、另一个对象类型、接口或联合。此外,它可以是任何包装类型,其底层基本类型是这五种类型之一。
例如,Person
类型可能包含 relationship
Example № 49type Person {
name: String
age: Int
picture: Url
relationship: Person
}
有效操作必须为任何返回对象的字段提供嵌套字段集,因此此操作无效
Counter Example № 50{
name
relationship
}
但是,此示例是有效的
Example № 51{
name
relationship {
name
}
}
并将产生每个查询对象类型的子集
Example № 52{
"name": "Mark Zuckerberg",
"relationship": {
"name": "Priscilla Chan"
}
}
字段排序
当查询对象时,字段的结果映射在概念上以执行期间遇到的相同顺序排序,不包括类型不适用的片段以及通过 @skip
或 @include
指令跳过的字段或片段。当使用 CollectFields() 算法时,可以正确生成此排序。
能够表示有序映射的响应序列化格式应保持此排序。只能表示无序映射的序列化格式(例如 JSON)应以文本形式保留此顺序。也就是说,如果按顺序查询了两个字段 {foo, bar}
,则生成的 JSON 序列化应包含 {"foo": "...", "bar": "..."}
,并且顺序相同。
生成响应,其中字段以它们在请求中出现的相同顺序表示,可以提高调试期间的人类可读性,并使在可以预期属性顺序的情况下更有效地解析响应。
如果片段在其他字段之前展开,则该片段指定的字段在响应中出现在后续字段之前。
Example № 53{
foo
...Frag
qux
}
fragment Frag on Query {
bar
baz
}
产生有序结果
Example № 54{
"foo": 1,
"bar": 2,
"baz": 3,
"qux": 4
}
如果在选择中多次查询字段,则按首次遇到该字段的顺序排序。但是,类型不适用的片段不会影响排序。
Example № 55{
foo
...Ignored
...Matching
bar
}
fragment Ignored on UnknownType {
qux
baz
}
fragment Matching on Query {
bar
qux
foo
}
产生有序结果
Example № 56{
"foo": 1,
"bar": 2,
"qux": 3
}
此外,如果指令导致字段被排除,则在字段排序中不考虑它们。
Example № 57{
foo @skip(if: true)
bar
foo
}
产生有序结果
Example № 58{
"bar": 1,
"foo": 2
}
结果强制
确定强制转换对象的结果是 GraphQL 执行器的核心,请参阅 值完成。
输入强制
对象永远不是有效的输入。
类型验证
如果对象类型定义不正确,则可能无效。GraphQL 模式中的每个对象类型都必须遵守这组规则。
- 对象类型必须定义一个或多个字段。
- 对于对象类型的每个字段
- 该字段在该对象类型中必须具有唯一名称;没有两个字段可以共享相同的名称。
- 该字段不得具有以字符 "__" (两个下划线) 开头的名称。
- 该字段必须返回一个类型,其中 IsOutputType(fieldType) 返回 true。
- 对于字段的每个参数
- 该参数不得具有以字符 "__" (两个下划线) 开头的名称。
- 该参数在该字段中必须具有唯一名称;没有两个参数可以共享相同的名称。
- 该参数必须接受一个类型,其中 IsInputType(argumentType) 返回 true。
- 如果参数类型为 Non-Null 且未定义默认值
@deprecated
指令不得应用于此参数。
- 对象类型可以声明它实现一个或多个唯一接口。
- 对象类型必须是它实现的所有接口的超集
- 让此对象类型为 objectType。
- 对于声明实现的每个接口 interfaceType,IsValidImplementation(objectType, interfaceType) 必须为 true。
- 如果 implementedType 声明它实现了任何接口,则 type 也必须声明它实现了这些接口。
- type 必须为 implementedType 中定义的每个字段包含一个同名字段。
- 让 field 成为 type 上的该命名字段。
- 让 implementedField 成为 implementedType 上的该命名字段。
- field 必须为 implementedField 中定义的每个参数包含一个同名参数。
- field 上的该命名参数必须接受与 implementedField 上的该命名参数相同的类型(不变性)。
- field 可以包含 implementedField 中未定义的其他参数,但任何其他参数都不得是必需的,例如,不得为非可为空类型。
- field 必须返回一个类型,该类型等于或为 implementedField 字段返回类型的子类型(协变性)
- 让 fieldType 为 field 的返回类型。
- 让 implementedFieldType 为 implementedField 的返回类型。
- IsValidImplementationFieldType(fieldType, implementedFieldType) 必须为 true。
- 如果 fieldType 是 Non-Null 类型
- 让 nullableType 为 fieldType 的解包可为空类型。
- 让 implementedNullableType 为 implementedFieldType 的解包可为空类型(如果它是 Non-Null 类型),否则直接让它为 implementedFieldType。
- 返回 IsValidImplementationFieldType(nullableType, implementedNullableType)。
- 如果 fieldType 是 List 类型,并且 implementedFieldType 也是 List 类型
- 让 itemType 为 fieldType 的解包项类型。
- 让 implementedItemType 为 implementedFieldType 的解包项类型。
- 返回 IsValidImplementationFieldType(itemType, implementedItemType)。
- 返回 IsSubType(fieldType, implementedFieldType)。
- 如果 possibleSubType 与 superType 的类型相同,则返回 true。
- 如果 possibleSubType 是对象类型,并且 superType 是联合类型,并且 possibleSubType 是 superType 的可能类型,则返回 true。
- 如果 possibleSubType 是对象或接口类型,并且 superType 是接口类型,并且 possibleSubType 声明它实现了 superType,则返回 true。
- 否则返回 false。
3.6.1字段参数
对象字段在概念上是产生值的函数。有时,对象字段可以接受参数以进一步指定返回值。对象字段参数定义为所有可能的参数名称及其预期输入类型的列表。
在字段中定义的所有参数都不得具有以 "__" (两个下划线) 开头的名称,因为这专门用于 GraphQL 的内省系统。
例如,具有 picture
字段的 Person
类型可以接受一个参数来确定要返回的图像大小。
Example № 59type Person {
name: String
picture(size: Int): Url
}
操作可以选择性地为其字段指定参数以提供这些参数。
此示例操作
Example № 60{
name
picture(size: 600)
}
可能会返回结果
Example № 61{
"name": "Mark Zuckerberg",
"picture": "http://some.cdn/picture_600.jpg"
}
对象字段参数的类型必须是输入类型(除对象、接口或联合类型之外的任何类型)。
3.6.2字段弃用
对象中的字段可以根据应用程序的需要标记为已弃用。在 选择集 中包含这些字段仍然是合法的(以确保现有客户端不会因更改而中断),但应在文档和工具中适当处理这些字段。
当使用类型系统定义语言时,@deprecated
指令用于指示字段已弃用
Example № 62type ExampleType {
oldField: String @deprecated
}
3.6.3对象扩展
对象类型扩展用于表示已从先前类型扩展而来的类型。例如,这可能用于表示本地数据,或者由本身是另一个 GraphQL 服务的扩展的 GraphQL 服务使用。
在此示例中,本地数据字段添加到 Story
类型
Example № 63extend type Story {
isHiddenLocally: Boolean
}
对象类型扩展可以选择不添加其他字段,而仅添加接口或指令。
在此示例中,指令添加到 User
类型,但不添加字段
Example № 64extend type User @addedDirective
类型验证
如果对象类型扩展定义不正确,则可能无效。
- 命名类型必须已定义,并且必须是对象类型。
- 对象类型扩展的字段必须具有唯一名称;没有两个字段可以共享相同的名称。
- 对象类型扩展的任何字段都不得已在先前的对象类型上定义。
- 提供的任何不可重复指令都不得已应用于先前的对象类型。
- 提供的任何接口都不得已由先前的对象类型实现。
- 生成的扩展对象类型必须是它实现的所有接口的超集。
3.7接口
GraphQL 接口表示命名字段及其参数的列表。GraphQL 对象和接口随后可以实现这些接口,这要求实现类型将定义这些接口定义的所有字段。
GraphQL 接口上的字段与 GraphQL 对象上的字段具有相同的规则;它们的类型可以是标量、对象、枚举、接口或联合,或任何包装类型,其基本类型是这五种类型之一。
例如,接口 NamedEntity
可以描述一个必需字段,然后诸如 Person
或 Business
之类的类型可以实现此接口以保证此字段始终存在。
类型也可以实现多个接口。例如,在下面的示例中,Business
同时实现了 NamedEntity
和 ValuedEntity
接口。
Example № 65interface NamedEntity {
name: String
}
interface ValuedEntity {
value: Int
}
type Person implements NamedEntity {
name: String
age: Int
}
type Business implements NamedEntity & ValuedEntity {
name: String
value: Int
employeeCount: Int
}
当预期多个对象类型之一,但应保证某些字段时,产生接口的字段很有用。
为了继续该示例,Contact
可能会引用 NamedEntity
。
Example № 66type Contact {
entity: NamedEntity
phoneNumber: String
address: String
}
这使我们可以为 Contact
编写一个 选择集,它可以选择公共字段。
Example № 67{
entity {
name
}
phoneNumber
}
当在接口类型上选择字段时,只能查询在该接口上声明的字段。在上面的示例中,entity
返回 NamedEntity
,并且 name
在 NamedEntity
上定义,因此它是有效的。但是,以下内容对于 Contact
来说将不是有效的选择集
Counter Example № 68{
entity {
name
age
}
phoneNumber
}
因为 entity
引用了 NamedEntity
,并且 age
未在该接口上定义。仅当 entity
的结果为 Person
时,查询 age
才有效;这可以使用片段或内联片段来表示
Example № 69{
entity {
name
... on Person {
age
}
}
phoneNumber
}
接口实现接口
当定义实现另一个接口的接口时,实现接口必须定义已实现接口指定的每个字段。例如,接口 Resource 必须定义字段 id 才能实现 Node 接口
Example № 70interface Node {
id: ID!
}
interface Resource implements Node {
id: ID!
url: String
}
传递实现的接口(正在实现的接口实现的接口)也必须在实现类型或接口上定义。例如,Image
无法在不实现 Node
的情况下实现 Resource
Example № 71interface Node {
id: ID!
}
interface Resource implements Node {
id: ID!
url: String
}
interface Image implements Resource & Node {
id: ID!
url: String
thumbnail: String
}
接口定义不得包含循环引用,也不得实现自身。此示例无效,因为 Node
和 Named
实现了自身,也实现了彼此
Counter Example № 72interface Node implements Named & Node {
id: ID!
name: String
}
interface Named implements Node & Named {
id: ID!
name: String
}
结果强制
接口类型应具有某种方式来确定给定结果对应于哪个对象。一旦完成,接口的结果强制转换与对象的结果强制转换相同。
输入强制
接口永远不是有效的输入。
类型验证
如果接口类型定义不正确,则可能无效。
- 接口类型必须定义一个或多个字段。
- 对于接口类型的每个字段
- 字段在该接口类型中必须具有唯一的名称;任何两个字段都不得共享相同的名称。
- 该字段不得具有以字符 "__" (两个下划线) 开头的名称。
- 该字段必须返回一个类型,其中 IsOutputType(fieldType) 返回 true。
- 对于字段的每个参数
- 该参数不得具有以字符 "__" (两个下划线) 开头的名称。
- 该参数在该字段中必须具有唯一名称;没有两个参数可以共享相同的名称。
- 该参数必须接受一个类型,其中 IsInputType(argumentType) 返回 true。
- 接口类型可以声明它实现了一个或多个唯一接口,但不得实现自身。
- 接口类型必须是其实现的所有接口的超集
- 令此接口类型为 implementingType。
- 对于声明为实现的每个接口 implementedType,IsValidImplementation(implementingType, implementedType) 必须为 true。
3.7.1接口扩展
接口类型扩展用于表示从先前接口扩展而来的接口。例如,这可以用于表示许多类型的通用本地数据,或者表示 GraphQL 服务本身是另一个 GraphQL 服务的扩展。
在此示例中,扩展的数据字段被添加到 NamedEntity
类型以及实现它的类型中
Example № 73extend interface NamedEntity {
nickname: String
}
extend type Person {
nickname: String
}
extend type Business {
nickname: String
}
接口类型扩展可以选择不添加额外的字段,而只添加指令。
在此示例中,指令被添加到 NamedEntity
类型,而没有添加字段
Example № 74extend interface NamedEntity @addedDirective
类型验证
如果接口类型扩展定义不正确,则可能无效。
- 命名类型必须已定义,并且必须是接口类型。
- 接口类型扩展的字段必须具有唯一的名称;任何两个字段都不得共享相同的名称。
- 接口类型扩展的任何字段都不得已在先前的接口类型上定义。
- 任何实现了先前接口类型的对象或接口类型,也必须是接口类型扩展字段的超集(这可能是由于对象类型扩展)。
- 提供的任何不可重复指令都不得已应用于先前的接口类型。
- 生成的扩展接口类型必须是其实现的所有接口的超集。
3.8联合
GraphQL 联合表示一个对象,该对象可以是 GraphQL 对象类型列表中的一种,但不提供这些类型之间任何保证的字段。 它们与接口的不同之处还在于,对象类型声明它们实现了哪些接口,但不知道哪些联合包含它们。
对于接口和对象,只能直接查询在该类型上定义的字段;要查询接口上的其他字段,必须使用类型化的片段。这与联合相同,但联合不定义任何字段,因此如果不使用类型细化片段或内联片段,则无法在此类型上查询任何字段(元字段 __typename 除外)。
例如,我们可能会定义以下类型
Example № 75union SearchResult = Photo | Person
type Person {
name: String
age: Int
}
type Photo {
height: Int
width: Int
}
type SearchQuery {
firstSearchResult: SearchResult
}
在此示例中,查询操作想要在结果为 Person 时获取名称,在结果为照片时获取高度。但是,由于联合本身未定义任何字段,因此这可能是模棱两可且无效的。
Counter Example № 76{
firstSearchResult {
name
height
}
}
有效的操作包括类型化的片段(在本例中为内联片段)
Example № 77{
firstSearchResult {
... on Person {
name
}
... on Photo {
height
}
}
}
联合成员可以使用可选的前导 |
字符进行定义,以帮助在表示更长的可能类型列表时进行格式化
Example № 78union SearchResult =
| Photo
| Person
结果强制
联合类型应具有某种方式来确定给定结果对应于哪个对象。一旦完成,联合的结果强制转换与对象的结果强制转换相同。
输入强制
联合永远不是有效的输入。
类型验证
如果联合类型定义不正确,则可能无效。
- 联合类型必须包含一个或多个唯一的成员类型。
- 联合类型的成员类型都必须是对象基本类型;标量、接口和联合类型不得作为联合的成员类型。 同样,包装类型也不得作为联合的成员类型。
3.8.1联合扩展
联合类型扩展用于表示从先前联合类型扩展而来的联合类型。例如,这可以用于表示额外的本地数据,或者表示 GraphQL 服务本身是另一个 GraphQL 服务的扩展。
类型验证
如果联合类型扩展定义不正确,则可能无效。
- 命名类型必须已定义,并且必须是联合类型。
- 联合类型扩展的成员类型都必须是对象基本类型;标量、接口和联合类型不得作为联合的成员类型。 同样,包装类型也不得作为联合的成员类型。
- 联合类型扩展的所有成员类型都必须是唯一的。
- 联合类型扩展的所有成员类型都不得已经是先前联合类型的成员。
- 提供的任何不可重复指令都不得已应用于先前的联合类型。
3.9枚举
GraphQL 枚举类型,与标量类型一样,也表示 GraphQL 类型系统中的叶值。但是,枚举类型描述了可能值的集合。
枚举不是数值的引用,而是其自身独有的值。它们可以序列化为字符串:所表示值的名称。
在此示例中,定义了一个名为 Direction
的枚举类型
Example № 79enum Direction {
NORTH
EAST
SOUTH
WEST
}
结果强制
GraphQL 服务必须返回定义的可能值集合之一。如果无法进行合理的强制转换,则它们必须引发 字段错误。
输入强制
GraphQL 具有表示枚举输入值的常量文字。GraphQL 字符串文字不得接受为枚举输入,而应引发请求错误。
对于非字符串符号值具有不同表示形式的变量传输序列化(例如,EDN)应仅允许此类值作为枚举输入值。 否则,对于大多数没有这样做的传输序列化,字符串可以解释为具有相同名称的枚举输入值。
类型验证
如果枚举类型定义不正确,则可能无效。
- 枚举类型必须定义一个或多个唯一的枚举值。
3.9.1枚举扩展
枚举类型扩展用于表示从先前枚举类型扩展而来的枚举类型。例如,这可以用于表示额外的本地数据,或者表示 GraphQL 服务本身是另一个 GraphQL 服务的扩展。
类型验证
如果枚举类型扩展定义不正确,则可能无效。
- 命名类型必须已定义,并且必须是枚举类型。
- 枚举类型扩展的所有值都必须是唯一的。
- 枚举类型扩展的所有值都不得已经是先前枚举的值。
- 提供的任何不可重复指令都不得已应用于先前的枚举类型。
3.10输入对象
字段可以接受参数来配置其行为。这些输入通常是标量或枚举,但有时它们需要表示更复杂的值。
GraphQL 输入对象定义了一组输入字段;输入字段可以是标量、枚举或其他输入对象。这允许参数接受任意复杂的结构。
在此示例中,名为 Point2D
的输入对象描述了 x
和 y
输入
Example № 80input Point2D {
x: Float
y: Float
}
循环引用
输入对象允许将其他输入对象作为字段类型引用。当输入对象直接或通过引用的输入对象引用自身时,就会发生循环引用。
通常允许循环引用,但是它们不能定义为非空单数字段的不间断链。此类输入对象无效,因为无法为其提供合法值。
此循环引用输入类型的示例有效,因为字段 self
可以省略或值为 null。
Example № 81input Example {
self: Example
value: String
}
此示例也有效,因为字段 self
可以是空列表。
Example № 82input Example {
self: [Example!]!
value: String
}
此循环引用输入类型的示例无效,因为无法为字段 self
提供有限值。
Counter Example № 83input Example {
value: String
self: Example!
}
此示例也无效,因为通过 First.second
和 Second.first
字段存在非空单数循环引用。
Counter Example № 84input First {
second: Second!
value: String
}
input Second {
first: First!
value: String
}
结果强制
输入对象永远不是有效的结果。输入对象类型不能是对象或接口字段的返回类型。
输入强制
输入对象的值应为输入对象文字或由变量提供的无序映射,否则必须引发 请求错误。 在任何一种情况下,输入对象文字或无序映射都不得包含任何名称未由此输入对象类型的字段定义的条目,否则必须引发请求错误。
强制转换的结果是一个无序映射,其中包含由输入对象类型定义且存在值的每个字段的条目。生成的映射按照以下规则构造
- 如果未为定义的输入对象字段提供值,并且该字段定义提供了默认值,则应使用默认值。 如果未提供默认值,并且输入对象字段的类型为非空,则应引发错误。 否则,如果该字段不是必需的,则不会将任何条目添加到强制转换的无序映射中。
- 如果为输入对象字段提供了值 null,并且该字段的类型不是非空类型,则强制转换的无序映射中的条目将被赋予值 null。 换句话说,显式提供的值 null 与未提供值之间存在语义差异。
- 如果为输入对象字段提供了文字值,则强制转换的无序映射中的条目将根据该字段类型的输入强制转换规则,被赋予强制转换该值的结果。
- 如果为输入对象字段提供了变量,则必须使用该变量的运行时值。 如果运行时值为 null 且字段类型为非空,则必须引发 字段错误。 如果未提供运行时值,则应使用变量定义的默认值。 如果变量定义未提供默认值,则应使用输入对象字段定义的默认值。
以下是输入对象类型的输入强制转换示例,该输入对象类型具有 String
字段 a
和必需(非空)Int!
字段 b
Example № 85input ExampleInputObject {
a: String
b: Int!
}
文字值 | 变量 | 强制转换的值 |
---|---|---|
{ a: "abc", b: 123 } |
{} |
{ a: "abc", b: 123 } |
{ a: null, b: 123 } |
{} |
{ a: null, b: 123 } |
{ b: 123 } |
{} |
{ b: 123 } |
{ a: $var, b: 123 } |
{ var: null } |
{ a: null, b: 123 } |
{ a: $var, b: 123 } |
{} |
{ b: 123 } |
{ b: $var } |
{ var: 123 } |
{ b: 123 } |
$var |
{ var: { b: 123 } } |
{ b: 123 } |
"abc123" |
{} |
错误:值不正确 |
$var |
{ var: "abc123" } |
错误:值不正确 |
{ a: "abc", b: "123" } |
{} |
错误:字段 b 的值不正确 |
{ a: "abc" } |
{} |
错误:缺少必需字段 b |
{ b: $var } |
{} |
错误:缺少必需字段 b。 |
$var |
{ var: { a: "abc" } } |
错误:缺少必需字段 b |
{ a: "abc", b: null } |
{} |
错误:b 必须是非空的。 |
{ b: $var } |
{ var: null } |
错误:b 必须是非空的。 |
{ b: 123, c: "xyz" } |
{} |
错误:意外字段 c |
类型验证
- 输入对象类型必须定义一个或多个输入字段。
- 对于输入对象类型的每个输入字段
- 输入字段在该输入对象类型中必须具有唯一的名称;任何两个输入字段都不得共享相同的名称。
- 输入字段不得具有以字符 "__" (两个下划线)开头的名称。
- 输入字段必须接受 IsInputType(inputFieldType) 返回 true 的类型。
- 如果输入字段类型为非空且未定义默认值
@deprecated
指令不得应用于此输入字段。
- 如果输入对象直接或通过引用的输入对象引用自身,则引用链中的字段必须至少有一个是可为空类型或列表类型。
3.10.1输入对象扩展
输入对象类型扩展用于表示从先前输入对象类型扩展而来的输入对象类型。例如,GraphQL 服务本身是另一个 GraphQL 服务的扩展时,可以使用它。
类型验证
如果输入对象类型扩展定义不正确,则可能无效。
- 命名类型必须已定义,并且必须是输入对象类型。
- 输入对象类型扩展的所有字段都必须具有唯一的名称。
- 输入对象类型扩展的所有字段都不得已经是先前输入对象的字段。
- 提供的任何不可重复指令都不得已应用于先前的输入对象类型。
3.11列表
GraphQL 列表是一种特殊的集合类型,它声明列表中每个项目的类型(称为列表的项目类型)。列表值序列化为有序列表,其中列表中的每个项目都按照项目类型进行序列化。
要表示字段使用列表类型,项目类型用方括号括起来,如下所示: pets: [Pet]
。 允许嵌套列表: matrix: [[Int]]
。
结果强制
GraphQL 服务必须返回有序列表作为列表类型的结果。列表中的每个项目都必须是项目类型的结果强制转换的结果。如果无法进行合理的强制转换,则必须引发 字段错误。 特别是,如果返回非列表,则强制转换应失败,因为这表明类型系统和实现之间的期望不匹配。
如果列表的项目类型可为空,则在列表中单个项目的准备或强制转换期间发生的错误必须导致在该列表位置的值为 null,并在响应中添加 字段错误。 如果列表的项目类型为非空,则在列表中单个项目处发生的字段错误必须导致整个列表的字段错误。
输入强制
当预期作为输入时,仅当列表中的每个项目都可以被列表的项目类型接受时,才接受列表值。
如果作为列表类型的输入传递的值不是列表且不是 null 值,则输入强制转换的结果是大小为 1 的列表,其中单个项目值是对提供的列表的项目类型进行输入强制转换的结果(请注意,这可以递归地应用于嵌套列表)。
这允许接受一个或多个参数(有时称为“可变参数”)的输入将其输入类型声明为列表,而在单个值的常见情况下,客户端可以直接传递该值,而不是构造列表。
以下是各种列表类型和值的输入强制转换示例
预期类型 | 提供的值 | 强制转换的值 |
---|---|---|
[Int] |
[1, 2, 3] |
[1, 2, 3] |
[Int] |
[1, "b", true] |
错误:项目值不正确 |
[Int] |
1 |
[1] |
[Int] |
null |
null |
[[Int]] |
[[1], [2, 3]] |
[[1], [2, 3]] |
[[Int]] |
[1, 2, 3] |
[[1], [2], [3]] |
[[Int]] |
[1, null, 3] |
[[1], null, [3]] |
[[Int]] |
[[1], ["b"]] |
错误:项目值不正确 |
[[Int]] |
1 |
[[1]] |
[[Int]] |
null |
null |
3.12非空
默认情况下,GraphQL 中的所有类型都是可为空的;null 值对于上述所有类型都是有效的响应。要声明不允许为空的类型,可以使用 GraphQL 非空类型。 此类型包装了一个底层类型,并且此类型的行为与该包装类型完全相同,但 null 不是包装类型的有效响应。 尾部的感叹号用于表示使用非空类型的字段,如下所示: name: String!
。
可为空 vs. 可选
字段在 选择集 的上下文中始终是可选的,可以省略字段,并且选择集仍然有效(只要选择集不为空)。 但是,返回非空类型的字段在查询时永远不会返回 null 值。
输入(例如字段参数)默认情况下始终是可选的。 但是,非空输入类型是必需的。 除了不接受值 null 之外,它也不接受省略。 为了简单起见,可为空类型始终是可选的,而非空类型始终是必需的。
结果强制
在上述所有结果强制转换中,null 都被认为是有效值。 要强制转换非空类型的结果,应执行包装类型的强制转换。 如果该结果不是 null,则强制转换非空类型的结果就是该结果。 如果该结果是 null,则必须引发 字段错误。
输入强制
如果未提供非空类型的参数或输入对象字段,或者提供了文字值 null,或者提供了运行时未提供值或提供了值 null 的变量,则必须引发 请求错误。
如果为非空类型提供的值是 null 以外的文字值,或者是非空变量值,则使用包装类型的输入强制转换对其进行强制转换。
非空参数不能省略
Counter Example № 86{
fieldWithNonNullArg
}
值 null 不能提供给非空参数
Counter Example № 87{
fieldWithNonNullArg(nonNullArg: null)
}
可为空类型的变量不能提供给非空参数
Example № 88query withNullableVariable($var: String) {
fieldWithNonNullArg(nonNullArg: $var)
}
类型验证
- 非空类型不得包装另一个非空类型。
3.12.1组合列表和非空
列表和非空包装类型可以组合,表示更复杂的类型。 列表和非空类型的结果强制转换和输入强制转换规则以递归方式应用。
例如,如果列表的内部项目类型为非空(例如 [T!]
),则该列表可能不包含任何 null 项目。 但是,如果非空的内部类型是列表(例如 [T]!
),则不接受 null,但接受空列表。
以下是各种类型和值的结果强制转换示例
预期类型 | 内部值 | 强制转换的结果 |
---|---|---|
[Int] |
[1, 2, 3] |
[1, 2, 3] |
[Int] |
null |
null |
[Int] |
[1, 2, null] |
[1, 2, null] |
[Int] |
[1, 2, Error] |
[1, 2, null] (记录错误) |
[Int]! |
[1, 2, 3] |
[1, 2, 3] |
[Int]! |
null |
错误:值不能为空 |
[Int]! |
[1, 2, null] |
[1, 2, null] |
[Int]! |
[1, 2, Error] |
[1, 2, null] (记录错误) |
[Int!] |
[1, 2, 3] |
[1, 2, 3] |
[Int!] |
null |
null |
[Int!] |
[1, 2, null] |
null (记录强制转换错误) |
[Int!] |
[1, 2, Error] |
null (记录错误) |
[Int!]! |
[1, 2, 3] |
[1, 2, 3] |
[Int!]! |
null |
错误:值不能为空 |
[Int!]! |
[1, 2, null] |
错误:项目不能为空 |
[Int!]! |
[1, 2, Error] |
错误:项目中发生错误 |
3.13指令
QUERY |
MUTATION |
SUBSCRIPTION |
FIELD |
FRAGMENT_DEFINITION |
FRAGMENT_SPREAD |
INLINE_FRAGMENT |
VARIABLE_DEFINITION |
SCHEMA |
SCALAR |
OBJECT |
FIELD_DEFINITION |
ARGUMENT_DEFINITION |
INTERFACE |
UNION |
ENUM |
ENUM_VALUE |
INPUT_OBJECT |
INPUT_FIELD_DEFINITION |
GraphQL schema 描述了指令,指令用于注释 GraphQL 文档的各个部分,以指示验证器、执行器或客户端工具(例如代码生成器)应以不同的方式对其进行评估。
内置指令
内置指令 是本规范中定义的任何指令。
GraphQL 实现应提供 @skip
和 @include
指令。
如果 GraphQL 实现支持类型系统定义语言,则在表示 schema 的已弃用部分时,必须提供 @deprecated
指令。
如果 GraphQL 实现支持类型系统定义语言,则在表示自定义标量定义时,应提供 @specifiedBy
指令。
当使用类型系统定义语言表示 GraphQL schema 时,为了简洁起见,可以省略任何 内置指令。
当内省 GraphQL 服务时,所有提供的指令,包括任何 内置指令,都必须包含在返回的指令集中。
自定义指令
GraphQL 服务和客户端工具可以提供超出本文档中定义的任何其他 自定义指令。 指令是使用自定义或实验性行为扩展 GraphQL 的首选方式。
_
)。 例如,Facebook 的 GraphQL 服务使用的 自定义指令 应命名为 @fb_auth
而不是 @auth
。 对于本规范的拟议添加项,尤其建议这样做,因为这些添加项可能会在 RFC 流程 期间发生更改。 例如,正在进行中的 @live
版本应命名为 @rfc_live
。指令必须仅在其声明所属的位置使用。 在此示例中,定义了一个指令,该指令可用于注释字段
Example № 89directive @example on FIELD
fragment SomeFragment on SomeType {
field @example
}
指令位置可以使用可选的前导 |
字符进行定义,以帮助在表示更长的可能位置列表时进行格式化
Example № 90directive @example on
| FIELD
| FRAGMENT_SPREAD
| INLINE_FRAGMENT
指令还可以用于注释类型系统定义语言,这可以作为一种有用的工具来提供额外的元数据,以便生成 GraphQL 执行服务、生成客户端生成的运行时代码或 GraphQL 语义的许多其他有用的扩展。
在此示例中,指令 @example
注释了字段和参数定义
Example № 91directive @example on FIELD_DEFINITION | ARGUMENT_DEFINITION
type SomeType {
field(arg: Int @example): String @example
}
可以通过包含“repeatable”关键字将指令定义为可重复的。 当应在单个位置使用具有不同参数的同一指令时,可重复指令通常很有用,尤其是在需要通过指令向类型或 schema 扩展提供其他信息的情况下
Example № 92directive @delegateField(name: String!) repeatable on OBJECT | INTERFACE
type Book @delegateField(name: "pageCount") @delegateField(name: "author") {
id: ID!
}
extend type Book @delegateField(name: "index")
在定义指令时,它不得直接或间接地引用自身
Counter Example № 93directive @invalidExample(arg: String @invalidExample) on ARGUMENT_DEFINITION
验证
- 指令定义不得包含使用直接引用自身的指令。
- 指令定义不得包含使用间接引用自身的指令,即通过引用传递包含对此指令引用的类型或指令。
- 指令不得具有以字符 "__" (两个下划线)开头的名称。
- 对于指令的每个参数
- 该参数不得具有以字符 "__" (两个下划线) 开头的名称。
- 参数在该指令中必须具有唯一的名称;任何两个参数都不得共享相同的名称。
- 该参数必须接受一个类型,其中 IsInputType(argumentType) 返回 true。
3.13.1@skip
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
@skip
内置指令 可以为字段、片段展开和内联片段提供,并允许在执行期间进行条件排除,如 if
参数所述。
在此示例中,只有当变量 $someTest
的值为 false
时,才会查询 experimentalField
。
Example № 94query myQuery($someTest: Boolean!) {
experimentalField @skip(if: $someTest)
}
3.13.2@include
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
@include
内置指令 可以为字段、片段展开和内联片段提供,并允许在执行期间进行条件包含,如 if
参数所述。
在此示例中,只有当变量 $someTest
的值为 true
时,才会查询 experimentalField
。
Example № 95query myQuery($someTest: Boolean!) {
experimentalField @include(if: $someTest)
}
@skip
和 @include
均不具有优先权。如果同一字段或片段上同时提供了 @skip
和 @include
指令,则必须仅在 @skip
条件为 false 且 @include
条件为 true 时才查询。反之,如果 @skip
条件为 true 或 @include
条件为 false,则不得查询该字段或片段。3.13.3@deprecated
directive @deprecated(
reason: String! = "No longer supported"
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE
@deprecated
内置指令 在类型系统定义语言中使用,用于指示 GraphQL 服务模式中已弃用的部分,例如类型上的已弃用字段、字段上的参数、输入类型上的输入字段或枚举类型的值。
弃用包括弃用原因,该原因使用 Markdown 语法(由 CommonMark 指定)格式化。
在此类型定义示例中,oldField
已弃用,建议使用 newField
,而 oldArg
已弃用,建议使用 newArg
。
Example № 96type ExampleType {
newField: String
oldField: String @deprecated(reason: "Use `newField`.")
anotherField(
newArg: String
oldArg: String @deprecated(reason: "Use `newArg`.")
): String
}
@deprecated
指令不得出现在必需(非空且没有默认值)的参数或输入对象字段定义中。
Counter Example № 97type ExampleType {
invalidField(
newArg: String
oldArg: String! @deprecated(reason: "Use `newArg`.")
): String
}
要弃用必需的参数或输入字段,必须首先使其成为可选的,方法是将类型更改为可为空或添加默认值。
3.13.4@specifiedBy
directive @specifiedBy(url: String!) on SCALAR
@specifiedBy
内置指令 在类型系统定义语言中使用,用于为 自定义标量类型 提供 标量规范 URL,以指定其行为。该 URL 应指向数据格式、序列化和强制规则的人类可读规范。它不得出现在内置标量类型上。
在此示例中,为 UUID
自定义标量类型定义了一个 URL,指向相关的 IETF 规范。
Example № 98scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
4内省
GraphQL 服务支持对其模式进行内省。此模式本身使用 GraphQL 查询,从而为工具构建创建强大的平台。
以一个简单应用程序的示例请求为例。在本例中,有一个 User 类型,包含三个字段:id、name 和 birthday。
例如,给定一个具有以下类型定义的服务
Example № 99type User {
id: String
name: String
birthday: Date
}
包含以下操作的请求
Example № 100{
__type(name: "User") {
name
fields {
name
type {
name
}
}
}
}
将产生以下结果
Example № 101{
"__type": {
"name": "User",
"fields": [
{
"name": "id",
"type": { "name": "String" }
},
{
"name": "name",
"type": { "name": "String" }
},
{
"name": "birthday",
"type": { "name": "Date" }
}
]
}
}
保留名称
GraphQL 内省系统所需的类型和字段与用户定义的类型和字段在同一上下文中使用,并以 "__" 两个下划线为前缀。这样做是为了避免与用户定义的 GraphQL 类型发生命名冲突。
否则,GraphQL 类型系统中的任何 名称 都不得以两个下划线 "__" 开头。
4.1类型名称内省
GraphQL 支持在操作中的任何 选择集 内进行类型名称内省,但订阅操作根级别的选择除外。类型名称内省通过任何 Object、Interface 或 Union 上的元字段 __typename: String!
完成。它返回执行期间该点的具体 Object 类型的名称。
当针对 Interface 或 Union 类型进行查询以识别已返回的可能类型的实际 Object 类型时,这通常最有用。
作为元字段,__typename
是隐式的,不会出现在任何已定义类型的字段列表中。
__typename
不得作为订阅操作中的根字段包含。4.2模式内省
模式内省系统可以通过元字段 __schema
和 __type
访问,这些元字段可以从查询操作根类型的类型访问。
__schema: __Schema!
__type(name: String!): __Type
与所有元字段一样,这些字段是隐式的,不会出现在查询操作的根类型的字段列表中。
一流的文档
内省系统中的所有类型都提供 description
字段(类型为 String
),以允许类型设计者发布文档以及功能。GraphQL 服务可以使用 Markdown 语法(由 CommonMark 指定)返回 description
字段。因此,建议任何显示 description
的工具都使用符合 CommonMark 的 Markdown 渲染器。
弃用
为了支持向后兼容性管理,GraphQL 字段、参数、输入字段和枚举值可以指示它们是否已弃用(isDeprecated: Boolean!
),并提供弃用原因的描述(deprecationReason: String
)。
使用 GraphQL 内省构建的工具应通过隐藏信息或面向开发者的警告来阻止已弃用的用法,从而尊重弃用。
模式内省模式
模式内省系统本身表示为 GraphQL 模式。以下是提供模式内省的完整类型系统定义集,这些定义在下面的章节中完全定义。
type __Schema {
description: String
types: [__Type!]!
queryType: __Type!
mutationType: __Type
subscriptionType: __Type
directives: [__Directive!]!
}
type __Type {
kind: __TypeKind!
name: String
description: String
# must be non-null for OBJECT and INTERFACE, otherwise null.
fields(includeDeprecated: Boolean = false): [__Field!]
# must be non-null for OBJECT and INTERFACE, otherwise null.
interfaces: [__Type!]
# must be non-null for INTERFACE and UNION, otherwise null.
possibleTypes: [__Type!]
# must be non-null for ENUM, otherwise null.
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
# must be non-null for INPUT_OBJECT, otherwise null.
inputFields(includeDeprecated: Boolean = false): [__InputValue!]
# must be non-null for NON_NULL and LIST, otherwise null.
ofType: __Type
# may be non-null for custom SCALAR, otherwise null.
specifiedByURL: String
}
enum __TypeKind {
SCALAR
OBJECT
INTERFACE
UNION
ENUM
INPUT_OBJECT
LIST
NON_NULL
}
type __Field {
name: String!
description: String
args(includeDeprecated: Boolean = false): [__InputValue!]!
type: __Type!
isDeprecated: Boolean!
deprecationReason: String
}
type __InputValue {
name: String!
description: String
type: __Type!
defaultValue: String
isDeprecated: Boolean!
deprecationReason: String
}
type __EnumValue {
name: String!
description: String
isDeprecated: Boolean!
deprecationReason: String
}
type __Directive {
name: String!
description: String
locations: [__DirectiveLocation!]!
args(includeDeprecated: Boolean = false): [__InputValue!]!
isRepeatable: Boolean!
}
enum __DirectiveLocation {
QUERY
MUTATION
SUBSCRIPTION
FIELD
FRAGMENT_DEFINITION
FRAGMENT_SPREAD
INLINE_FRAGMENT
VARIABLE_DEFINITION
SCHEMA
SCALAR
OBJECT
FIELD_DEFINITION
ARGUMENT_DEFINITION
INTERFACE
UNION
ENUM
ENUM_VALUE
INPUT_OBJECT
INPUT_FIELD_DEFINITION
}
4.2.1__Schema 类型
__Schema
类型从 __schema
元字段返回,并提供有关 GraphQL 服务模式的所有信息。
字段
description
可以返回 String 或 null。queryType
是查询操作的根类型。mutationType
是 mutation 操作的根类型(如果支持)。否则为 null。subscriptionType
是 subscription 操作的根类型(如果支持)。否则为 null。types
必须返回此模式中包含的所有命名类型的集合。可以通过任何内省类型的字段找到的任何命名类型都必须包含在此集合中。directives
必须返回此模式中可用的所有指令的集合,包括所有内置指令。
4.2.2__Type 类型
__Type
是类型内省系统的核心,它表示系统中的所有类型:命名类型(例如 Scalars 和 Object 类型)和类型修饰符(例如 List 和 Non-Null 类型)。
类型修饰符用于修改字段 ofType
中呈现的类型。此修改后的类型可以递归地成为修改后的类型,表示列表、不可为空和它们的组合,最终修改命名类型。
有几种不同的类型。在每种类型中,实际上有效的字段都不同。所有可能的类型都列在 __TypeKind
枚举中。
下面的每个小节定义了给定 __TypeKind
枚举的每个可能值时 __Type
的预期字段
- "SCALAR"
- "OBJECT"
- "INTERFACE"
- "UNION"
- "ENUM"
- "INPUT_OBJECT"
- "LIST"
- "NON_NULL"
标量
表示标量类型,例如 Int、String 和 Boolean。标量不能有字段。
也表示 自定义标量,它们可以提供 specifiedByURL
作为 标量规范 URL。
字段
kind
必须返回__TypeKind.SCALAR
。name
必须返回 String。description
可以返回 String 或 null。specifiedByURL
可以为自定义标量返回 String(URL 形式),否则必须为 null。- 所有其他字段必须返回 null。
对象
对象类型表示字段集的具体实例化。内省类型(例如 __Type
、__Field
等)是对象的示例。
字段
kind
必须返回__TypeKind.OBJECT
。name
必须返回 String。description
可以返回 String 或 null。fields
必须返回可以为此类型选择的字段集。- 接受参数
includeDeprecated
,默认为 false。如果为 true,则也会返回已弃用的字段。
- 接受参数
interfaces
必须返回对象实现的接口集(如果没有,则interfaces
必须返回空集)。- 所有其他字段必须返回 null。
联合
联合是一种抽象类型,其中未声明公共字段。联合的可能类型在 possibleTypes
中显式列出。类型可以成为联合的一部分,而无需修改该类型。
字段
kind
必须返回__TypeKind.UNION
。name
必须返回 String。description
可以返回 String 或 null。possibleTypes
返回可以在此联合中表示的类型列表。它们必须是对象类型。- 所有其他字段必须返回 null。
接口
接口是一种抽象类型,其中声明了公共字段。任何实现接口的类型都必须定义所有命名字段,其中每个实现字段类型都等于或为接口类型的子类型(协变)。此接口的实现显式列在 possibleTypes
中。
字段
kind
必须返回__TypeKind.INTERFACE
。name
必须返回 String。description
可以返回 String 或 null。fields
必须返回此接口所需的字段集。- 接受参数
includeDeprecated
,默认为 false。如果为 true,则也会返回已弃用的字段。
- 接受参数
interfaces
必须返回接口实现的接口集(如果没有,则interfaces
必须返回空集)。possibleTypes
返回实现此接口的类型列表。它们必须是对象类型。- 所有其他字段必须返回 null。
枚举
枚举是特殊的标量,只能有一组定义的值。
字段
kind
必须返回__TypeKind.ENUM
。name
必须返回 String。description
可以返回 String 或 null。enumValues
必须将枚举值集作为__EnumValue
列表返回。必须至少有一个,并且它们必须具有唯一的名称。- 接受参数
includeDeprecated
,默认为 false。如果为 true,则也会返回已弃用的枚举值。
- 接受参数
- 所有其他字段必须返回 null。
输入对象
输入对象是复合类型,定义为命名输入值列表。它们仅用作参数和变量的输入,不能作为字段返回类型。
例如,输入对象 Point
可以定义为
Example № 102input Point {
x: Int
y: Int
}
字段
kind
必须返回__TypeKind.INPUT_OBJECT
。name
必须返回 String。description
可以返回 String 或 null。inputFields
必须将输入字段集作为__InputValue
列表返回。- 接受参数
includeDeprecated
,默认为 false。如果为 true,则也会返回已弃用的输入字段。
- 接受参数
- 所有其他字段必须返回 null。
列表
列表表示 GraphQL 中的值序列。List 类型是一种类型修饰符:它将另一个类型实例包装在 ofType
字段中,该字段定义列表中每个项的类型。
ofType
字段中的修改类型本身可以是修改类型,从而允许表示列表的列表或非空列表。
字段
kind
必须返回__TypeKind.LIST
。ofType
必须返回任何类型的类型。- 所有其他字段必须返回 null。
非空
GraphQL 类型是可为空的。值 null 对于字段类型是有效的响应。
Non-Null 类型是一种类型修饰符:它将另一个类型实例包装在 ofType
字段中。非空类型不允许 null 作为响应,并指示参数和输入对象字段的必需输入。
ofType
字段中的修改类型本身可以是修改后的 List 类型,从而允许表示非空列表。但是,它不得是修改后的 Non-Null 类型,以避免冗余的 Non-Null of Non-Null。
字段
kind
必须返回__TypeKind.NON_NULL
。ofType
必须返回除 Non-Null 之外的任何类型的类型。- 所有其他字段必须返回 null。
4.2.3__Field 类型
__Field
类型表示 Object 或 Interface 类型中的每个字段。
字段
name
必须返回 String。description
可以返回 String 或 null。args
返回__InputValue
列表,表示此字段接受的参数。- 接受参数
includeDeprecated
,默认为 false。如果为 true,则也会返回已弃用的参数。
- 接受参数
type
必须返回__Type
,表示此字段返回的值的类型。isDeprecated
如果此字段不应再使用,则返回 true,否则返回 false。deprecationReason
返回此字段被弃用的原因,如果此字段未被弃用,则返回 null。
4.2.4__InputValue 类型
__InputValue
类型表示字段和指令参数以及输入对象的 inputFields
。
字段
name
必须返回 String。description
可以返回 String 或 null。type
必须返回__Type
,表示此输入值期望的类型。defaultValue
可以返回一个 String 编码(使用 GraphQL 语言),表示在运行时未提供值的情况下此输入值使用的默认值。如果此输入值没有默认值,则返回 null。isDeprecated
如果此输入字段或参数不应再使用,则返回 true,否则返回 false。deprecationReason
返回此输入字段或参数被弃用的原因,如果输入字段或参数未被弃用,则返回 null。
4.2.5__EnumValue 类型
__EnumValue
类型表示枚举的可能值之一。
字段
name
必须返回 String。description
可以返回 String 或 null。isDeprecated
如果此枚举值不应再使用,则返回 true,否则返回 false。deprecationReason
返回此枚举值被弃用的原因,如果枚举值未被弃用,则返回 null。
4.2.6__Directive 类型
__Directive
类型表示服务支持的指令。
单个指令可能仅在显式支持的位置使用。所有可能的位置都列在 __DirectiveLocation
枚举中
- "QUERY"
- "MUTATION"
- "SUBSCRIPTION"
- "FIELD"
- "FRAGMENT_DEFINITION"
- "FRAGMENT_SPREAD"
- "INLINE_FRAGMENT"
- "VARIABLE_DEFINITION"
- "SCHEMA"
- "SCALAR"
- "OBJECT"
- "FIELD_DEFINITION"
- "ARGUMENT_DEFINITION"
- "INTERFACE"
- "UNION"
- "ENUM"
- "ENUM_VALUE"
- "INPUT_OBJECT"
- "INPUT_FIELD_DEFINITION"
字段
name
必须返回 String。description
可以返回 String 或 null。locations
返回__DirectiveLocation
列表,表示此指令可能放置的有效位置。args
返回__InputValue
列表,表示此指令接受的参数。- 接受参数
includeDeprecated
,默认为 false。如果为 true,则也会返回已弃用的参数。
- 接受参数
isRepeatable
必须返回一个布尔值,指示指令是否可以在单个位置重复使用。
5验证
GraphQL 服务不仅验证请求在语法上是否正确,还确保在给定 GraphQL 模式的上下文中,请求是明确且无错误的。
无效的请求在技术上仍然是可执行的,并且将始终产生执行部分中定义的算法所定义的稳定结果,但是相对于包含验证错误的请求,该结果可能是模棱两可、令人惊讶或意外的,因此执行应仅针对有效的请求发生。
通常,验证在请求执行之前立即在请求的上下文中执行,但是,如果已知完全相同的请求之前已通过验证,则 GraphQL 服务可以在不显式验证请求的情况下执行请求。例如:请求可以在开发期间进行验证,前提是它之后没有更改,或者服务可以验证一次请求并记忆结果,以避免将来再次验证相同的请求。任何客户端或开发时工具都应报告验证错误,并且不允许制定或执行已知在给定时间点无效的请求。
类型系统演变
随着 GraphQL 类型系统模式随着时间的推移通过添加新类型和新字段而发展,先前有效的请求可能稍后变得无效。任何可能导致先前有效的请求变为无效的更改都被认为是破坏性更改。鼓励 GraphQL 服务和模式维护者避免破坏性更改,但是为了更好地应对这些破坏性更改,复杂的 GraphQL 系统可能仍然允许执行在某个时间点已知没有任何验证错误并且自那时以来没有更改的请求。
示例
对于本模式的此部分,我们将假定以下类型系统以演示示例
Example № 103type Query {
dog: Dog
findDog(searchBy: FindDogInput): Dog
}
enum DogCommand {
SIT
DOWN
HEEL
}
type Dog implements Pet {
name: String!
nickname: String
barkVolume: Int
doesKnowCommand(dogCommand: DogCommand!): Boolean!
isHouseTrained(atOtherHomes: Boolean): Boolean!
owner: Human
}
interface Sentient {
name: String!
}
interface Pet {
name: String!
}
type Alien implements Sentient {
name: String!
homePlanet: String
}
type Human implements Sentient {
name: String!
pets: [Pet!]
}
enum CatCommand {
JUMP
}
type Cat implements Pet {
name: String!
nickname: String
doesKnowCommand(catCommand: CatCommand!): Boolean!
meowVolume: Int
}
union CatOrDog = Cat | Dog
union DogOrHuman = Dog | Human
union HumanOrAlien = Human | Alien
input FindDogInput {
name: String
owner: String
}
5.1文档
5.1.1可执行定义
正式规范
- 对于文档中的每个定义 definition
- definition 必须是 ExecutableDefinition(它不能是 TypeSystemDefinitionOrExtension)。
解释性文本
GraphQL 执行将仅考虑可执行定义 Operation 和 Fragment。类型系统定义和扩展不可执行,并且在执行期间不予考虑。
为了避免歧义,包含 TypeSystemDefinitionOrExtension 的文档对于执行无效。
不打算直接执行的 GraphQL 文档可能包含 TypeSystemDefinitionOrExtension。
例如,以下文档对于执行无效,因为原始执行模式可能不知道提供的类型扩展
Counter Example № 104query getDogName {
dog {
name
color
}
}
extend type Dog {
color: String
}
5.2操作
5.2.1命名操作定义
5.2.1.1操作名称唯一性
正式规范
- 对于文档中的每个操作定义 operation
- 令 operationName 为 operation 的名称。
- 如果 operationName 存在
- 令 operations 为文档中所有名为 operationName 的操作定义。
- operations 必须是单个集合。
解释性文本
每个命名操作定义在其名称引用时必须在文档中是唯一的。
例如,以下文档是有效的
Example № 105query getDogName {
dog {
name
}
}
query getOwnerName {
dog {
owner {
name
}
}
}
而此文档是无效的
Counter Example № 106query getName {
dog {
name
}
}
query getName {
dog {
owner {
name
}
}
}
即使每个操作的类型不同,它也是无效的
Counter Example № 107query dogOperation {
dog {
name
}
}
mutation dogOperation {
mutateDog {
id
}
}
5.2.2匿名操作定义
5.2.2.1单独的匿名操作
正式规范
- 令 operations 为文档中的所有操作定义。
- 令 anonymous 为文档中的所有匿名操作定义。
- 如果 operations 是包含多个元素的集合
- anonymous 必须为空。
解释性文本
当文档中仅存在一个查询操作时,GraphQL 允许使用简写形式来定义查询操作。
例如,以下文档是有效的
Example № 108{
dog {
name
}
}
而此文档是无效的
Counter Example № 109{
dog {
name
}
}
query getName {
dog {
owner {
name
}
}
}
5.2.3订阅操作定义
5.2.3.1单根字段
正式规范
- 令 subscriptionType 为 schema 中的根 Subscription 类型。
- 对于文档中的每个订阅操作定义 subscription
- 令 selectionSet 为 subscription 上的顶层选择集。
- 令 variableValues 为空集。
- 令 groupedFieldSet 为 CollectFields(subscriptionType, selectionSet, variableValues) 的结果。
- groupedFieldSet 必须恰好有一个条目,该条目不得为内省字段。
解释性文本
订阅操作必须恰好有一个根字段。
有效示例
Example № 110subscription sub {
newMessage {
body
sender
}
}
Example № 111subscription sub {
...newMessageFields
}
fragment newMessageFields on Subscription {
newMessage {
body
sender
}
}
无效
Counter Example № 112subscription sub {
newMessage {
body
sender
}
disallowedSecondRootField
}
Counter Example № 113subscription sub {
...multipleSubscriptions
}
fragment multipleSubscriptions on Subscription {
newMessage {
body
sender
}
disallowedSecondRootField
}
订阅操作的根字段不得为内省字段。以下示例也无效
Counter Example № 114subscription sub {
__typename
}
5.3字段
5.3.1字段选择
字段选择必须存在于 Object、Interface 和 Union 类型上。
正式规范
- 对于文档中的每个 selection
- 令 fieldName 为 selection 的目标字段。
- fieldName 必须在作用域内的类型上定义。
解释性文本
字段选择的目标字段必须在选择集的作用域类型上定义。别名名称没有限制。
例如,以下片段将无法通过验证
Counter Example № 115fragment fieldNotDefined on Dog {
meowVolume
}
fragment aliasedLyingFieldTargetNotDefined on Dog {
barkVolume: kawVolume
}
对于接口,直接字段选择只能在字段上完成。具体实现者的字段与给定接口类型选择集的有效性无关。
例如,以下是有效的
Example № 116fragment interfaceFieldSelection on Pet {
name
}
而以下是无效的
Counter Example № 117fragment definedOnImplementersButNotInterface on Pet {
nickname
}
由于联合不定义字段,因此不得直接从联合类型选择集中选择字段,但元字段 __typename 除外。来自联合类型选择集的字段必须仅通过片段间接查询。
例如,以下是有效的
Example № 118fragment inDirectFieldSelectionOnUnion on CatOrDog {
__typename
... on Pet {
name
}
... on Dog {
barkVolume
}
}
但以下是无效的
Counter Example № 119fragment directFieldSelectionOnUnion on CatOrDog {
name
barkVolume
}
5.3.2字段选择合并
正式规范
- 令 set 为 GraphQL 文档中定义的任何选择集。
- FieldsInSetCanMerge(set) 必须为 true。
- 令 fieldsForName 为 set 中具有给定响应名称的选择集,包括访问片段和内联片段。
- 给定 fieldsForName 中每对不同的成员 fieldA 和 fieldB
- SameResponseShape(fieldA, fieldB) 必须为 true。
- 如果 fieldA 和 fieldB 的父类型相等,或者其中任何一个不是 Object 类型
- fieldA 和 fieldB 必须具有相同的字段名称。
- fieldA 和 fieldB 必须具有相同的参数集。
- 令 mergedSet 为添加 fieldA 的选择集和 fieldB 的选择集的结果。
- FieldsInSetCanMerge(mergedSet) 必须为 true。
- 令 typeA 为 fieldA 的返回类型。
- 令 typeB 为 fieldB 的返回类型。
- 如果 typeA 或 typeB 为 Non-Null
- 如果 typeA 或 typeB 为可为空,则返回 false。
- 令 typeA 为 typeA 的可为空类型。
- 令 typeB 为 typeB 的可为空类型。
- 如果 typeA 或 typeB 为 List
- 如果 typeA 或 typeB 不是 List,则返回 false。
- 令 typeA 为 typeA 的项类型。
- 令 typeB 为 typeB 的项类型。
- 从步骤 3 重复。
- 如果 typeA 或 typeB 为 Scalar 或 Enum
- 如果 typeA 和 typeB 是相同类型,则返回 true,否则返回 false。
- 断言:typeA 是对象、联合或接口类型。
- 断言:typeB 是对象、联合或接口类型。
- 令 mergedSet 为添加 fieldA 的选择集和 fieldB 的选择集的结果。
- 令 fieldsForName 为 mergedSet 中具有给定响应名称的选择集,包括访问片段和内联片段。
- 给定 fieldsForName 中每对不同的成员 subfieldA 和 subfieldB
- 如果 SameResponseShape(subfieldA, subfieldB) 为 false,则返回 false。
- 返回 true。
解释性文本
如果在执行期间遇到具有相同响应名称的多个字段选择,则要执行的字段和参数以及结果值应明确。因此,如果两个字段选择都可能针对同一对象遇到,则仅当它们等效时才有效。
在执行期间,具有相同响应名称的字段的同步执行是通过 MergeSelectionSets() 和 CollectFields() 完成的。
对于简单的手写 GraphQL,此规则显然是明显的开发者错误,但是嵌套片段会使手动检测变得困难。
以下选择正确合并
Example № 120fragment mergeIdenticalFields on Dog {
name
name
}
fragment mergeIdenticalAliasesAndFields on Dog {
otherName: name
otherName: name
}
以下无法合并
Counter Example № 121fragment conflictingBecauseAlias on Dog {
name: nickname
name
}
如果具有相同的参数,则相同的字段也会合并。值和变量都可以正确合并。
例如,以下正确合并
Example № 122fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: SIT)
}
fragment mergeIdenticalFieldsWithIdenticalValues on Dog {
doesKnowCommand(dogCommand: $dogCommand)
doesKnowCommand(dogCommand: $dogCommand)
}
以下内容无法正确合并
Counter Example № 123fragment conflictingArgsOnValues on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: HEEL)
}
fragment conflictingArgsValueAndVar on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: $dogCommand)
}
fragment conflictingArgsWithVars on Dog {
doesKnowCommand(dogCommand: $varOne)
doesKnowCommand(dogCommand: $varTwo)
}
fragment differingArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand
}
以下字段不会合并在一起,但是两者都不能针对同一对象遇到,因此它们是安全的
Example № 124fragment safeDifferingFields on Pet {
... on Dog {
volume: barkVolume
}
... on Cat {
volume: meowVolume
}
}
fragment safeDifferingArgs on Pet {
... on Dog {
doesKnowCommand(dogCommand: SIT)
}
... on Cat {
doesKnowCommand(catCommand: JUMP)
}
}
但是,字段响应必须是可以合并的形状。例如,叶子类型不得不同。在此示例中,someValue
可能是 String
或 Int
Counter Example № 125fragment conflictingDifferingResponses on Pet {
... on Dog {
someValue: nickname
}
... on Cat {
someValue: meowVolume
}
}
5.3.3叶子字段选择
正式规范
- 对于文档中的每个 selection
- 令 selectionType 为 selection 的解包结果类型。
- 如果 selectionType 是标量或枚举
- 该选择的子选择集必须为空。
- 如果 selectionType 是接口、联合或对象
- 该选择的子选择集不能为空。
解释性文本
叶子字段不允许字段子选择。叶子字段是任何具有标量或枚举解包类型的字段。
以下是有效的。
Example № 126fragment scalarSelection on Dog {
barkVolume
}
以下是无效的。
Counter Example № 127fragment scalarSelectionsNotAllowedOnInt on Dog {
barkVolume {
sinceWhen
}
}
相反,非叶子字段必须具有字段子选择。非叶子字段是任何具有对象、接口或联合解包类型的字段。
让我们假设以下添加到模式的查询根操作类型
Example № 128extend type Query {
human: Human
pet: Pet
catOrDog: CatOrDog
}
以下示例无效,因为它们包含没有字段子选择的非叶子字段。
Counter Example № 129query directQueryOnObjectWithoutSubFields {
human
}
query directQueryOnInterfaceWithoutSubFields {
pet
}
query directQueryOnUnionWithoutSubFields {
catOrDog
}
但是,以下示例是有效的,因为它包含字段子选择。
Example № 130query directQueryOnObjectWithSubFields {
human {
name
}
}
5.4参数
参数提供给字段和指令。以下验证规则在两种情况下都适用。
5.4.1参数名称
正式规范
- 对于文档中的每个 argument
- 令 argumentName 为 argument 的名称。
- 令 argumentDefinition 为父字段或名为 argumentName 的定义提供的参数定义。
- argumentDefinition 必须存在。
解释性文本
提供给字段或指令的每个参数都必须在字段或指令的可能参数集中定义。
例如,以下是有效的
Example № 131fragment argOnRequiredArg on Dog {
doesKnowCommand(dogCommand: SIT)
}
fragment argOnOptional on Dog {
isHouseTrained(atOtherHomes: true) @include(if: true)
}
以下是无效的,因为 command
未在 DogCommand
上定义。
Counter Example № 132fragment invalidArgName on Dog {
doesKnowCommand(command: CLEAN_UP_HOUSE)
}
这也是无效的,因为 unless
未在 @include
上定义。
Counter Example № 133fragment invalidArgName on Dog {
isHouseTrained(atOtherHomes: true) @include(unless: false)
}
为了探索更复杂的参数示例,让我们将以下内容添加到我们的类型系统
Example № 134type Arguments {
multipleRequirements(x: Int!, y: Int!): Int!
booleanArgField(booleanArg: Boolean): Boolean
floatArgField(floatArg: Float): Float
intArgField(intArg: Int): Int
nonNullBooleanArgField(nonNullBooleanArg: Boolean!): Boolean!
booleanListArgField(booleanListArg: [Boolean]!): [Boolean]
optionalNonNullBooleanArgField(optionalBooleanArg: Boolean! = false): Boolean!
}
extend type Query {
arguments: Arguments
}
参数中的顺序无关紧要。因此,以下两个示例都是有效的。
Example № 135fragment multipleArgs on Arguments {
multipleRequirements(x: 1, y: 2)
}
fragment multipleArgsReverseOrder on Arguments {
multipleRequirements(y: 2, x: 1)
}
5.4.2参数唯一性
字段和指令将参数视为参数名称到值的映射。参数集中具有相同名称的多个参数是模棱两可且无效的。
正式规范
- 对于文档中的每个 argument
- 令 argumentName 为 argument 的名称。
- 令 arguments 为包含 argument 的参数集中所有名为 argumentName 的参数。
- arguments 必须是仅包含 argument 的集合。
5.4.3必需参数
- 对于文档中的每个字段或指令
- 设 arguments 为字段或指令提供的参数。
- 设 argumentDefinitions 为该字段或指令的参数定义集合。
- 对于 argumentDefinitions 中的每个 argumentDefinition
- 设 type 为 argumentDefinition 的预期类型。
- 设 defaultValue 为 argumentDefinition 的默认值。
- 如果 type 为非空且 defaultValue 不存在
- 设 argumentName 为 argumentDefinition 的名称。
- 设 argument 为 arguments 中名为 argumentName 的参数。
- argument 必须存在。
- 设 value 为 argument 的值。
- value 不得为 null 字面量。
解释性文本
参数可以是必需的。如果参数类型为非空且没有默认值,则参数为必需的。否则,参数是可选的。
例如,以下是有效的
Example № 136fragment goodBooleanArg on Arguments {
booleanArgField(booleanArg: true)
}
fragment goodNonNullArg on Arguments {
nonNullBooleanArgField(nonNullBooleanArg: true)
}
对于可空参数的字段,可以省略参数。
因此,以下片段是有效的
Example № 137fragment goodBooleanArgDefault on Arguments {
booleanArgField
}
但对于必需参数而言,这是无效的。
Counter Example № 138fragment missingRequiredArg on Arguments {
nonNullBooleanArgField
}
提供显式值 null 也是无效的,因为必需参数始终具有非空类型。
Counter Example № 139fragment missingRequiredArg on Arguments {
nonNullBooleanArgField(nonNullBooleanArg: null)
}
5.5片段
5.5.1片段声明
5.5.1.1片段名称唯一性
正式规范
- 对于文档中的每个片段定义 fragment
- 设 fragmentName 为 fragment 的名称。
- 设 fragments 为文档中所有名为 fragmentName 的片段定义。
- fragments 必须是一个单元素集合。
解释性文本
片段定义通过名称在片段展开中被引用。为避免歧义,每个片段的名称在文档中必须是唯一的。
内联片段不被视为片段定义,并且不受此验证规则的影响。
例如,以下文档是有效的
Example № 140{
dog {
...fragmentOne
...fragmentTwo
}
}
fragment fragmentOne on Dog {
name
}
fragment fragmentTwo on Dog {
owner {
name
}
}
而此文档是无效的
Counter Example № 141{
dog {
...fragmentOne
}
}
fragment fragmentOne on Dog {
name
}
fragment fragmentOne on Dog {
owner {
name
}
}
5.5.1.2片段展开类型存在性
正式规范
- 对于文档中的每个命名展开 namedSpread
- 设 fragment 为 namedSpread 的目标。
- fragment 的目标类型必须在模式中定义。
解释性文本
片段必须在模式中存在的类型上指定。这适用于命名片段和内联片段。如果它们未在模式中定义,则片段无效。
例如,以下片段是有效的
Example № 142fragment correctType on Dog {
name
}
fragment inlineFragment on Dog {
... on Dog {
name
}
}
fragment inlineFragment2 on Dog {
... @include(if: true) {
name
}
}
而以下片段则无法通过验证
Counter Example № 143fragment notOnExistingType on NotInSchema {
name
}
fragment inlineNotExistingType on Dog {
... on NotInSchema {
name
}
}
5.5.1.3对象、接口或联合类型上的片段
正式规范
- 对于文档中定义的每个 fragment
- 片段的目标类型必须具有 UNION、INTERFACE 或 OBJECT 类型。
解释性文本
片段只能在联合、接口和对象上声明。它们在标量上是无效的。它们只能应用于非叶子字段。此规则适用于内联片段和命名片段。
以下片段声明是有效的
Example № 144fragment fragOnObject on Dog {
name
}
fragment fragOnInterface on Pet {
name
}
fragment fragOnUnion on CatOrDog {
... on Dog {
name
}
}
而以下片段声明是无效的
Counter Example № 145fragment fragOnScalar on Int {
something
}
fragment inlineFragOnScalar on Dog {
... on Boolean {
somethingElse
}
}
5.5.1.4片段必须被使用
正式规范
- 对于文档中定义的每个 fragment
- fragment 必须是文档中至少一个展开的目标。
解释性文本
已定义的片段必须在文档中使用。
例如,以下是一个无效的文档
Counter Example № 146fragment nameFragment on Dog { # unused
name
}
{
dog {
name
}
}
5.5.2片段展开
字段选择也由将片段展开到彼此之中来确定。目标片段的选择集被合并到引用目标片段的级别的选择集中。
5.5.2.1片段展开目标已定义
正式规范
- 对于文档中的每个 namedSpread
- 设 fragment 为 namedSpread 的目标。
- fragment 必须在文档中定义。
解释性文本
命名片段展开必须引用文档中定义的片段。如果展开的目标未定义,则会发生验证错误。
Counter Example № 147{
dog {
...undefinedFragment
}
}
5.5.2.2片段展开不得形成循环
正式规范
- 对于文档中的每个 fragmentDefinition
- 设 visited 为空集。
- DetectFragmentCycles(fragmentDefinition, visited).
- 设 spreads 为 fragmentDefinition 的所有片段展开后代。
- 对于 spreads 中的每个 spread
- visited 不得包含 spread。
- 设 nextVisited 为包含 spread 和 visited 成员的集合。
- 设 nextFragmentDefinition 为 spread 的目标。
- DetectFragmentCycles(nextFragmentDefinition, nextVisited).
解释性文本
片段展开图不得形成任何循环,包括自身展开。否则,操作可能会无限展开或在底层数据中的循环上无限执行。
这将使导致无限展开的片段无效
Counter Example № 148{
dog {
...nameFragment
}
}
fragment nameFragment on Dog {
name
...barkVolumeFragment
}
fragment barkVolumeFragment on Dog {
barkVolume
...nameFragment
}
如果以上片段被内联,这将导致无限大的结果
Example № 149{
dog {
name
barkVolume
name
barkVolume
name
barkVolume
name
# forever...
}
}
这也使在循环数据上执行时会导致无限递归的片段无效
Counter Example № 150{
dog {
...dogFragment
}
}
fragment dogFragment on Dog {
name
owner {
...ownerFragment
}
}
fragment ownerFragment on Human {
name
pets {
...dogFragment
}
}
5.5.2.3片段展开是可能的
正式规范
- 对于文档中定义的每个 spread(命名或内联)
- 设 fragment 为 spread 的目标。
- 设 fragmentType 为 fragment 的类型条件。
- 设 parentType 为包含 spread 的选择集的类型。
- 设 applicableTypes 为 GetPossibleTypes(fragmentType) 和 GetPossibleTypes(parentType) 的交集。
- applicableTypes 不得为空。
- 如果 type 是对象类型,则返回包含 type 的集合。
- 如果 type 是接口类型,则返回实现 type 的类型集合。
- 如果 type 是联合类型,则返回 type 的可能类型集合。
解释性文本
片段在类型上声明,并且仅当运行时对象类型与类型条件匹配时才适用。它们也在父类型的上下文中展开。只有当片段的类型条件有可能在父类型内应用时,片段展开才是有效的。
5.5.2.3.1对象范围内的对象展开
在对象类型的范围内,唯一有效的对象类型片段展开是应用于范围内同一类型的片段展开。
例如
Example № 151fragment dogFragment on Dog {
... on Dog {
barkVolume
}
}
而以下是无效的
Counter Example № 152fragment catInDogFragmentInvalid on Dog {
... on Cat {
meowVolume
}
}
5.5.2.3.2对象范围内的抽象展开
在对象类型的范围内,如果对象类型实现了接口或是联合的成员,则可以使用联合或接口展开。
例如
Example № 153fragment petNameFragment on Pet {
name
}
fragment interfaceWithinObjectFragment on Dog {
...petNameFragment
}
是有效的,因为 Dog 实现了 Pet。
同样
Example № 154fragment catOrDogNameFragment on CatOrDog {
... on Cat {
meowVolume
}
}
fragment unionWithObjectFragment on Dog {
...catOrDogNameFragment
}
是有效的,因为 Dog 是 CatOrDog 联合的成员。值得注意的是,如果检查 CatOrDogNameFragment 的内容,您可能会注意到永远不会返回有效的結果。但是,我们不将其指定为无效,因为我们只考虑片段声明,而不考虑其主体。
5.5.2.3.3抽象范围内的对象展开
联合或接口展开可以在对象类型片段的上下文中使用,但前提是对象类型是该接口或联合的可能类型之一。
例如,以下片段是有效的
Example № 155fragment petFragment on Pet {
name
... on Dog {
barkVolume
}
}
fragment catOrDogFragment on CatOrDog {
... on Cat {
meowVolume
}
}
petFragment 是有效的,因为 Dog 实现了接口 Pet。catOrDogFragment 是有效的,因为 Cat 是 CatOrDog 联合的成员。
相比之下,以下片段是无效的
Counter Example № 156fragment sentientFragment on Sentient {
... on Dog {
barkVolume
}
}
fragment humanOrAlienFragment on HumanOrAlien {
... on Cat {
meowVolume
}
}
Dog 未实现接口 Sentient,因此 sentientFragment 永远无法返回有意义的结果。因此,片段是无效的。同样,Cat 不是联合 HumanOrAlien 的成员,它也永远无法返回有意义的结果,使其无效。
5.5.2.3.4抽象范围内的抽象展开
联合或接口片段可以相互使用。只要在作用域和展开的可能类型的交集中至少存在一个对象类型,该展开就被认为是有效的。
例如
Example № 157fragment unionWithInterface on Pet {
...dogOrHumanFragment
}
fragment dogOrHumanFragment on DogOrHuman {
... on Dog {
barkVolume
}
}
被认为是有效的,因为 Dog 实现了接口 Pet 并且是 DogOrHuman 的成员。
然而
Counter Example № 158fragment nonIntersectingInterfaces on Pet {
...sentientFragment
}
fragment sentientFragment on Sentient {
name
}
是无效的,因为不存在实现 Pet 和 Sentient 两者的类型。
接口范围中接口展开
此外,接口类型片段始终可以展开到它实现的接口范围中。
在下面的示例中,...resourceFragment
片段展开是有效的,因为 Resource
实现了 Node
。
Example № 159interface Node {
id: ID!
}
interface Resource implements Node {
id: ID!
url: String
}
fragment interfaceWithInterface on Node {
...resourceFragment
}
fragment resourceFragment on Resource {
url
}
5.6值
5.6.1正确类型的值
正式规范
- 对于文档中的每个输入值 value
- 设 type 为在找到 value 的位置期望的类型。
- value 必须可强制转换为 type。
解释性文本
字面量值必须与在类型系统章节中定义的强制转换规则一致,与它们所在位置期望的类型兼容。
位置期望的类型包括为值提供的参数定义的类型、为值提供的输入对象字段定义的类型,以及为其提供默认值的变量定义的类型。
以下示例是值字面量的有效用法
Example № 160fragment goodBooleanArg on Arguments {
booleanArgField(booleanArg: true)
}
fragment coercedIntIntoFloatArg on Arguments {
# Note: The input coercion rules for Float allow Int literals.
floatArgField(floatArg: 123)
}
query goodComplexDefaultValue($search: FindDogInput = { name: "Fido" }) {
findDog(searchBy: $search) {
name
}
}
不可强制转换的值(例如,将字符串转换为整数)是无效的。以下示例是无效的
Counter Example № 161fragment stringIntoInt on Arguments {
intArgField(intArg: "123")
}
query badComplexValue {
findDog(searchBy: { name: 123 }) {
name
}
}
5.6.2输入对象字段名称
正式规范
- 对于文档中的每个输入对象字段 inputField
- 设 inputFieldName 为 inputField 的名称。
- 设 inputFieldDefinition 为父输入对象类型提供的名为 inputFieldName 的输入字段定义。
- inputFieldDefinition 必须存在。
解释性文本
输入对象值中提供的每个输入字段都必须在输入对象预期类型的可能字段集中定义。
例如,以下示例输入对象是有效的
Example № 162{
findDog(searchBy: { name: "Fido" }) {
name
}
}
而以下示例输入对象使用了在预期类型上未定义的字段 “favoriteCookieFlavor”
Counter Example № 163{
findDog(searchBy: { favoriteCookieFlavor: "Bacon" }) {
name
}
}
5.6.3输入对象字段唯一性
正式规范
- 对于文档中的每个输入对象值 inputObject
- 对于 inputObject 中的每个 inputField
- 设 name 为 inputField 的名称。
- 设 fields 为 inputObject 中所有名为 name 的输入对象字段。
- fields 必须是仅包含 inputField 的集合。
- 对于 inputObject 中的每个 inputField
解释性文本
输入对象不得包含多个同名字段,否则会存在歧义,其中包括语法的忽略部分。
例如,以下文档将无法通过验证。
Counter Example № 164{
field(arg: { field: true, field: false })
}
5.6.4输入对象必需字段
正式规范
- 对于文档中的每个输入对象
- 设 fields 为该输入对象提供的字段。
- 设 fieldDefinitions 为该输入对象的输入字段定义集合。
- 对于 fieldDefinitions 中的每个 fieldDefinition
- 设 type 为 fieldDefinition 的预期类型。
- 设 defaultValue 为 fieldDefinition 的默认值。
- 如果 type 为非空且 defaultValue 不存在
- 设 fieldName 为 fieldDefinition 的名称。
- 设 field 为 fields 中名为 fieldName 的输入字段。
- field 必须存在。
- 设 value 为 field 的值。
- value 不得为 null 字面量。
解释性文本
输入对象字段可以是必需的。就像字段可能具有必需参数一样,输入对象也可能具有必需字段。如果输入字段具有非空类型且没有默认值,则该字段是必需的。否则,输入对象字段是可选的。
5.7指令
5.7.1指令已定义
正式规范
- 对于文档中的每个 directive
- 设 directiveName 为 directive 的名称。
- 设 directiveDefinition 为名为 directiveName 的指令。
- directiveDefinition 必须存在。
解释性文本
GraphQL 服务定义它们支持哪些指令。对于指令的每次使用,该服务上都必须提供该指令。
5.7.2指令位于有效位置
正式规范
- 对于文档中的每个 directive
- 设 directiveName 为 directive 的名称。
- 设 directiveDefinition 为名为 directiveName 的指令。
- 设 locations 为 directiveDefinition 的有效位置。
- 设 adjacent 为指令影响的 AST 节点。
- adjacent 必须由 locations 中的一个项表示。
解释性文本
GraphQL 服务定义它们支持哪些指令以及在何处支持它们。对于指令的每次使用,指令都必须在服务已声明支持的位置使用。
例如,以下文档将无法通过验证,因为 @skip
未提供 QUERY
作为有效位置。
Counter Example № 165query @skip(if: $foo) {
field
}
5.7.3每个位置的指令都是唯一的
正式规范
- 对于文档中指令可以应用的每个 location
- 设 directives 为应用于 location 且不可重复的指令集合。
- 对于 directives 中的每个 directive
- 设 directiveName 为 directive 的名称。
- 设 namedDirectives 为 directives 中所有名为 directiveName 的指令集合。
- namedDirectives 必须是一个单元素集合。
解释性文本
GraphQL 允许定义为 repeatable
的指令在其应用到的定义上多次使用,可能带有不同的参数。相反,如果指令不是 repeatable
,则每个位置只允许出现一次。
例如,以下文档将无法通过验证,因为不可重复的 @skip
已对同一字段使用了两次
Counter Example № 166query ($foo: Boolean = true, $bar: Boolean = false) {
field @skip(if: $foo) @skip(if: $bar)
}
但是,以下示例是有效的,因为 @skip
每个位置仅使用一次,尽管在操作中和在同一命名字段上使用了两次
Example № 167query ($foo: Boolean = true, $bar: Boolean = false) {
field @skip(if: $foo) {
subfieldA
}
field @skip(if: $bar) {
subfieldB
}
}
5.8变量
5.8.1变量唯一性
正式规范
- 对于文档中的每个 operation
- 对于在 operation 上定义的每个 variable
- 设 variableName 为 variable 的名称。
- 设 variables 为在 operation 上所有名为 variableName 的变量集合。
- variables 必须是一个单元素集合。
- 对于在 operation 上定义的每个 variable
解释性文本
如果任何操作定义了多个同名变量,则它是含糊不清且无效的。即使重复变量的类型相同,它也是无效的。
Counter Example № 168query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) {
dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
}
多个操作定义具有相同名称的变量是有效的。如果两个操作引用相同的片段,则实际上可能是必要的
Example № 169query A($atOtherHomes: Boolean) {
...HouseTrainedFragment
}
query B($atOtherHomes: Boolean) {
...HouseTrainedFragment
}
fragment HouseTrainedFragment on Query {
dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
}
5.8.2变量是输入类型
正式规范
- 对于 document 中的每个 operation
- 对于每个 operation 上的每个 variable
- 设 variableType 为 variable 的类型。
- IsInputType(variableType) 必须为 true。
- 对于每个 operation 上的每个 variable
解释性文本
变量只能是输入类型。对象、联合和接口不能用作输入。
对于这些示例,请考虑以下类型系统添加
Example № 170extend type Query {
booleanList(booleanListArg: [Boolean!]): Boolean
}
以下操作是有效的
Example № 171query takesBoolean($atOtherHomes: Boolean) {
dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
}
query takesComplexInput($search: FindDogInput) {
findDog(searchBy: $search) {
name
}
}
query TakesListOfBooleanBang($booleans: [Boolean!]) {
booleanList(booleanListArg: $booleans)
}
以下操作是无效的
Counter Example № 172query takesCat($cat: Cat) {
# ...
}
query takesDogBang($dog: Dog!) {
# ...
}
query takesListOfPet($pets: [Pet]) {
# ...
}
query takesCatOrDog($catOrDog: CatOrDog) {
# ...
}
5.8.3所有变量使用已定义
正式规范
- 对于文档中的每个 operation
- 对于作用域中的每个 variableUsage,变量必须在 operation 的变量列表中。
- 设 fragments 为该 operation 传递引用的每个片段。
- 对于 fragments 中的每个 fragment
- 对于 fragment 作用域中的每个 variableUsage,变量必须在 operation 的变量列表中。
解释性文本
变量的作用域是基于每个操作的。这意味着在操作上下文中使用的任何变量都必须在该操作的顶层定义
例如
Example № 173query variableIsDefined($atOtherHomes: Boolean) {
dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
}
是有效的。$atOtherHomes 由操作定义。
相比之下,以下文档是无效的
Counter Example № 174query variableIsNotDefined {
dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
}
$atOtherHomes 未由操作定义。
片段使此规则复杂化。操作传递包含的任何片段都可以访问该操作定义的变量。片段可以出现在多个操作中,因此变量的使用必须与所有这些操作中的变量定义相对应。
例如,以下是有效的
Example № 175query variableIsDefinedUsedInSingleFragment($atOtherHomes: Boolean) {
dog {
...isHouseTrainedFragment
}
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
因为 isHouseTrainedFragment 在操作 variableIsDefinedUsedInSingleFragment 的上下文中使用,并且该变量由该操作定义。
另一方面,如果片段包含在未定义引用变量的操作中,则文档无效。
Counter Example № 176query variableIsNotDefinedUsedInSingleFragment {
dog {
...isHouseTrainedFragment
}
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
这也适用于传递,因此以下操作也失败
Counter Example № 177query variableIsNotDefinedUsedInNestedFragment {
dog {
...outerHouseTrainedFragment
}
}
fragment outerHouseTrainedFragment on Dog {
...isHouseTrainedFragment
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
变量必须在片段使用的所有操作中定义。
Example № 178query houseTrainedQueryOne($atOtherHomes: Boolean) {
dog {
...isHouseTrainedFragment
}
}
query houseTrainedQueryTwo($atOtherHomes: Boolean) {
dog {
...isHouseTrainedFragment
}
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
但是,以下操作无法通过验证
Counter Example № 179query houseTrainedQueryOne($atOtherHomes: Boolean) {
dog {
...isHouseTrainedFragment
}
}
query houseTrainedQueryTwoNotDefined {
dog {
...isHouseTrainedFragment
}
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
这是因为 houseTrainedQueryTwoNotDefined 未定义变量 $atOtherHomes,但该变量由 isHouseTrainedFragment 使用,而 isHouseTrainedFragment 包含在该操作中。
5.8.4所有变量已使用
正式规范
- 对于文档中的每个 operation
- 设 variables 为该 operation 定义的变量。
- variables 中的每个 variable 必须在操作作用域本身或该操作传递引用的任何片段中至少使用一次。
解释性文本
操作定义的所有变量都必须在该操作或该操作传递包含的片段中使用。未使用的变量会导致验证错误。
例如,以下操作是无效的
Counter Example № 180query variableUnused($atOtherHomes: Boolean) {
dog {
isHouseTrained
}
}
因为 $atOtherHomes 未被引用。
这些规则也适用于传递片段展开
Example № 181query variableUsedInFragment($atOtherHomes: Boolean) {
dog {
...isHouseTrainedFragment
}
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
以上操作是有效的,因为 $atOtherHomes 在 isHouseTrainedFragment 中使用,而 isHouseTrainedFragment 由 variableUsedInFragment 包含。
如果该片段没有对 $atOtherHomes 的引用,则它将无效
Counter Example № 182query variableNotUsedWithinFragment($atOtherHomes: Boolean) {
dog {
...isHouseTrainedWithoutVariableFragment
}
}
fragment isHouseTrainedWithoutVariableFragment on Dog {
isHouseTrained
}
文档中的所有操作都必须使用其所有变量。
因此,以下文档无法通过验证。
Counter Example № 183query queryWithUsedVar($atOtherHomes: Boolean) {
dog {
...isHouseTrainedFragment
}
}
query queryWithExtraVar($atOtherHomes: Boolean, $extra: Int) {
dog {
...isHouseTrainedFragment
}
}
fragment isHouseTrainedFragment on Dog {
isHouseTrained(atOtherHomes: $atOtherHomes)
}
此文档无效,因为 queryWithExtraVar 定义了一个多余的变量。
5.8.5所有变量使用均被允许
正式规范
- 对于 document 中的每个 operation
- 设 variableUsages 为传递包含在 operation 中的所有用法。
- 对于 variableUsages 中的每个 variableUsage
- 设 variableName 为 variableUsage 的名称。
- 设 variableDefinition 为在 operation 中定义的名为 variableName 的 VariableDefinition。
- IsVariableUsageAllowed(variableDefinition, variableUsage) 必须为 true。
- 设 variableType 为 variableDefinition 的预期类型。
- 设 locationType 为 variableUsage 所在位置的 Argument、ObjectField 或 ListValue 条目的预期类型。
- 如果 locationType 是非空类型 且 variableType 不是非空类型
- 如果 variableDefinition 存在默认值且不是值 null,则设 hasNonNullVariableDefaultValue 为 true。
- 如果 Argument 或 ObjectField(variableUsage 所在位置)存在默认值,则设 hasLocationDefaultValue 为 true。
- 如果 hasNonNullVariableDefaultValue 不是 true 且 hasLocationDefaultValue 不是 true,则返回 false。
- 设 nullableLocationType 为 locationType 的解包可空类型。
- 返回 AreTypesCompatible(variableType, nullableLocationType)。
- 返回 AreTypesCompatible(variableType, locationType)。
- 如果 locationType 是非空类型
- 如果 variableType 不是非空类型,则返回 false。
- 设 nullableLocationType 为 locationType 的解包可空类型。
- 设 nullableVariableType 为 variableType 的解包可空类型。
- 返回 AreTypesCompatible(nullableVariableType, nullableLocationType)。
- 否则,如果 variableType 是非空类型
- 设 nullableVariableType 为 variableType 的可空类型。
- 返回 AreTypesCompatible(nullableVariableType, locationType)。
- 否则,如果 locationType 是列表类型
- 如果 variableType 不是列表类型,则返回 false。
- 设 itemLocationType 为 locationType 的解包项类型。
- 设 itemVariableType 为 variableType 的解包项类型。
- 返回 AreTypesCompatible(itemVariableType, itemLocationType)。
- 否则,如果 variableType 是列表类型,则返回 false。
- 如果 variableType 和 locationType 相同,则返回 true,否则返回 false。
解释性文本
变量的使用必须与传递给它们的参数兼容。
当变量在完全不匹配的类型上下文中使用,或者可空类型的变量传递给非空参数类型时,会发生验证失败。
类型必须匹配
Counter Example № 184query intCannotGoIntoBoolean($intArg: Int) {
arguments {
booleanArgField(booleanArg: $intArg)
}
}
类型为 Int 的 $intArg 不能用作类型为 Boolean 的 booleanArg 的参数。
列表基数也必须相同。例如,列表不能传递到单数值中。
Counter Example № 185query booleanListCannotGoIntoBoolean($booleanListArg: [Boolean]) {
arguments {
booleanArgField(booleanArg: $booleanListArg)
}
}
可空性也必须受到尊重。通常,可空变量不能传递给非空参数。
Counter Example № 186query booleanArgQuery($booleanArg: Boolean) {
arguments {
nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)
}
}
对于列表类型,关于可空性的相同规则适用于外部类型和内部类型。可空列表不能传递给非空列表,并且可空值列表不能传递给非空值列表。以下操作是有效的
Example № 187query nonNullListToList($nonNullBooleanList: [Boolean]!) {
arguments {
booleanListArgField(booleanListArg: $nonNullBooleanList)
}
}
但是,可空列表不能传递给非空列表
Counter Example № 188query listToNonNullList($booleanList: [Boolean]) {
arguments {
nonNullBooleanListField(nonNullBooleanListArg: $booleanList)
}
}
这将无法通过验证,因为 [T]
不能传递给 [T]!
。 同样,[T]
不能传递给 [T!]
。
当默认值存在时允许可选变量
变量类型兼容性的一个显着例外是允许将具有可空类型的变量定义提供给非空位置,只要该变量或该位置提供默认值即可。
在下面的示例中,允许在非空参数 optionalBooleanArg
中使用可选变量 $booleanArg
,因为字段参数是可选的,因为它在模式中提供了默认值。
Example № 189query booleanArgQueryWithDefault($booleanArg: Boolean) {
arguments {
optionalNonNullBooleanArgField(optionalBooleanArg: $booleanArg)
}
}
在下面的示例中,允许在非空参数 (nonNullBooleanArg
) 中使用可选变量 $booleanArg
,因为该变量在操作中提供了默认值。显式支持此行为是为了与本规范的早期版本兼容。GraphQL 创作工具可能希望将此报告为警告,并建议将 Boolean
替换为 Boolean!
以避免歧义。
Example № 190query booleanArgQueryWithDefault($booleanArg: Boolean = true) {
arguments {
nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)
}
}
6执行
GraphQL 服务通过执行从请求生成响应。
执行的 请求 由以下几部分信息组成
- schema:要使用的模式,通常仅由 GraphQL 服务提供。
- document:一个 Document,它必须包含 GraphQL OperationDefinition,并且可能包含 FragmentDefinition。
- operationName(可选):要在文档中执行的操作的名称。
- variableValues(可选):操作定义的任何变量的值。
- initialValue(可选):与正在执行的根类型对应的初始值。从概念上讲,初始值表示可通过 GraphQL 服务获得的数据“宇宙”。GraphQL 服务通常为每个请求始终使用相同的初始值。
给定此信息,ExecuteRequest(schema, document, operationName, variableValues, initialValue) 的结果生成响应,该响应将按照下面的“响应”部分进行格式化。
6.1执行请求
要执行请求,执行器必须具有已解析的 Document 和要运行的选定操作名称(如果文档定义了多个操作),否则文档应仅包含单个操作。请求的结果由根据下面的“执行操作”部分执行此操作的结果确定。
- 设 operation 为 GetOperation(document, operationName) 的结果。
- 设 coercedVariableValues 为 CoerceVariableValues(schema, operation, variableValues) 的结果。
- 如果 operation 是查询操作
- 返回 ExecuteQuery(operation, schema, coercedVariableValues, initialValue)。
- 否则,如果 operation 是变更操作
- 返回 ExecuteMutation(operation, schema, coercedVariableValues, initialValue)。
- 否则,如果 operation 是订阅操作
- 返回 Subscribe(operation, schema, coercedVariableValues, initialValue)。
- 如果 operationName 为 null
- 如果 document 仅包含一个操作。
- 返回 document 中包含的 Operation。
- 否则,引发 request error,要求提供 operationName。
- 如果 document 仅包含一个操作。
- 否则
- 令 operation 为 document 中名为 operationName 的 Operation。
- 如果未找到 operation,则引发 request error。
- 返回 operation。
6.1.1验证请求
正如在“验证”章节中所解释的,只有通过所有验证规则的请求才应被执行。如果已知存在验证错误,则应在响应的“errors”列表中报告这些错误,并且请求必须失败且不执行。
通常,验证是在执行前立即在请求的上下文中执行的,但是,如果 GraphQL 服务已知完全相同的请求之前已经过验证,则可以执行请求而不立即验证它。GraphQL 服务应仅执行 *在某个时间点* 已知没有验证错误且此后未更改的请求。
例如:请求可以在开发期间进行验证,前提是之后没有更改,或者服务可以验证一次请求并 memoize 结果,以避免将来再次验证相同的请求。
6.1.2强制变量值
如果操作定义了任何变量,则需要使用变量声明类型的输入强制规则来强制这些变量的值。如果在变量值的输入强制期间遇到 request error,则操作将失败且不执行。
- 令 coercedValues 为一个空的无序 Map。
- 令 variablesDefinition 为 operation 定义的变量。
- 对于 variablesDefinition 中的每个 variableDefinition
- 令 variableName 为 variableDefinition 的名称。
- 设 variableType 为 variableDefinition 的预期类型。
- 断言:IsInputType(variableType) 必须为 true。
- 令 defaultValue 为 variableDefinition 的默认值。
- 令 hasValue 为 true,如果 variableValues 为名称 variableName 提供了值。
- 令 value 为在 variableValues 中为名称 variableName 提供的值。
- 如果 hasValue 不为 true 且 defaultValue 存在(包括 null)
- 向 coercedValues 添加一个名为 variableName 的条目,其值为 defaultValue。
- 否则,如果 variableType 是一个 Non-Nullable 类型,并且 hasValue 不为 true 或 value 为 null,则引发 request error。
- 否则,如果 hasValue 为 true
- 如果 value 为 null
- 向 coercedValues 添加一个名为 variableName 的条目,其值为 null。
- 否则
- 如果 value 无法根据 variableType 的输入强制规则进行强制转换,则引发 request error。
- 令 coercedValue 为根据 variableType 的输入强制规则强制转换 value 的结果。
- 向 coercedValues 添加一个名为 variableName 的条目,其值为 coercedValue。
- 如果 value 为 null
- 返回 coercedValues。
6.2执行操作
类型系统,如规范的“类型系统”章节中所述,必须提供查询根操作类型。如果支持 mutation 或 subscription,则它还必须分别提供 mutation 或 subscription 根操作类型。
6.2.1查询
如果操作是查询,则操作的结果是使用查询根操作类型执行操作的顶级 selection set 的结果。
在执行查询操作时,可以提供初始值。
- 令 queryType 为 schema 中的根 Query 类型。
- 断言:queryType 是一个 Object 类型。
- 令 selectionSet 为 query 中的顶级 selection set。
- 令 data 为 *正常* 运行 ExecuteSelectionSet(selectionSet, queryType, initialValue, variableValues)(允许并行化)的结果。
- 令 errors 为执行 selection set 时引发的所有 field error 的列表。
- 返回一个包含 data 和 errors 的无序 map。
6.2.2Mutation
如果操作是 mutation,则操作的结果是在 mutation 根对象类型上执行操作的顶级 selection set 的结果。此 selection set 应串行执行。
预计 mutation 操作中的顶级字段会对底层数据系统执行副作用。串行执行提供的 mutation 可确保在这些副作用期间避免竞争条件。
- 令 mutationType 为 schema 中的根 Mutation 类型。
- 断言:mutationType 是一个 Object 类型。
- 令 selectionSet 为 mutation 中的顶级 selection set。
- 令 data 为 *串行* 运行 ExecuteSelectionSet(selectionSet, mutationType, initialValue, variableValues) 的结果。
- 令 errors 为执行 selection set 时引发的所有 field error 的列表。
- 返回一个包含 data 和 errors 的无序 map。
6.2.3Subscription
如果操作是 subscription,则结果是一个 event stream,称为 response stream,其中 event stream 中的每个事件都是在底层 source stream 上为每个新事件执行操作的结果。
执行 subscription 操作会在服务上创建一个持久函数,该函数将底层 source stream 映射到返回的 response stream。
- 令 sourceStream 为运行 CreateSourceEventStream(subscription, schema, variableValues, initialValue) 的结果。
- 令 responseStream 为运行 MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues) 的结果。
- 返回 responseStream。
举例来说,考虑一个聊天应用程序。要订阅发布到聊天室的新消息,客户端发送如下请求
Example № 191subscription NewMessages {
newMessage(roomId: 123) {
sender
text
}
}
当客户端订阅后,每当有新消息发布到 ID 为 “123” 的聊天室时,“sender”和“text”的选择将被评估并发布到客户端,例如
Example № 192{
"data": {
"newMessage": {
"sender": "Hagrid",
"text": "You're a wizard!"
}
}
}
“发布到聊天室的新消息”可以使用“发布-订阅”系统,其中聊天室 ID 是“主题”,每个“发布”都包含发送者和文本。
事件流
event stream 表示一系列事件:随时间推移的离散发射值,可以被观察到。例如,“发布-订阅”系统在“订阅主题”时可能会产生一个 event stream,每次“发布”到该主题时都会发射一个值。
event stream 可能在任何时候完成,通常是因为不会再发生其他事件。event stream 可能会发射无限的值序列,在这种情况下,它可能永远不会完成。如果 event stream 遇到错误,则必须以该错误完成。
观察者可以在任何时候决定通过取消 event stream 来停止观察它。当 event stream 被取消时,它必须完成。
内部用户代码也可能出于任何原因取消 event stream,这将观察到该 event stream 完成。
大规模支持 Subscription
查询和 mutation 操作是无状态的,允许通过克隆 GraphQL 服务实例进行扩展。相比之下,Subscription 是有状态的,需要在 subscription 的整个生命周期内维护 GraphQL 文档、变量和其他上下文。
考虑当服务中单台机器发生故障导致状态丢失时,系统的行为。通过为管理 subscription 状态和客户端连接设置单独的专用服务,可以提高持久性和可用性。
交付无关性
GraphQL subscription 不要求任何特定的序列化格式或传输机制。GraphQL 规范了用于创建响应流、该流上每个 payload 的内容以及关闭该流的算法。有意地没有针对消息确认、缓冲、重发请求或任何其他服务质量 (QoS) 细节的规范。消息序列化、传输机制和服务质量细节应由实现服务选择。
6.2.3.1Source Stream
source stream 是一个 event stream,表示一系列根值,每个根值都将触发 GraphQL 执行。与字段值解析类似,创建 source stream 的逻辑是特定于应用程序的。
- 令 subscriptionType 为 schema 中的根 Subscription 类型。
- 断言:subscriptionType 是一个 Object 类型。
- 令 selectionSet 为 subscription 中的顶级 selection set。
- 令 groupedFieldSet 为 CollectFields(subscriptionType, selectionSet, variableValues) 的结果。
- 如果 groupedFieldSet 没有正好一个条目,则引发 request error。
- 令 fields 为 groupedFieldSet 中第一个条目的值。
- 令 fieldName 为 fields 中第一个条目的名称。注意:如果使用了别名,则此值不受影响。
- 令 field 为 fields 中的第一个条目。
- 令 argumentValues 为 CoerceArgumentValues(subscriptionType, field, variableValues) 的结果。
- 令 sourceStream 为运行 ResolveFieldEventStream(subscriptionType, initialValue, fieldName, argumentValues) 的结果。
- 返回 sourceStream。
- 令 resolver 为 subscriptionType 提供的内部函数,用于确定名为 fieldName 的 subscription 字段的已解析 event stream。
- 返回调用 resolver 的结果,提供 rootValue 和 argumentValues。
6.2.3.2Response Stream
来自底层 source stream 的每个事件都会触发 subscription selection set 的执行,使用该事件的值作为 initialValue。
- 令 responseStream 为一个新的 event stream。
- 当 sourceStream 发射 sourceValue 时
- 令 response 为运行 ExecuteSubscriptionEvent(subscription, schema, variableValues, sourceValue) 的结果。
- 如果引发了内部 error
- 取消 sourceStream。
- 使用 error 完成 responseStream。
- 否则,在 responseStream 上发射 response。
- 当 sourceStream 正常完成时
- 正常完成 responseStream。
- 当 sourceStream 以 error 完成时
- 使用 error 完成 responseStream。
- 当 responseStream 被取消时
- 取消 sourceStream。
- 正常完成 responseStream。
- 返回 responseStream。
- 令 subscriptionType 为 schema 中的根 Subscription 类型。
- 断言:subscriptionType 是一个 Object 类型。
- 令 selectionSet 为 subscription 中的顶级 selection set。
- 令 data 为 *正常* 运行 ExecuteSelectionSet(selectionSet, subscriptionType, initialValue, variableValues)(允许并行化)的结果。
- 令 errors 为执行 selection set 时引发的所有 field error 的列表。
- 返回一个包含 data 和 errors 的无序 map。
6.2.3.3取消订阅
当客户端不再希望接收 subscription 的 payload 时,取消订阅会取消 response stream。这反过来也会取消 Source Stream,这是一个清理 subscription 使用的任何其他资源的良好机会。
- 取消 responseStream。
6.3执行 Selection Set
要执行 selection set,需要知道正在评估的对象值和对象类型,以及它必须串行执行还是可以并行执行。
首先,selection set 被转换为分组字段集;然后,分组字段集中的每个表示字段都会生成一个响应 map 的条目。
- 令 groupedFieldSet 为 CollectFields(objectType, selectionSet, variableValues) 的结果。
- 将 resultMap 初始化为一个空的有序 map。
- 对于每个 groupedFieldSet,作为 responseKey 和 fields
- 令 fieldName 为 fields 中第一个条目的名称。注意:如果使用了别名,则此值不受影响。
- 令 fieldType 为为 objectType 的字段 fieldName 定义的返回类型。
- 如果定义了 fieldType
- 令 responseValue 为 ExecuteField(objectType, objectValue, fieldType, fields, variableValues)。
- 将 responseValue 设置为 resultMap 中 responseKey 的值。
- 返回 resultMap。
错误和 Non-Null 字段
如果在 ExecuteSelectionSet() 期间,具有 non-null fieldType 的字段引发了 field error,则该错误必须传播到整个 selection set,如果允许,则解析为 null,否则进一步传播到父字段。
如果发生这种情况,则可以取消任何尚未执行或尚未产生值的同级字段,以避免不必要的工作。
6.3.1正常和串行执行
通常,执行器可以按照它选择的任何顺序执行分组字段集中的条目(通常是并行执行)。由于顶级 mutation 字段以外的字段的解析必须始终是无副作用和幂等的,因此执行顺序不得影响结果,因此服务可以自由地按照其认为最佳的任何顺序执行字段条目。
例如,给定以下要正常执行的分组字段集
Example № 193{
birthday {
month
}
address {
street
}
}
有效的 GraphQL 执行器可以按照它选择的任何顺序解析这四个字段(当然,birthday
必须在 month
之前解析,address
必须在 street
之前解析)。
当执行 mutation 时,最顶层 selection set 中的选择将按串行顺序执行,从文本上第一个出现的字段开始。
当串行执行分组字段集时,执行器必须按照分组字段集中提供的顺序考虑分组字段集中的每个条目。它必须在继续处理分组字段集中的下一个条目之前,确定结果 map 中每个条目的对应条目并完成。
例如,给定以下 mutation 操作,根 selection set 必须串行执行
Example № 194mutation ChangeBirthdayAndAddress($newBirthday: String!, $newAddress: String!) {
changeBirthday(birthday: $newBirthday) {
month
}
changeAddress(address: $newAddress) {
street
}
}
因此,执行器必须串行地:
- 运行
changeBirthday
的 ExecuteField(),它在 CompleteValue() 期间将正常执行{ month }
子 selection set。 - 运行
changeAddress
的 ExecuteField(),它在 CompleteValue() 期间将正常执行{ street }
子 selection set。
作为一个说明性示例,假设我们有一个 mutation 字段 changeTheNumber
,它返回一个包含一个字段 theNumber
的对象。如果我们串行执行以下 selection set
Example № 195# Note: This is a selection set, not a full document using the query shorthand.
{
first: changeTheNumber(newNumber: 1) {
theNumber
}
second: changeTheNumber(newNumber: 3) {
theNumber
}
third: changeTheNumber(newNumber: 2) {
theNumber
}
}
执行器将串行执行以下操作:
- 解析
changeTheNumber(newNumber: 1)
字段 - 正常执行
first
的{ theNumber }
子 selection set - 解析
changeTheNumber(newNumber: 3)
字段 - 正常执行
second
的{ theNumber }
子 selection set - 解析
changeTheNumber(newNumber: 2)
字段 - 正常执行
third
的{ theNumber }
子 selection set
正确的执行器必须为该 selection set 生成以下结果
Example № 196{
"first": {
"theNumber": 1
},
"second": {
"theNumber": 3
},
"third": {
"theNumber": 2
}
}
6.3.2字段收集
在执行之前,通过调用 CollectFields() 将 selection set 转换为分组字段集。分组字段集中的每个条目都是共享响应键(如果定义了别名,则为别名,否则为字段名称)的字段列表。这确保了所有具有相同响应键的字段(包括引用片段中的字段)都同时执行。
例如,收集此 selection set 的字段将收集字段 a
的两个实例和字段 b
的一个实例
Example № 197{
a {
subfield1
}
...ExampleFragment
}
fragment ExampleFragment on Query {
a {
subfield2
}
b
}
由 CollectFields() 生成的字段组的深度优先搜索顺序在执行过程中保持不变,确保字段以稳定且可预测的顺序出现在已执行的响应中。
- 如果未提供 visitedFragments,则将其初始化为空集。
- 将 groupedFields 初始化为一个空的有序列表 map。
- 对于 selectionSet 中的每个 selection
- 如果 selection 提供了指令
@skip
,则令 skipDirective 为该指令。- 如果 skipDirective 的 if 参数为 true 或为 variableValues 中值为 true 的变量,则继续处理 selectionSet 中的下一个 selection。
- 如果 selection 提供了指令
@include
,则令 includeDirective 为该指令。- 如果 includeDirective 的 if 参数不为 true 且不是 variableValues 中值为 true 的变量,则继续处理 selectionSet 中的下一个 selection。
- 如果 selection 是一个 Field
- 令 responseKey 为 selection 的响应键(如果定义了别名,则为别名,否则为字段名称)。
- 令 groupForResponseKey 为 groupedFields 中 responseKey 的列表;如果不存在此类列表,则将其创建为空列表。
- 将 selection 附加到 groupForResponseKey。
- 如果 selection 是一个 FragmentSpread
- 令 fragmentSpreadName 为 selection 的名称。
- 如果 fragmentSpreadName 在 visitedFragments 中,则继续处理 selectionSet 中的下一个 selection。
- 将 fragmentSpreadName 添加到 visitedFragments。
- 令 fragment 为当前 Document 中名称为 fragmentSpreadName 的 Fragment。
- 如果不存在此类 fragment,则继续处理 selectionSet 中的下一个 selection。
- 令 fragmentType 为 fragment 上的类型条件。
- 如果 DoesFragmentTypeApply(objectType, fragmentType) 为 false,则继续处理 selectionSet 中的下一个 selection。
- 令 fragmentSelectionSet 为 fragment 的顶级 selection set。
- 令 fragmentGroupedFieldSet 为调用 CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments) 的结果。
- 对于 fragmentGroupedFieldSet 中的每个 fragmentGroup
- 令 responseKey 为 fragmentGroup 中所有字段共享的响应键。
- 令 groupForResponseKey 为 groupedFields 中 responseKey 的列表;如果不存在此类列表,则将其创建为空列表。
- 将 fragmentGroup 中的所有条目附加到 groupForResponseKey。
- 如果 selection 是一个 InlineFragment
- 令 fragmentType 为 selection 上的类型条件。
- 如果 fragmentType 不为 null 且 DoesFragmentTypeApply(objectType, fragmentType) 为 false,则继续处理 selectionSet 中的下一个 selection。
- 令 fragmentSelectionSet 为 selection 的顶级 selection set。
- 令 fragmentGroupedFieldSet 为调用 CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments) 的结果。
- 对于 fragmentGroupedFieldSet 中的每个 fragmentGroup
- 令 responseKey 为 fragmentGroup 中所有字段共享的响应键。
- 令 groupForResponseKey 为 groupedFields 中 responseKey 的列表;如果不存在此类列表,则将其创建为空列表。
- 将 fragmentGroup 中的所有条目附加到 groupForResponseKey。
- 如果 selection 提供了指令
- 返回 groupedFields。
- 如果 fragmentType 是一个 Object Type
- 如果 objectType 和 fragmentType 是相同类型,则返回 true,否则返回 false。
- 如果 fragmentType 是一个 Interface Type
- 如果 objectType 是 fragmentType 的实现,则返回 true,否则返回 false。
- 如果 fragmentType 是一个 Union
- 如果 objectType 是 fragmentType 的可能类型,则返回 true,否则返回 false。
6.4执行字段
在所选 objectType 上定义的已分组字段集中请求的每个字段都将在响应映射中生成一个条目。字段执行首先强制转换任何提供的参数值,然后解析字段的值,最后通过递归执行另一个选择集或强制转换标量值来完成该值。
- 令 field 为 fields 中的第一个条目。
- 设 fieldName 为 field 的字段名称。
- 设 argumentValues 为 CoerceArgumentValues(objectType, field, variableValues) 的结果。
- 设 resolvedValue 为 ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)。
- 返回 CompleteValue(fieldType, fields, resolvedValue, variableValues) 的结果。
6.4.1强制转换字段参数
字段可能包含参数,这些参数被提供给底层运行时以便正确生成值。这些参数在类型系统中由字段定义为具有特定的输入类型。
在操作中的每个参数位置,可能是一个字面量 Value,或一个 Variable 以在运行时提供。
- 令 coercedValues 为一个空的无序 Map。
- 设 argumentValues 为在 field 中提供的参数值。
- 设 fieldName 为 field 的名称。
- 设 argumentDefinitions 为 objectType 为名为 fieldName 的字段定义的参数定义。
- 对于 argumentDefinitions 中的每个 argumentDefinition
- 设 argumentName 为 argumentDefinition 的名称。
- 设 argumentType 为 argumentDefinition 的预期类型。
- 设 defaultValue 为 argumentDefinition 的默认值。
- 设 hasValue 为 true,如果 argumentValues 为名称 argumentName 提供了值。
- 设 argumentValue 为在 argumentValues 中为名称 argumentName 提供的值。
- 如果 argumentValue 是一个 Variable
- 设 variableName 为 argumentValue 的名称。
- 令 hasValue 为 true,如果 variableValues 为名称 variableName 提供了值。
- 令 value 为在 variableValues 中为名称 variableName 提供的值。
- 否则,设 value 为 argumentValue。
- 如果 hasValue 不为 true 且 defaultValue 存在(包括 null)
- 向 coercedValues 添加一个名为 argumentName 的条目,其值为 defaultValue。
- 否则,如果 argumentType 是一个非空类型,并且 hasValue 不是 true 或 value 是 null,则引发一个 字段错误。
- 否则,如果 hasValue 为 true
- 如果 value 为 null
- 向 coercedValues 添加一个名为 argumentName 的条目,其值为 null。
- 否则,如果 argumentValue 是一个 Variable
- 向 coercedValues 添加一个名为 argumentName 的条目,其值为 value。
- 否则
- 如果 value 无法根据 argumentType 的输入强制转换规则进行强制转换,则引发一个 字段错误。
- 设 coercedValue 为根据 argumentType 的输入强制转换规则强制转换 value 的结果。
- 向 coercedValues 添加一个名为 argumentName 的条目,其值为 coercedValue。
- 如果 value 为 null
- 返回 coercedValues。
6.4.2值解析
虽然几乎所有的 GraphQL 执行都可以通用地描述,但最终暴露 GraphQL 接口的内部系统必须提供值。这通过 ResolveFieldValue 公开,它为一个给定类型上的给定字段生成一个真实值。
例如,这可能接受 objectType Person
,field "soulMate",以及代表约翰·列侬的 objectValue。它应该产生代表小野洋子的值。
- 设 resolver 为 objectType 提供的内部函数,用于确定名为 fieldName 的字段的已解析值。
- 返回调用 resolver 的结果,提供 objectValue 和 argumentValues。
6.4.3值完成
在解析字段的值之后,通过确保它符合预期的返回类型来完成它。如果返回类型是另一个对象类型,则字段执行过程将继续递归。
- 如果 fieldType 是一个非空类型
- 设 innerType 为 fieldType 的内部类型。
- 设 completedResult 为调用 CompleteValue(innerType, fields, result, variableValues) 的结果。
- 如果 completedResult 是 null,则引发一个 字段错误。
- 返回 completedResult。
- 如果 result 是 null(或另一个类似于 null 的内部值,例如 undefined),则返回 null。
- 如果 fieldType 是一个列表类型
- 如果 result 不是一个值集合,则引发一个 字段错误。
- 设 innerType 为 fieldType 的内部类型。
- 返回一个列表,其中每个列表项都是调用 CompleteValue(innerType, fields, resultItem, variableValues) 的结果,其中 resultItem 是 result 中的每个项。
- 如果 fieldType 是一个标量或枚举类型
- 返回 CoerceResult(fieldType, result) 的结果。
- 如果 fieldType 是一个对象、接口或联合类型
- 如果 fieldType 是一个对象类型。
- 设 objectType 为 fieldType。
- 否则,如果 fieldType 是一个接口或联合类型。
- 设 objectType 为 ResolveAbstractType(fieldType, result)。
- 设 subSelectionSet 为调用 MergeSelectionSets(fields) 的结果。
- 返回正常评估 ExecuteSelectionSet(subSelectionSet, objectType, result, variableValues) 的结果(允许并行化)。
- 如果 fieldType 是一个对象类型。
强制转换结果
值完成的主要目的是确保字段解析器返回的值根据 GraphQL 类型系统和服务的模式是有效的。这种“动态类型检查”允许 GraphQL 在任何服务的内部运行时之上提供关于返回类型的一致保证。
有关 GraphQL 内置标量如何强制转换结果值的更多详细信息,请参阅标量 结果强制转换和序列化 子章节。
- 断言 value 不是 null。
- 返回调用类型系统提供的内部方法的结果,该方法用于确定给定值 value 的 leafType 的“结果强制转换”。此内部方法必须返回类型的有效值,而不是 null。 否则,引发一个 字段错误。
解析抽象类型
当完成具有抽象返回类型的字段时,即接口或联合返回类型,首先必须将抽象类型解析为相关的对象类型。此确定由内部系统使用任何适当的方式进行。
- 返回调用类型系统提供的内部方法的结果,该方法用于确定给定值 objectValue 的 abstractType 的对象类型。
合并选择集
当并行执行多个同名字段时,每个字段的 选择集 在完成值时合并在一起,以便继续执行子选择集。
一个示例操作,说明具有子选择的同名并行字段。
Example № 198{
me {
firstName
}
me {
lastName
}
}
在解析 me
的值之后,选择集被合并在一起,以便可以为一个值解析 firstName
和 lastName
。
- 设 selectionSet 为一个空列表。
- 对于 fields 中的每个 field
- 设 fieldSelectionSet 为 field 的选择集。
- 如果 fieldSelectionSet 为 null 或为空,则继续下一个字段。
- 将 fieldSelectionSet 中的所有选择项追加到 selectionSet。
- 返回 selectionSet。
6.4.4处理字段错误
字段错误是在值解析或强制转换期间从特定字段引发的错误。 虽然这些错误应在响应中报告,但它们通过生成部分响应来“处理”。
如果在解析字段时引发字段错误,则将其视为字段返回 null 进行处理,并且必须将错误添加到响应中的 "errors" 列表中。
如果解析字段的结果为 null(要么是因为解析字段的函数返回了 null,要么是因为引发了字段错误),并且该字段是非空类型,则会引发字段错误。 错误必须添加到响应中的 "errors" 列表中。
如果字段由于已添加到响应中 "errors" 列表中的字段错误而返回 null,则 "errors" 列表不得进一步受到影响。 也就是说,每个字段只能向错误列表添加一个错误。
由于非空类型字段不能为 null,因此字段错误会传播到父字段进行处理。 如果父字段可能为 null,则它解析为 null,否则,如果它是非空类型,则字段错误会进一步传播到其父字段。
如果列表类型包装了非空类型,并且该列表的其中一个元素解析为 null,则整个列表必须解析为 null。 如果列表类型也包装在非空中,则字段错误会继续向上传播。
如果从请求的根到字段错误的源的所有字段都返回非空类型,则响应中的 "data" 条目应为 null。
7响应
当 GraphQL 服务收到 请求 时,它必须返回格式良好的响应。 服务的响应描述了成功执行请求的操作的结果,并描述了请求期间引发的任何错误。
在字段错误在字段上引发并被替换为 null 的情况下,响应可能同时包含部分响应和错误列表。
7.1响应格式
7.1.1响应
当 GraphQL 操作是查询或变更时,GraphQL 请求返回 响应。响应 必须是一个映射。
如果请求引发了任何错误,则响应映射必须包含一个键为 errors
的条目。 此条目的值在“错误”部分中描述。 如果请求完成时没有引发任何错误,则此条目不得存在。
如果请求包含执行,则响应映射必须包含一个键为 data
的条目。 此条目的值在“数据”部分中描述。 如果请求在执行之前失败,由于语法错误、缺少信息或验证错误,则此条目不得存在。
响应映射也可能包含一个键为 extensions
的条目。 此条目(如果设置)必须具有一个映射作为其值。 此条目保留供实现者扩展协议,他们可以随意扩展,因此对其内容没有额外的限制。
为了确保协议的未来更改不会破坏现有服务和客户端,顶级响应映射不得包含除上述三个描述之外的任何条目。
errors
时,将其序列化时首先出现可能有助于在调试期间更清楚地了解响应中何时存在错误。7.1.2响应流
7.1.3数据
响应中的 data
条目将是请求操作执行的结果。 如果操作是查询,则此输出将是查询根操作类型的对象; 如果操作是变更,则此输出将是变更根操作类型的对象。
如果在执行开始之前引发了错误,则 data
条目不应存在于响应中。
如果在执行期间引发了阻止有效响应的错误,则响应中的 data
条目应为 null
。
7.1.4错误
响应中的 errors
条目是在 请求 期间引发的非空错误列表,其中每个错误都是一个映射,其中包含错误结果格式描述的数据,如下所示。
如果存在,则响应中的 errors
条目必须包含至少一个错误。 如果请求期间未引发任何错误,则 errors
条目不得存在。
如果响应中的 data
条目不存在,则 errors
条目必须存在。 它必须包含至少一个 请求错误,指示为什么无法返回任何数据。
如果响应中的 data
条目存在(包括如果它是值 null),则当且仅当在执行期间引发了一个或多个 字段错误 时,errors
条目才必须存在。
请求错误
请求错误是在 请求 期间引发的错误,这会导致没有响应数据。 请求错误通常在执行开始之前引发,可能是由于 文档 中的解析语法或验证错误、无法确定要执行哪个操作或变量的输入值无效而发生。
请求错误通常是请求客户端的错误。
如果引发了请求错误,则响应中的 data
条目不得存在,errors
条目必须包含该错误,并且应停止请求执行。
字段错误
字段错误是在执行特定字段期间引发的错误,这会导致部分响应数据。 这可能是由于值解析期间的内部错误或未能强制转换结果值而发生的。
字段错误通常是 GraphQL 服务的错误。
如果引发了字段错误,则执行尝试继续并生成部分结果(参见 处理字段错误)。 响应中的 data
条目必须存在。 errors
条目应包含此错误。
错误结果格式
每个错误都必须包含一个键为 message
的条目,其中包含错误的字符串描述,供开发人员参考,以了解和纠正错误。
如果错误可以与请求的 GraphQL 文档中的特定点关联,则它应包含一个键为 locations
的条目,其中包含位置列表,其中每个位置都是一个映射,其中包含键 line
和 column
,这两个键都是从 1
开始的正数,用于描述关联的语法元素的开头。
如果错误可以与 GraphQL 结果中的特定字段关联,则它必须包含一个键为 path
的条目,该条目详细说明了遇到错误的响应字段的路径。 这允许客户端识别 null
结果是故意的还是由运行时错误引起的。 此 路径条目 的值在 路径 部分中描述。
例如,如果在以下操作中获取其中一个朋友的姓名失败
Example № 199{
hero(episode: $episode) {
name
heroFriends: friends {
id
name
}
}
}
响应可能如下所示
Example № 200{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"]
}
],
"data": {
"hero": {
"name": "R2-D2",
"heroFriends": [
{
"id": "1000",
"name": "Luke Skywalker"
},
{
"id": "1002",
"name": null
},
{
"id": "1003",
"name": "Leia Organa"
}
]
}
}
}
如果遇到错误的字段被声明为 Non-Null
,则 null
结果将冒泡到下一个可为空字段。 在这种情况下,错误的 path
应包括到引发错误的结果字段的完整路径,即使该字段未在响应中出现。
例如,如果上面示例中的 name
字段在模式中声明了 Non-Null
返回类型,则结果将看起来不同,但报告的错误将是相同的
Example № 201{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"]
}
],
"data": {
"hero": {
"name": "R2-D2",
"heroFriends": [
{
"id": "1000",
"name": "Luke Skywalker"
},
null,
{
"id": "1003",
"name": "Leia Organa"
}
]
}
}
}
GraphQL 服务可能会为错误提供一个额外的键为 extensions
的条目。 此条目(如果设置)必须具有一个映射作为其值。 此条目保留供实现者向错误添加额外信息,他们可以随意添加,并且对其内容没有额外的限制。
Example № 202{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
GraphQL 服务不应为错误格式提供任何额外的条目,因为它们可能会与本规范未来版本中可能添加的额外条目冲突。
extensions
条目。 虽然未指定的条目不是违规行为,但仍不鼓励使用它们。Counter Example № 203{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"],
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
]
}
7.1.5路径
路径条目是错误结果中的一个条目,它允许与 GraphQL 执行期间到达的特定字段关联。
路径条目的值必须是以响应根目录开头并以要关联的字段结尾的路径段列表。 表示字段的路径段必须是字符串,表示列表索引的路径段必须是 0 索引整数。 如果路径段与别名字段关联,则必须使用别名名称,因为它表示响应中的路径,而不是请求中的路径。
当 路径条目 出现在错误结果上时,它标识了遇到错误的响应字段。
7.2序列化格式
GraphQL 不要求特定的序列化格式。 但是,客户端应使用支持 GraphQL 响应中主要原语的序列化格式。 特别是,序列化格式必须至少支持以下四个原语的表示形式
- 映射
- 列表
- 字符串
- Null
序列化格式还应支持以下原语,每个原语代表一个常见的 GraphQL 标量类型,但是,如果任何原语未直接支持,则可以使用字符串或更简单的原语作为替代
- 布尔值
- 整数
- 浮点数
- 枚举值
这并非旨在详尽列出序列化格式可以编码的内容。 例如,代表日期、时间、URI 或具有不同精度的数字的自定义标量可以使用给定序列化格式可能支持的任何相关格式表示。
7.2.1JSON 序列化
JSON 是 GraphQL 最常见的序列化格式。 尽管如上所述,GraphQL 不要求特定的序列化格式。
当使用 JSON 作为 GraphQL 响应的序列化时,应使用以下 JSON 值来编码相关的 GraphQL 值
GraphQL 值 | JSON 值 |
---|---|
映射 | 对象 |
列表 | 数组 |
Null | null |
字符串 | 字符串 |
布尔值 | true 或 false |
整数 | 数字 |
浮点数 | 数字 |
枚举值 | 字符串 |
7.2.2序列化映射排序
由于评估 选择集 的结果是有序的,因此序列化结果映射应通过按照选择集执行定义的请求字段的相同顺序写入映射条目来保留此顺序。 生成序列化响应,其中字段以与请求中出现的顺序相同的顺序表示,可以提高调试期间的人工可读性,并且如果可以预期属性的顺序,则可以更有效地解析响应。
表示有序映射的序列化格式应保留请求字段的顺序,如执行部分中的 CollectFields() 所定义。 仅表示无序映射但顺序仍隐含在序列化的文本顺序中的序列化格式(例如 JSON)应在文本上保留请求字段的顺序。
例如,如果请求是 { name, age }
,则以 JSON 响应的 GraphQL 服务应使用 { "name": "Mark", "age": 30 }
进行响应,而不应使用 { "age": 30, "name": "Mark" }
进行响应。
虽然 JSON 对象被指定为 键值对的无序集合,但这些对以有序的方式表示。 换句话说,虽然 JSON 字符串 { "name": "Mark", "age": 30 }
和 { "age": 30, "name": "Mark" }
编码相同的值,但它们也具有可观察到的不同属性排序。
A附录:符号约定
本规范文档包含许多符号约定,用于描述技术概念,例如语言语法和语义以及运行时算法。
本附录旨在更详细地解释这些符号,以避免歧义。
A.1上下文无关文法
上下文无关文法由许多产生式组成。 每个产生式都有一个抽象符号,称为“非终结符”作为其左侧,以及零个或多个非终结符和/或终结字符的可能序列作为其右侧。
从单个目标非终结符开始,上下文无关文法描述了一种语言:可以通过重复地将目标序列中的任何非终结符替换为由其定义的序列之一,直到所有非终结符都被终结符替换为止,从而描述的可能的字符序列的集合。
终结符在本文件中以等宽字体用两种形式表示:特定的 Unicode 字符或 Unicode 字符序列(即 = 或 terminal),以及通常描述特定 Unicode 代码点的散文 "空格 (U+0020)"。Unicode 字符序列仅出现在语法文法中,并表示该特定序列的 名称 词法单元。
非终结符产生式规则在本文件中使用以下符号表示具有单个定义的非终结符
同时,对于具有定义列表的产生式,使用以下符号表示
定义可以引用自身,这描述了重复序列,例如
A.2词法和语法文法
GraphQL 语言在语法文法中定义,其中终结符是词法单元。词法单元在词法文法中定义,词法文法匹配源字符的模式。解析 Unicode 字符的源文本序列的结果首先根据词法文法生成词法单元序列,然后根据语法文法生成抽象语法树 (AST)。
词法文法产生式通过终结符 Unicode 字符的模式来描述非终结符“词法单元”。在词法文法产生式中,任何终结符 Unicode 字符之间都不得出现“空白”或其他忽略的字符。词法文法产生式以双冒号 ::
定义为区分。
语法文法产生式通过终结符词法单元的模式来描述非终结符“规则”。WhiteSpace 和其他 Ignored 序列可以出现在任何终结符 Token 之前或之后。语法文法产生式以单冒号 :
定义为区分。
A.3文法符号
本规范使用一些额外的符号来描述常见模式,例如可选或重复模式,或非终结符定义的参数化变体。本节解释这些简写符号及其在上下文无关文法中的展开定义。
约束
文法产生式可以指定某些扩展是不允许的,方法是使用短语“but not”(但不是),然后指出要排除的扩展。
例如,以下产生式意味着非终结符 SafeWord 可以被可以替换 Word 的任何字符序列替换,前提是相同的字符序列不能替换 SevenCarlinWords。
文法也可以在“but not”之后列出多个限制,用“or”(或)分隔。
例如
前瞻限制
文法产生式可以指定不允许某些字符或词法单元跟随其后,方法是使用模式 NotAllowed。前瞻限制通常用于消除文法中的歧义。
可选性和列表
下标后缀“Symbolopt”是两种可能序列的简写,一种包含该符号,另一种不包含该符号。
例如
是以下内容的简写
下标后缀“Symbollist”是一个或多个该符号的列表的简写,表示为额外的递归产生式。
例如
是以下内容的简写
参数化文法产生式
花括号中的符号定义下标后缀参数“SymbolParam”是两个符号定义的简写,一个附加了该参数名称,另一个没有附加。符号上的相同下标后缀是该定义的变体的简写。如果参数以“?”开头,则如果在具有相同参数的符号定义中使用该符号形式。当分别以“[+Param]”和“[~Param]”为前缀时,可以有条件地包含或排除某些可能的序列。
例如
是以下内容的简写
A.4文法语义
本规范以算法步骤列表的形式描述了许多文法产生式的语义值。
例如,这描述了解析器应如何解释字符串字面量
A.5算法
本规范描述了静态和运行时语义使用的一些算法,它们以类似函数的语法形式定义,包括算法的名称和它接受的参数,以及按顺序列出的要执行的算法步骤列表。每个步骤可能会建立对其他值的引用,检查各种条件,调用其他算法,并最终返回表示为提供的参数的算法结果的值。
例如,以下示例描述了一个名为 Fibonacci 的算法,它接受一个参数 number。该算法的步骤产生斐波那契数列中的下一个数字
B附录:文法摘要
B.1源文本
B.2忽略的词法单元
B.3词法单元
! | $ | & | ( | ) | ... | : | = | @ | [ | ] | { | | | } |
A | B | C | D | E | F | G | H | I | J | K | L | M |
N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
a | b | c | d | e | f | g | h | i | j | k | l | m |
n | o | p | q | r | s | t | u | v | w | x | y | z |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
e | E |
+ | - |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
A | B | C | D | E | F | ||||
a | b | c | d | e | f |
" | \ | / | b | f | n | r | t |
B.4文档语法
query | mutation | subscription |
true | false |
QUERY |
MUTATION |
SUBSCRIPTION |
FIELD |
FRAGMENT_DEFINITION |
FRAGMENT_SPREAD |
INLINE_FRAGMENT |
VARIABLE_DEFINITION |
SCHEMA |
SCALAR |
OBJECT |
FIELD_DEFINITION |
ARGUMENT_DEFINITION |
INTERFACE |
UNION |
ENUM |
ENUM_VALUE |
INPUT_OBJECT |
INPUT_FIELD_DEFINITION |
§索引
- 别名
- AreTypesCompatible
- Argument
- Arguments
- ArgumentsDefinition
- 块字符串
- 块字符串字符
- BlockStringValue
- 布尔值
- 内置指令
- CoerceArgumentValues
- CoerceResult
- CoerceVariableValues
- CollectFields
- Comma
- Comment
- CommentChar
- CompleteValue
- CreateSourceEventStream
- 自定义指令
- 默认根类型名称
- DefaultValue
- Definition
- Description
- DetectFragmentCycles
- 数字
- Directive
- DirectiveDefinition
- DirectiveLocation
- DirectiveLocations
- Directives
- Document
- DoesFragmentTypeApply
- EnumTypeDefinition
- EnumTypeExtension
- 枚举值
- EnumValueDefinition
- EnumValuesDefinition
- 转义字符
- 转义的 Unicode
- 事件流
- ExecutableDefinition
- ExecutableDirectiveLocation
- ExecutableDocument
- ExecuteField
- ExecuteMutation
- ExecuteQuery
- ExecuteRequest
- ExecuteSelectionSet
- ExecuteSubscriptionEvent
- 指数指示符
- 指数部分
- 字段
- 字段错误
- FieldDefinition
- FieldsDefinition
- FieldsInSetCanMerge
- FloatValue
- 小数部分
- FragmentDefinition
- 片段名称
- 片段展开
- GetOperation
- GetPossibleTypes
- 十六进制数字
- Ignored
- ImplementsInterfaces
- 内联片段
- InputFieldsDefinition
- InputObjectTypeDefinition
- InputObjectTypeExtension
- InputValueDefinition
- 整数部分
- InterfaceTypeDefinition
- InterfaceTypeExtension
- IntValue
- IsInputType
- IsOutputType
- IsSubType
- IsValidImplementation
- IsValidImplementationFieldType
- IsVariableUsageAllowed
- 字母
- LineTerminator
- ListType
- ListValue
- MapSourceToResponseEvent
- MergeSelectionSets
- Name
- NameContinue
- NamedType
- NameStart
- 负号
- NonNullType
- 非零数字
- 空值
- ObjectField
- ObjectTypeDefinition
- ObjectTypeExtension
- ObjectValue
- OperationDefinition
- OperationType
- 路径条目
- Punctuator
- 请求
- 请求错误
- ResolveAbstractType
- ResolveFieldEventStream
- ResolveFieldValue
- 响应
- 响应流
- 根操作类型
- RootOperationTypeDefinition
- SameResponseShape
- 标量规范 URL
- ScalarTypeDefinition
- ScalarTypeExtension
- SchemaDefinition
- SchemaExtension
- 选择
- 选择集
- SelectionSet
- 符号
- 源流
- SourceCharacter
- 字符串字符
- StringValue
- Subscribe
- Token
- Type
- 类型条件
- TypeDefinition
- TypeExtension
- TypeSystemDefinition
- TypeSystemDefinitionOrExtension
- TypeSystemDirectiveLocation
- TypeSystemDocument
- TypeSystemExtension
- TypeSystemExtensionDocument
- Unicode 文本
- UnicodeBOM
- UnionMemberTypes
- UnionTypeDefinition
- UnionTypeExtension
- Unsubscribe
- Value
- Variable
- VariableDefinition
- VariablesDefinition
- WhiteSpace