GraphQL

2018年6月版

简介

这是 GraphQL 的规范,GraphQL 是一种查询语言和执行引擎,最初于 2012 年在 Facebook 创建,用于描述客户端-服务器应用程序的数据模型的功能和需求。此开放标准的开发始于 2015 年。

GraphQL 已经发展,并可能在本规范的未来版本中继续发展。GraphQL 规范的先前版本可以在与其 发布标签 匹配的永久链接中找到。最新的工作草案版本可以在 facebook.github.io/graphql/draft/ 中找到。

版权声明

版权所有 © 2015 年至今,Facebook, Inc.

截至 2017 年 9 月 26 日,以下个人或实体已根据开放 Web 基金会最终规范协议 (OWFa 1.0) 提供本规范,该协议可在 openwebfoundation.org 上查阅。

您可以在 github.com/facebook/graphql 上查看本规范的开放 Web 基金会最终规范协议 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 工具、客户端库和服务器实现的生态系统(跨越组织和平台)提供基础和框架,而这些生态系统尚待构建。我们期待与社区合作以实现这一目标。

2语言

客户端使用 GraphQL 查询语言向 GraphQL 服务发出请求。我们将这些请求源称为文档。文档可能包含操作(查询、变更和订阅)以及片段,片段是允许查询重用的常用组合单元。

GraphQL 文档被定义为语法文法,其中终结符是标记(不可分割的词法单元)。这些标记在词法文法中定义,该文法匹配源字符的模式(由双冒号 :: 定义)。

有关词法和句法文法定义以及本文档中使用的其他符号约定的更多详细信息,请参见 附录 A

2.1源文本

SourceCharacter
/[\u0009\u000A\u000D\u0020-\uFFFF]/

GraphQL 文档表示为 Unicode 字符序列。但是,除了少数例外,GraphQL 的大部分内容仅以原始的非控制 ASCII 范围表示,以便尽可能广泛地兼容尽可能多的现有工具、语言和序列化格式,并避免在文本编辑器和源代码控制中出现显示问题。

2.1.1Unicode

UnicodeBOM
字节顺序标记 (Byte Order Mark) (U+FEFF)

非 ASCII Unicode 字符可以自由地出现在 GraphQL 的 StringValueComment 部分中。

“字节顺序标记”是一种特殊的 Unicode 字符,可能出现在包含 Unicode 的文件的开头,程序可以使用它来确定文本流是 Unicode,文本流的字节序以及要解释的几种 Unicode 编码中的哪一种。

2.1.2空白字符

WhiteSpace
水平制表符 (Horizontal Tab) (U+0009)
空格 (Space) (U+0020)

空白字符用于提高源文本的可读性,并充当标记之间的分隔符,并且任何数量的空白字符都可能出现在任何标记之前或之后。标记之间的空白字符对于 GraphQL 文档的语义含义并不重要,但是空白字符可能出现在 StringComment 标记中。

GraphQL 有意不将 Unicode “Zs” 类别字符视为空白字符,避免文本编辑器和源代码控制工具的误解。

2.1.3行终止符

LineTerminator
换行符 (New Line) (U+000A)
回车符 (Carriage Return) (U+000D)换行符 (New Line) (U+000A)
回车符 (Carriage Return) (U+000D)换行符 (New Line) (U+000A)

与空白字符一样,行终止符用于提高源文本的可读性,任何数量的行终止符都可能出现在任何其他标记之前或之后,并且对 GraphQL 文档的语义含义没有影响。在任何其他标记中都找不到行终止符。

任何提供违规语法源代码行号的错误报告都应使用前面的 LineTerminator 数量来生成行号。

2.1.4注释

GraphQL 源文档可能包含单行注释,以 # 标记开头。

注释可以包含除 LineTerminator 之外的任何 Unicode 代码点,因此注释始终由以 # 字符开头的所有代码点组成,直到但不包括行终止符。

注释的行为类似于空白字符,可以出现在任何标记之后或行终止符之前,并且对 GraphQL 文档的语义含义没有影响。

2.1.5无关紧要的逗号

与空白字符和行终止符类似,逗号 (,) 用于提高源文本的可读性并分隔词法标记,但在 GraphQL 文档中,它们在语法和语义上都是无关紧要的。

非重要的逗号字符确保逗号的缺失或存在不会有意义地改变文档的解释语法,因为这可能是其他语言中常见的用户错误。它还允许风格上使用尾随逗号或行终止符作为列表分隔符,这对于源代码的可读性和可维护性通常都是需要的。

2.1.6词法标记

GraphQL 文档由几种不可分割的词法标记组成,这些标记在此处的词法文法中通过源 Unicode 字符的模式定义。

标记稍后用作 GraphQL 文档句法文法中的终结符。

2.1.7忽略的标记

在每个词法标记之前和之后,都可能存在任何数量的忽略的标记,包括 WhiteSpaceComment。源文档的任何忽略区域都不重要,但是忽略的源字符可能以重要的方式出现在词法标记中,例如 String 可能包含空白字符。

在解析给定标记时不会忽略任何字符,例如,在定义 FloatValue 的字符之间不允许有空白字符。

2.1.8标点符号

Punctuator
!$()...:=@[]{|}

GraphQL 文档包含标点符号以描述结构。GraphQL 是一种数据描述语言,而不是编程语言,因此 GraphQL 缺少通常用于描述数学表达式的标点符号。

2.1.9名称

Name
/[_A-Za-z][_0-9A-Za-z]*/

GraphQL 文档中充满了命名事物:操作、字段、参数、类型、指令、片段和变量。所有名称都必须遵循相同的语法形式。

GraphQL 中的名称区分大小写。也就是说,nameNameNAME 都指的是不同的名称。下划线是有意义的,这意味着 other_nameothername 是两个不同的名称。

GraphQL 中的名称仅限于此ASCII可能的字符子集,以支持与尽可能多的其他系统进行互操作。

2.2文档

GraphQL 文档描述了 GraphQL 服务或客户端操作的完整文件或请求字符串。文档包含多个定义,可以是可执行的,也可以代表 GraphQL 类型系统。

仅当文档包含 OperationDefinition 并且仅包含 ExecutableDefinition 时,GraphQL 服务才能执行文档。但是,不包含 OperationDefinition 或包含 TypeSystemDefinitionTypeSystemExtension 的文档仍然可以被解析和验证,以允许客户端工具表示可能出现在许多单独文件中的许多 GraphQL 用法。

如果文档仅包含一个操作,则该操作可以是未命名的或以简写形式表示,这两种形式都省略了 query 关键字和操作名称。否则,如果 GraphQL 文档包含多个操作,则每个操作都必须命名。当向 GraphQL 服务提交包含多个操作的文档时,还必须提供要执行的所需操作的名称。

仅寻求提供 GraphQL 查询执行的 GraphQL 服务可以选择仅包含 ExecutableDefinition,并从 Definition 中省略 TypeSystemDefinitionTypeSystemExtension 规则。

2.3操作

OperationType
querymutationsubscription

GraphQL 建模了三种类型的操作

每个操作都由一个可选的操作名称和一个选择集表示。

例如,此 mutation 操作可能“喜欢”一个故事,然后检索新的喜欢数量

Example № 5mutation {
  likeStory(storyID: 12345) {
    story {
      likeCount
    }
  }
}

查询简写

如果文档仅包含一个 query 操作,并且该 query 操作未定义任何变量且不包含任何指令,则该操作可以用简写形式表示,该形式省略了 query 关键字和 query 名称。

例如,此未命名的 query 操作是通过 query 简写编写的。

Example № 6{
  field
}
下面的许多示例将使用 query 简写语法。

2.4选择集

操作选择它需要的信息集,并将准确接收该信息,不多也不少,从而避免过度获取和获取不足数据。

Example № 7{
  id
  firstName
  lastName
}

在此查询中,idfirstNamelastName 字段形成一个选择集。选择集也可能包含片段引用。

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参数

ArgumentsConst
(ArgumentConstlist)

字段在概念上是返回值的函数,有时接受更改其行为的参数。这些参数通常直接映射到 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)
}

查询 noFragmentswithFragmentswithNestedFragments 都产生相同的响应对象。

2.8.1类型条件

片段必须指定它们应用到的类型。在此示例中,friendFields 可以在查询 User 的上下文中使用。

片段不能在任何输入值(标量、枚举或输入对象)上指定。

片段可以在对象类型、接口和联合类型上指定。

仅当片段操作的对象的具体类型与片段的类型匹配时,片段内的选择才会返回值。

例如,在此 Facebook 数据模型上的查询中

Example № 21query FragmentTyping {
  profiles(handles: ["zuck", "cocacola"]) {
    handle
    ...userFragment
    ...pageFragment
  }
}

fragment userFragment on User {
  friends {
    count
  }
}

fragment pageFragment on Page {
  likers {
    count
  }
}

profiles 根字段返回一个列表,其中每个元素可以是 PageUser。当 profiles 结果中的对象是 User 时,将存在 friends,而 likers 将不存在。相反,当结果是 Page 时,将存在 likers,而 friends 将不存在。

Example № 22{
  "profiles": [
    {
      "handle": "zuck",
      "friends": { "count" : 1234 }
    },
    {
      "handle": "cocacola",
      "likers": { "count" : 90234512 }
    }
  ]
}

2.8.2内联片段

片段可以在选择集中内联定义。这样做是为了根据运行时类型有条件地包含字段。标准片段包含的此功能在 query FragmentTyping 示例中得到了演示。我们可以使用内联片段完成同样的事情。

Example № 23query inlineFragmentTyping {
  profiles(handles: ["zuck", "cocacola"]) {
    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输入值

字段和指令参数接受各种字面量基本类型的输入值;输入值可以是标量、枚举值、列表或输入对象。

如果未定义为常量(例如,在 默认值 中),则输入值可以指定为变量。列表和输入对象也可以包含变量(除非定义为常量)。

2.9.1整数值

数字
0123456789

整数值是没有小数点或指数的数字(例如 1)。

2.9.2浮点数值

浮点数值包括小数点(例如 1.0)或指数(例如 1e50)或两者(例如 6.0221413e23)。

2.9.3布尔值

布尔值
truefalse

关键字 truefalse 表示两个布尔值。

2.9.4字符串值

StringValue
"字符串字符列表可选"
"""块字符串字符列表可选"""
转义 Unicode
/[0-9A-Fa-f]{4}/
转义字符
"\/bfnrt

字符串是用双引号 (") 包裹的字符序列。(例如 "Hello World")。空格和其他原本被忽略的字符在字符串值中具有意义。

Unicode 字符允许在字符串值字面量中使用,但是 源字符 不能包含某些 ASCII 控制字符,因此必须使用转义序列来表示这些字符。

块字符串

块字符串是用三引号 (""") 包裹的字符序列。空格、行终止符、引号和反斜杠字符都可以不转义地使用,以启用原文文本。字符必须都是有效的 源字符

由于块字符串表示通常在缩进位置使用的自由格式文本,因此块字符串的字符串值语义通过 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."""
如果字符串值中需要不可打印的 ASCII 字符,则必须使用带有适当转义序列的标准引号字符串,而不是块字符串。

语义

StringValue
"字符串字符列表可选"
  1. 返回所有 字符串字符 Unicode 字符值的 Unicode 字符序列(可能是一个空序列)。
字符串字符
  1. 返回 Unicode 基本多文种平面中代码单元值为 16 位十六进制值 转义 Unicode 的字符。
字符串字符
  1. 根据下表返回 转义字符 的字符值。
转义字符代码单元值字符名称
" U+0022双引号
\ U+005C反斜线
/ U+002F正斜线
b U+0008退格
f U+000C换页
n U+000A换行符(新行)
r U+000D回车符
t U+0009水平制表符
StringValue
"""块字符串字符列表可选"""
  1. rawValue 为所有 块字符串字符 Unicode 字符值的 Unicode 字符序列(可能是一个空序列)。
  2. 返回 BlockStringValue(rawValue) 的结果。
块字符串字符
源字符"""\"""
  1. 返回 源字符 的字符值。
块字符串字符
\"""
  1. 返回字符序列 """
BlockStringValue(rawValue)
  1. lines 为通过 行终止符 拆分 rawValue 的结果。
  2. commonIndentnull
  3. 对于 lines 中的每个 line
    1. 如果 linelines 中的第一个项目,则继续到下一行。
    2. lengthline 中的字符数。
    3. indentline 中前导连续 空白字符 的数量。
    4. 如果 indent 小于 length
      1. 如果 commonIndentnullindent 小于 commonIndent
        1. commonIndentindent
  4. 如果 commonIndent 不为 null
    1. 对于 lines 中的每个 line
      1. 如果 linelines 中的第一个项目,则继续到下一行。
      2. line 的开头删除 commonIndent 个字符。
  5. lines 中的第一个项目 line 仅包含 空白字符
    1. lines 中删除第一个项目。
  6. lines 中的最后一个项目 line 仅包含 空白字符
    1. lines 中删除最后一个项目。
  7. formatted 为空字符序列。
  8. 对于 lines 中的每个 line
    1. 如果 linelines 中的第一个项目
      1. line 附加到 formatted
    2. 否则
      1. 将换行符 (U+000A) 附加到 formatted
      2. line 附加到 formatted
  9. 返回 formatted

2.9.5空值

空值
null

空值表示为关键字 null

GraphQL 有两种语义上不同的方式来表示缺少值

  • 显式提供字面量值:null
  • 隐式完全不提供值。

例如,以下两个字段调用类似,但不完全相同

Example № 29{
  field(arg: null)
  field
}

第一个显式地为参数 “arg” 提供了 null,而第二个隐式地没有为参数 “arg” 提供值。这两种形式的解释可能不同。例如,一个突变表示删除一个字段,而另一个表示不更改一个字段。这两种形式都不能用于期望 Non‐Null 类型的输入。

通过变量表示缺少值的相同两种方法也是可能的,即提供变量值作为 null 和完全不提供变量值。

2.9.6枚举值

枚举值
名称truefalsenull

枚举值表示为不带引号的名称(例如 MOBILE_WEB)。建议枚举值使用“全大写”。枚举值仅在已知精确枚举类型的上下文中使用。因此,无需在字面量中提供枚举类型名称。

2.9.7列表值

列表值常量
[]
[条件常量列表]

列表是用方括号 [ ] 包裹的有序值序列。列表字面量的值可以是任何值字面量或变量(例如 [1, 2, 3])。

逗号在整个 GraphQL 中是可选的,因此允许尾随逗号,重复的逗号不表示缺少值。

语义

列表值
[]
  1. 返回一个新的空列表值。
列表值
[列表]
  1. inputList 为一个新的空列表值。
  2. 对于每个 列表
    1. value 为评估 的结果。
    2. value 附加到 inputList
  3. 返回 inputList

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 })
}

语义

对象值
{}
  1. 返回一个没有字段的新输入对象值。
对象值
{对象字段列表}
  1. inputObject 为一个没有字段的新输入对象值。
  2. 对于 对象字段列表 中的每个 field
    1. namefield 中的 名称
    2. value 为评估 field 中的 的结果。
    3. inputObject 添加一个名称为 name,包含值 value 的字段。
  3. 返回 inputObject

2.10变量

GraphQL 查询可以使用变量进行参数化,从而最大限度地提高查询重用率,并避免在客户端运行时进行代价高昂的字符串构建。

如果未定义为常量(例如,在 默认值 中),则可以为输入值提供 变量

变量必须在操作的顶部定义,并且在整个操作执行过程中都在作用域内。

在此示例中,我们希望根据特定设备的大小获取个人资料图片大小

Example № 32query getZuckProfile($devicePicSize: Int) {
  user(id: 4) {
    id
    name
    profilePic(size: $devicePicSize)
  }
}

这些变量的值与请求一起提供给 GraphQL 服务,以便在执行期间可以替换它们。如果为变量的值提供 JSON,我们可以运行此查询并请求宽度为 60 的 profilePic

Example № 33{
  "devicePicSize": 60
}

片段内的变量使用

查询变量可以在片段内使用。查询变量在给定的操作中具有全局作用域,因此在片段中使用的变量必须在任何传递使用该片段的顶层操作中声明。如果变量在片段中被引用,并且包含在一个未定义该变量的操作中,则该操作无法执行。

2.11类型引用

GraphQL 描述了查询变量期望的数据类型。输入类型可以是另一种输入类型的列表,或者任何其他输入类型的非空变体。

语义

类型
  1. name名称 的字符串值
  2. type 为 Schema 中名为 name 的类型
  3. type 不得为 null
  4. 返回 type
类型
  1. itemType 为评估 类型 的结果
  2. type 为列表类型,其中 itemType 是包含的类型。
  3. 返回 type
类型
  1. nullableType 为评估 类型 的结果
  2. type 为非空类型,其中 nullableType 是包含的类型。
  3. 返回 type

2.12指令

指令常量
指令条件常量列表
指令常量
@名称参数条件常量可选

指令提供了一种在 GraphQL 文档中描述备用运行时执行和类型验证行为的方法。

在某些情况下,您需要提供选项来更改 GraphQL 的执行行为,而字段参数不足以满足这些需求,例如有条件地包含或跳过字段。指令通过向执行器描述附加信息来提供此功能。

指令具有名称以及参数列表,这些参数可以接受任何输入类型的值。

指令可用于描述类型、字段、片段和操作的附加信息。

随着未来版本的 GraphQL 采用新的可配置执行能力,它们可能会通过指令公开。

3类型系统

GraphQL 类型系统描述了 GraphQL 服务器的功能,并用于确定查询是否有效。类型系统还描述了查询变量的输入类型,以确定运行时提供的值是否有效。

TypeSystemDefinition

GraphQL 语言包含一个 IDL,用于描述 GraphQL 服务的类型系统。工具可以使用此定义语言来提供实用程序,例如客户端代码生成或服务引导。

仅寻求提供 GraphQL 查询执行的 GraphQL 工具可以选择不解析 类型系统定义

包含 类型系统定义 的 GraphQL 文档不得执行;接收包含类型系统定义的 GraphQL 文档的 GraphQL 执行服务应返回描述性错误。

类型系统定义语言在本规范文档的其余部分中用于说明示例类型系统。

3.1类型系统扩展

类型系统扩展用于表示从某些原始类型系统扩展而来的 GraphQL 类型系统。例如,本地服务可以使用它来表示 GraphQL 客户端仅在本地访问的数据,或者 GraphQL 服务本身是另一个 GraphQL 服务的扩展。

3.2Schema

GraphQL 服务的集体类型系统功能被称为该服务的“schema”。schema 是根据它支持的类型和指令以及每种操作(查询、突变和订阅)的根操作类型来定义的;这决定了这些操作在类型系统中开始的位置。

GraphQL schema 本身必须是内部有效的。本节描述了与此验证过程相关的规则。

GraphQL schema 中的所有类型都必须具有唯一的名称。提供的任何两个类型都不得具有相同的名称。提供的任何类型都不得具有与任何内置类型(包括标量和内省类型)冲突的名称。

GraphQL schema 中的所有指令都必须具有唯一的名称。

schema 中定义的所有类型和指令的名称都不得以 "__"(两个下划线)开头,因为这专门用于 GraphQL 的内省系统。

3.2.1根操作类型

schema 为其支持的每种操作(查询、突变和订阅)定义初始根操作类型;这决定了这些操作在类型系统中开始的位置。

必须提供 query 根操作类型,并且必须是对象类型。

mutation 根操作类型是可选的;如果未提供,则服务不支持突变。如果提供了,则必须是对象类型。

类似地,subscription 根操作类型也是可选的;如果未提供,则服务不支持订阅。如果提供了,则必须是对象类型。

query 根操作类型上的字段指示在 GraphQL 查询的顶层可用的字段。例如,基本的 GraphQL 查询,如

Example № 34query {
  myName
}

query 根操作类型具有名为 “myName” 的字段时有效。

Example № 35type Query {
  myName: String
}

同样地,如果 mutation 根操作类型有一个名为“setName”的字段,则以下 mutation 是有效的。请注意,querymutation 根类型必须是不同的类型。

Example № 36mutation {
  setName(name: "Zuck") {
    newName
  }
}

当使用类型系统定义语言时,一个文档最多只能包含一个 schema 定义。

在此示例中,GraphQL 模式定义了 query 和 mutation 根类型

Example № 37schema {
  query: MyQueryRootType
  mutation: MyMutationRootType
}

type MyQueryRootType {
  someField: String
}

type MyMutationRootType {
  setSomeField(to: String): String
}

默认根操作类型名称

虽然任何类型都可以作为 GraphQL 操作的根操作类型,但当 querymutationsubscription 根类型分别命名为 QueryMutationSubscription 时,类型系统定义语言可以省略 schema 定义。

同样地,当使用类型系统定义语言表示 GraphQL 模式时,如果模式仅使用默认根操作类型名称,则应省略 schema 定义。

此示例描述了一个有效的完整 GraphQL 模式,尽管未显式包含 schema 定义。Query 类型被推定为模式的 query 根操作类型。

Example № 38type Query {
  someField: String
}

3.2.2模式扩展

SchemaExtension
extendschemaDirectivesConstopt{OperationTypeDefinitionlist}
extendschemaDirectivesConst

模式扩展用于表示从原始模式扩展而来的模式。例如,GraphQL 服务可能会使用它来向现有模式添加额外的操作类型或额外的指令。

模式验证

如果模式扩展定义不正确,则可能无效。

  1. 模式必须已定义。
  2. 提供的任何指令都不得已应用于原始模式。

3.3描述

文档是 GraphQL 类型系统的一流特性。为了确保 GraphQL 服务的文档与其功能保持一致,GraphQL 定义的描述与其定义一起提供,并通过内省提供。

为了使 GraphQL 服务设计者能够轻松地发布与其 GraphQL 服务功能相关的文档,GraphQL 描述使用 Markdown 语法(如 CommonMark 所指定)定义。在类型系统定义语言中,这些描述字符串(通常是 BlockString)出现在它们描述的定义之前。

所有 GraphQL 类型、字段、参数和其他可以描述的定义都应提供 Description,除非它们被认为是自描述的。

例如,这个简单的 GraphQL 模式就得到了很好的描述

Example № 39"""
A simple GraphQL schema which is well described.
"""
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.4类型

任何 GraphQL 模式的基本单元都是类型。GraphQL 中有六种命名的类型定义和两种包装类型。

最基本的类型是 Scalar。标量表示原始值,例如字符串或整数。通常,标量字段的可能响应是可枚举的。GraphQL 在这些情况下提供 Enum 类型,其中该类型指定了有效响应的空间。

标量和枚举构成了响应树中的叶子节点;中间层是 Object 类型,它定义了一组字段,其中每个字段都是系统中的另一种类型,从而允许定义任意类型层次结构。

GraphQL 支持两种抽象类型:接口和联合。

Interface 定义了一个字段列表;实现该接口的 Object 类型保证实现这些字段。每当类型系统声明它将返回一个接口时,它将返回一个有效的实现类型。

Union 定义了一个可能的类型列表;与接口类似,每当类型系统声明将返回一个联合时,将返回其中一种可能的类型。

最后,通常将复杂结构作为 GraphQL 字段参数或变量的输入非常有用;Input Object 类型允许模式精确定义期望的数据。

3.4.1包装类型

到目前为止,所有类型都被假定为既可为空又为单数:例如,标量字符串返回 null 或单个字符串。

GraphQL 模式可以描述一个字段表示另一种类型的列表;为此提供了 List 类型,并包装了另一种类型。

类似地,Non-Null 类型包装了另一种类型,并表示结果值永远不会是 null(并且错误不会导致 null 值)。

这两种类型被称为“包装类型”;非包装类型被称为“命名类型”。包装类型具有一个底层命名类型,通过不断解包类型直到找到命名类型为止。

3.4.2输入和输出类型

类型在整个 GraphQL 中用于描述作为参数和变量输入接受的值,以及字段输出的值。这两种用途将类型分类为输入类型输出类型。某些类型的类型,如标量和枚举类型,既可以用作输入类型,也可以用作输出类型;其他类型的类型只能用于其中一种。输入对象类型只能用作输入类型。对象、接口和联合类型只能用作输出类型。列表和非空类型可以用作输入类型或输出类型,具体取决于包装类型的使用方式。

IsInputType(type)
  1. 如果 type 是列表类型或非空类型
    1. unwrappedTypetype 的解包类型。
    2. 返回 IsInputType(unwrappedType)
  2. 如果 type 是标量、枚举或输入对象类型
    1. 返回 true
  3. 返回 false
IsOutputType(type)
  1. 如果 type 是列表类型或非空类型
    1. unwrappedTypetype 的解包类型。
    2. 返回 IsOutputType(unwrappedType)
  2. 如果 type 是标量、对象、接口、联合或枚举类型
    1. 返回 true
  3. 返回 false

3.4.3类型扩展

类型扩展用于表示从某些原始类型扩展而来的 GraphQL 类型。例如,本地服务可以使用它来表示 GraphQL 客户端仅在本地访问的额外字段。

3.5标量

标量类型表示 GraphQL 类型系统中的原始叶子值。GraphQL 响应采用分层树的形式;这些树上的叶子是 GraphQL 标量。

所有 GraphQL 标量都可以表示为字符串,尽管根据所使用的响应格式,对于给定的标量类型可能存在更合适的原始类型,服务器应在适当时使用这些类型。

GraphQL 提供了许多内置标量,但类型系统可以添加具有语义含义的额外标量。例如,GraphQL 系统可以定义一个名为 Time 的标量,它虽然序列化为字符串,但承诺符合 ISO‐8601。当查询 Time 类型的字段时,您可以依赖使用 ISO‐8601 解析器解析结果并为时间使用特定于客户端的原始类型的功能。另一个可能有用的自定义标量的例子是 Url,它序列化为字符串,但服务器保证它是有效的 URL。

Example № 40scalar Time
scalar Url

服务器可以从其模式中省略任何内置标量,例如,如果模式不引用浮点数,则它不得包含 Float 类型。但是,如果模式包含一个名称与此处描述的类型之一相同的类型,则它必须遵守所描述的行为。例如,服务器不得包含名为 Int 的类型并使用它来表示 128 位数字、国际化信息或本文档中定义以外的任何内容。

当使用类型系统定义语言表示 GraphQL 模式时,为了简洁起见,应省略内置标量类型。

结果强制转换

GraphQL 服务器在准备给定标量类型的字段时,必须遵守标量类型描述的约定,通过强制转换值或在无法强制转换值或强制转换可能导致数据丢失时生成字段错误。

GraphQL 服务可以决定允许将不同的内部类型强制转换为预期的返回类型。例如,当强制转换 Int 类型的字段时,布尔值 true 可能会生成 1,或者字符串值 "123" 可能会被解析为十进制 123。但是,如果内部类型强制转换在不丢失信息的情况下无法合理执行,则必须引发字段错误。

由于客户端无法观察到这种强制转换行为,因此强制转换的精确规则留给实现来决定。唯一的要求是服务器必须生成符合预期标量类型的值。

输入强制转换

如果 GraphQL 服务器期望标量类型作为参数的输入,则强制转换是可观察的,并且规则必须明确定义。如果输入值与强制转换规则不匹配,则必须引发查询错误。

GraphQL 具有不同的常量字面量来表示整数和浮点输入值,并且强制转换规则可能因遇到的输入值类型而异。GraphQL 可以通过查询变量进行参数化,查询变量的值通常在通过 HTTP 等传输发送时进行序列化。由于某些常见的序列化(例如 JSON)不区分整数值和浮点值,因此如果它们具有空的小数部分(例如 1.0),则将它们解释为整数输入值,否则将其解释为浮点输入值。

对于以下所有类型,除了 Non‐Null 之外,如果提供了显式值 null,则输入强制转换的结果为 null

内置标量

GraphQL 提供了一组基本的、定义良好的标量类型。GraphQL 服务器应支持所有这些类型,并且提供这些名称类型的 GraphQL 服务器必须遵守以下描述的行为。

3.5.1Int

Int 标量类型表示有符号 32 位数字非小数数值。支持 32 位整数或数字类型的响应格式应使用该类型来表示此标量。

结果强制转换

返回 Int 类型的字段期望遇到 32 位整数内部值。

当 GraphQL 服务器在不丢失信息的情况下合理地将非整数内部值强制转换为整数时,它们可能会这样做,否则它们必须引发字段错误。这方面的例子可能包括为浮点数 1.0 返回 1,或为字符串 "123" 返回 123。在强制转换可能丢失数据的情况下,引发字段错误更为合适。例如,浮点数 1.2 应引发字段错误,而不是被截断为 1

如果整数内部值表示小于 -231 或大于或等于 231 的值,则应引发字段错误。

输入强制转换

当预期作为输入类型时,仅接受整数输入值。所有其他输入值,包括具有数字内容的字符串,都必须引发查询错误,指示类型不正确。如果整数输入值表示小于 -231 或大于或等于 231 的值,则应引发查询错误。

大于 32 位的数字整数值应使用 String 或自定义定义的标量类型,因为并非所有平台和传输都支持编码大于 32 位的整数。

3.5.2Float

Float 标量类型表示由 IEEE 754 指定的有符号双精度小数数值。支持适当双精度数字类型的响应格式应使用该类型来表示此标量。

结果强制转换

返回 Float 类型的字段期望遇到双精度浮点内部值。

当 GraphQL 服务器在不丢失信息的情况下合理地将非浮点内部值强制转换为 Float 时,它们可能会这样做,否则它们必须引发字段错误。这方面的例子可能包括为整数 1 返回 1.0,或为字符串 "123" 返回 123.0

输入强制转换

当预期作为输入类型时,整数和浮点输入值都被接受。整数输入值通过添加空的小数部分强制转换为 Float,例如,整数输入值 1 的值为 1.0。所有其他输入值,包括具有数字内容的字符串,都必须引发查询错误,指示类型不正确。如果整数输入值表示 IEEE 754 无法表示的值,则应引发查询错误。

3.5.3String

String 标量类型表示文本数据,表示为 UTF‐8 字符序列。String 类型最常被 GraphQL 用于表示自由形式的人类可读文本。所有响应格式都必须支持字符串表示形式,并且此处必须使用该表示形式。

结果强制转换

返回 String 类型的字段期望遇到 UTF‐8 字符串内部值。

当 GraphQL 服务器在不丢失信息的情况下合理地将非字符串原始值强制转换为 String 时,它们可能会这样做,否则它们必须引发字段错误。这方面的例子可能包括为布尔值 true 返回字符串 "true",或为整数 1 返回字符串 "1"

输入强制转换

当预期作为输入类型时,仅接受有效的 UTF‐8 字符串输入值。所有其他输入值都必须引发查询错误,指示类型不正确。

3.5.4Boolean

Boolean 标量类型表示 truefalse。响应格式应使用内置布尔类型(如果支持);否则,它们应使用整数 10 的表示形式。

结果强制转换

返回 Boolean 类型的字段期望遇到布尔内部值。

当 GraphQL 服务器在不丢失信息的情况下合理地将非布尔原始值强制转换为 Boolean 时,它们可能会这样做,否则它们必须引发字段错误。这方面的例子可能包括为非零数字返回 true

输入强制转换

当预期作为输入类型时,仅接受布尔输入值。所有其他输入值都必须引发查询错误,指示类型不正确。

3.5.5ID

ID 标量类型表示唯一标识符,通常用于重新获取对象或作为缓存的键。ID 类型的序列化方式与 String 相同;但是,它不应该是人类可读的。虽然它通常是数字,但它应始终序列化为 String

结果强制转换

GraphQL 与 ID 格式无关,并序列化为字符串以确保 ID 可能表示的多种格式的一致性,从小的自动递增数字到大的 128 位随机数,到 base64 编码值,或格式如 GUID 的字符串值。

GraphQL 服务器应根据它们期望的 ID 格式进行适当的强制转换。当无法进行强制转换时,它们必须引发字段错误。

输入强制转换

当预期作为输入类型时,任何字符串(例如 "4")或整数(例如 4)输入值都应根据给定 GraphQL 服务器期望的 ID 格式强制转换为 ID。任何其他输入值,包括浮点输入值(例如 4.0),都必须引发查询错误,指示类型不正确。

3.5.6标量扩展

标量类型扩展用于表示从某些原始标量类型扩展而来的标量类型。例如,GraphQL 工具或服务可以使用它来向现有标量添加指令。

类型验证

如果标量类型扩展定义不正确,则可能无效。

  1. 命名类型必须已定义,并且必须是标量类型。
  2. 提供的任何指令都不得已应用于原始标量类型。

3.6对象

GraphQL 查询是分层和组合的,描述了一个信息树。虽然标量类型描述了这些分层查询的叶子值,但对象描述了中间层。

GraphQL 对象表示命名字段的列表,每个字段都产生特定类型的值。对象值应序列化为有序映射,其中查询的字段名称(或别名)是键,而评估字段的结果是值,按它们在查询中出现的顺序排序。

在对象类型中定义的所有字段都不得具有以 "__"(两个下划线)开头的名称,因为这由 GraphQL 的内省系统独占使用。

例如,类型 Person 可以描述为

Example № 41type Person {
  name: String
  age: Int
  picture: Url
}

其中 name 是一个将产生 String 值的字段,而 age 是一个将产生 Int 值的字段,而 picture 是一个将产生 Url 值的字段。

对象值的查询必须至少选择一个字段。此字段选择将产生一个有序映射,其中包含对象查询的确切子集,该子集应以它们被查询的顺序表示。只有在对象类型上声明的字段才能在该对象上进行有效查询。

例如,选择 Person 的所有字段

Example № 42{
  name
  age
  picture
}

将产生对象

Example № 43{
  "name": "Mark Zuckerberg",
  "age": 30,
  "picture": "http://some.cdn/picture.jpg"
}

而选择字段的子集

Example № 44{
  age
  name
}

必须只产生该子集

Example № 45{
  "age": 30,
  "name": "Mark Zuckerberg"
}

对象类型的字段可以是标量、枚举、另一个对象类型、接口或联合。此外,它可以是任何包装类型,其底层基本类型是这五种类型之一。

例如,Person 类型可能包含 relationship

Example № 46type Person {
  name: String
  age: Int
  picture: Url
  relationship: Person
}

有效查询必须为返回对象的字段提供嵌套字段集,因此此查询无效

Counter Example № 47{
  name
  relationship
}

但是,此示例有效

Example № 48{
  name
  relationship {
    name
  }
}

并将产生每个查询对象类型的子集

Example № 49{
  "name": "Mark Zuckerberg",
  "relationship": {
    "name": "Priscilla Chan"
  }
}

字段排序

当查询对象时,字段的结果映射在概念上按照它们在查询执行期间遇到的相同顺序排序,不包括类型不适用的片段以及通过 @skip@include 指令跳过的字段或片段。当使用 CollectFields() 算法时,可以正确生成此排序。

能够表示有序映射的响应序列化格式应保持此排序。只能表示无序映射的序列化格式(例如 JSON)应以文本形式保留此顺序。也就是说,如果按 {foo, bar} 的顺序查询了两个字段,则生成的 JSON 序列化应包含 {"foo": "...", "bar": "..."},顺序相同。

生成响应,其中字段以它们在请求中出现的相同顺序表示,可以提高调试期间的人类可读性,并且如果可以预测属性的顺序,则可以更有效地解析响应。

如果片段在其他字段之前展开,则该片段指定的字段在响应中出现在后续字段之前。

Example № 50{
  foo
  ...Frag
  qux
}

fragment Frag on Query {
  bar
  baz
}

产生有序结果

Example № 51{
  "foo": 1,
  "bar": 2,
  "baz": 3,
  "qux": 4
}

如果在选择中多次查询一个字段,则按第一次遇到它的时间排序。但是,类型不适用的片段不会影响排序。

Example № 52{
  foo
  ...Ignored
  ...Matching
  bar
}

fragment Ignored on UnknownType {
  qux
  baz
}

fragment Matching on Query {
  bar
  qux
  foo
}

产生有序结果

Example № 53{
  "foo": 1,
  "bar": 2,
  "qux": 3
}

此外,如果指令导致字段被排除,则在字段排序中不考虑它们。

Example № 54{
  foo @skip(if: true)
  bar
  foo
}

产生有序结果

Example № 55{
  "bar": 1,
  "foo": 2
}

结果强制转换

确定强制转换对象的结果是 GraphQL 执行器的核心,因此这在规范的该部分中介绍。

输入强制转换

对象永远不是有效的输入。

类型验证

如果对象类型定义不正确,则可能无效。GraphQL 模式中的每个对象类型都必须遵守这组规则。

  1. 对象类型必须定义一个或多个字段。
  2. 对于对象类型的每个字段
    1. 该字段在该对象类型中必须具有唯一的名称;任何两个字段都不得共享相同的名称。
    2. 该字段不得具有以字符 "__"(两个下划线)开头的名称。
    3. 该字段必须返回一个类型,其中 IsOutputType(fieldType) 返回 true
    4. 对于字段的每个参数
      1. 该参数不得具有以字符 "__"(两个下划线)开头的名称。
      2. 该参数必须接受一个类型,其中 IsInputType(argumentType) 返回 true
  3. 对象类型可以声明它实现一个或多个唯一接口。
  4. 对象类型必须是它实现的所有接口的超集
    1. 对于接口中定义的每个字段,对象类型必须包含一个同名字段。
      1. 对象字段的类型必须是接口字段的类型或子类型(协变)。
        1. 如果对象字段类型与接口字段类型相等(相同类型),则它是有效的子类型。
        2. 如果对象字段类型是对象类型,并且接口字段类型是接口类型或联合类型,并且对象字段类型是接口字段类型的可能类型,则它是有效的子类型。
        3. 如果对象字段类型是列表类型,并且接口字段类型也是列表类型,并且对象字段类型的列表项类型是接口字段类型的列表项类型的有效子类型,则它是有效的子类型。
        4. 如果对象字段类型是接口字段类型的有效子类型的非空变体,则它是有效的子类型。
      2. 对于接口字段中定义的每个参数,对象字段必须包含一个同名参数。
        1. 对象字段参数必须接受与接口字段参数相同的类型(不变)。
      3. 对象字段可以包含接口字段中未定义的其他参数,但是任何额外的参数都不能是必需的,例如,不能是非空类型。

3.6.1字段参数

对象字段在概念上是产生值的函数。有时,对象字段可以接受参数以进一步指定返回值。对象字段参数被定义为所有可能的参数名称及其预期输入类型的列表。

字段中定义的所有参数都不得具有以 "__"(两个下划线)开头的名称,因为这由 GraphQL 的内省系统独占使用。

例如,具有 picture 字段的 Person 类型可以接受一个参数来确定要返回的图像大小。

Example № 56type Person {
  name: String
  picture(size: Int): Url
}

GraphQL 查询可以选择性地为其字段指定参数以提供这些参数。

此示例查询

Example № 57{
  name
  picture(size: 600)
}

可能产生结果

Example № 58{
  "name": "Mark Zuckerberg",
  "picture": "http://some.cdn/picture_600.jpg"
}

对象字段参数的类型必须是输入类型(对象、接口或联合类型以外的任何类型)。

3.6.2字段弃用

对象中的字段可以根据应用程序的需要标记为已弃用。查询这些字段仍然是合法的(以确保现有客户端不会因更改而中断),但应在文档和工具中对这些字段进行适当处理。

当使用类型系统定义语言时,@deprecated 指令用于指示字段已被弃用

Example № 59type ExampleType {
  oldField: String @deprecated
}

3.6.3对象扩展

对象类型扩展用于表示从某些原始类型扩展而来的类型。例如,这可以用于表示本地数据,或者由本身是另一个 GraphQL 服务的扩展的 GraphQL 服务使用。

在此示例中,本地数据字段被添加到 Story 类型

Example № 60extend type Story {
  isHiddenLocally: Boolean
}

对象类型扩展可以选择不添加额外的字段,而是仅添加接口或指令。

在此示例中,指令被添加到 User 类型,而没有添加字段

Example № 61extend type User @addedDirective

类型验证

如果对象类型扩展定义不正确,则可能无效。

  1. 命名的类型必须已定义,并且必须是对象类型。
  2. 对象类型扩展的字段必须具有唯一的名称;任何两个字段都不得共享相同的名称。
  3. 对象类型扩展的任何字段都不得已在原始对象类型上定义。
  4. 提供的任何指令都不得已应用于原始对象类型。
  5. 提供的任何接口都不得已被原始对象类型实现。
  6. 最终扩展的对象类型必须是其实现的所有接口的超集。

3.7接口

GraphQL 接口表示命名字段及其参数的列表。然后,GraphQL 对象可以实现这些接口,这要求对象类型将定义这些接口定义的所有字段。

GraphQL 接口上的字段与 GraphQL 对象上的字段具有相同的规则;它们的类型可以是标量、对象、枚举、接口或联合,或者任何包装类型,其基本类型是这五种之一。

例如,接口 NamedEntity 可以描述一个必需的字段,而诸如 PersonBusiness 之类的类型可以实现此接口,以保证此字段将始终存在。

类型也可以实现多个接口。例如,在下面的示例中,Business 同时实现了 NamedEntityValuedEntity 接口。

Example № 62interface 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 № 63type Contact {
  entity: NamedEntity
  phoneNumber: String
  address: String
}

这允许我们为 Contact 编写查询,该查询可以选择通用字段。

Example № 64{
  entity {
    name
  }
  phoneNumber
}

当查询接口类型上的字段时,只能查询在该接口上声明的字段。在上面的示例中,entity 返回 NamedEntity,并且 nameNamedEntity 上定义,因此它是有效的。但是,以下查询将无效

Counter Example № 65{
  entity {
    name
    age
  }
  phoneNumber
}

因为 entity 引用 NamedEntity,而 age 未在该接口上定义。仅当 entity 的结果为 Person 时,查询 age 才有效;查询可以使用片段或内联片段来表达这一点

Example № 66{
  entity {
    name
    ... on Person {
      age
    }
  },
  phoneNumber
}

结果强制转换

接口类型应具有某种方式来确定给定结果对应于哪个对象。一旦完成,接口的结果强制转换与对象的结果强制转换相同。

输入强制转换

接口永远不是有效的输入。

类型验证

如果接口类型定义不正确,则可能无效。

  1. 接口类型必须定义一个或多个字段。
  2. 对于接口类型的每个字段
    1. 该字段必须在该接口类型中具有唯一的名称;任何两个字段都不得共享相同的名称。
    2. 该字段不得具有以字符 "__"(两个下划线)开头的名称。
    3. 该字段必须返回一个类型,其中 IsOutputType(fieldType) 返回 true
    4. 对于字段的每个参数
      1. 该参数不得具有以字符 "__"(两个下划线)开头的名称。
      2. 该参数必须接受一个类型,其中 IsInputType(argumentType) 返回 true

3.7.1接口扩展

接口类型扩展用于表示从某些原始接口扩展而来的接口。例如,这可以用于表示许多类型上的通用本地数据,或者由本身是另一个 GraphQL 服务的扩展的 GraphQL 服务使用。

在此示例中,扩展数据字段被添加到 NamedEntity 类型以及实现它的类型

Example № 67extend interface NamedEntity {
  nickname: String
}

extend type Person {
  nickname: String
}

extend type Business {
  nickname: String
}

接口类型扩展可以选择不添加额外的字段,而是仅添加指令。

在此示例中,指令被添加到 NamedEntity 类型,而没有添加字段

Example № 68extend interface NamedEntity @addedDirective

类型验证

如果接口类型扩展定义不正确,则可能无效。

  1. 命名的类型必须已定义,并且必须是接口类型。
  2. 接口类型扩展的字段必须具有唯一的名称;任何两个字段都不得共享相同的名称。
  3. 接口类型扩展的任何字段都不得已在原始接口类型上定义。
  4. 任何实现了原始接口类型的对象类型也必须是接口类型扩展字段的超集(这可能是由于对象类型扩展)。
  5. 提供的任何指令都不得已应用于原始接口类型。

3.8联合类型

GraphQL 联合类型表示一个对象,它可以是 GraphQL 对象类型列表中的一种,但不提供这些类型之间任何保证的字段。它们也与接口不同,因为对象类型声明它们实现了哪些接口,但不知道哪些联合类型包含它们。

对于接口和对象,只能直接查询在该类型上定义的字段;要查询接口上的其他字段,必须使用类型化的片段。这与联合类型相同,但是联合类型不定义任何字段,因此没有字段可以在不使用类型细化片段或内联片段的情况下在此类型上查询。

例如,我们可以定义以下类型

Example № 69union SearchResult = Photo | Person

type Person {
  name: String
  age: Int
}

type Photo {
  height: Int
  width: Int
}

type SearchQuery {
  firstSearchResult: SearchResult
}

当查询 SearchQuery 类型的 firstSearchResult 字段时,查询将要求片段内部的所有字段,指示适当的类型。如果查询想要在结果是 Person 时获取名称,并在结果是照片时获取高度,则以下查询无效,因为联合类型本身未定义任何字段

Counter Example № 70{
  firstSearchResult {
    name
    height
  }
}

相反,查询将是

Example № 71{
  firstSearchResult {
    ... on Person {
      name
    }
    ... on Photo {
      height
    }
  }
}

联合类型的成员可以使用可选的前导 | 字符定义,以帮助在表示更长的可能类型列表时进行格式化

Example № 72union SearchResult =
  | Photo
  | Person

结果强制转换

联合类型应具有某种方式来确定给定结果对应于哪个对象。一旦完成,联合类型的结果强制转换与对象的结果强制转换相同。

输入强制转换

联合类型永远不是有效的输入。

类型验证

如果联合类型定义不正确,则可能无效。

  1. 联合类型必须包含一个或多个唯一的成员类型。
  2. 联合类型的成员类型必须全部是对象基本类型;标量、接口和联合类型不得是联合类型的成员类型。同样,包装类型不得是联合类型的成员类型。

3.8.1联合类型扩展

联合类型扩展用于表示从某些原始联合类型扩展而来的联合类型。例如,这可以用于表示额外的本地数据,或者由本身是另一个 GraphQL 服务的扩展的 GraphQL 服务使用。

类型验证

如果联合类型扩展定义不正确,则可能无效。

  1. 命名的类型必须已定义,并且必须是联合类型。
  2. 联合类型扩展的成员类型必须全部是对象基本类型;标量、接口和联合类型不得是联合类型的成员类型。同样,包装类型不得是联合类型的成员类型。
  3. 联合类型扩展的所有成员类型都必须是唯一的。
  4. 联合类型扩展的所有成员类型都不得已经是原始联合类型的成员。
  5. 提供的任何指令都不得已应用于原始联合类型。

3.9枚举

GraphQL 枚举类型,与标量类型一样,也表示 GraphQL 类型系统中的叶值。但是,枚举类型描述了可能值的集合。

枚举不是数值的引用,而是其自身唯一的价值。它们可以序列化为字符串:表示值的名称。

在此示例中,定义了一个名为 Direction 的枚举类型

Example № 73enum Direction {
  NORTH
  EAST
  SOUTH
  WEST
}

结果强制转换

GraphQL 服务器必须返回定义的可能值集之一。如果无法进行合理的强制转换,则它们必须引发字段错误。

输入强制转换

GraphQL 具有常量字面量来表示枚举输入值。GraphQL 字符串字面量不得被接受为枚举输入,而应引发查询错误。

对于非字符串符号值具有不同表示形式的查询变量传输序列化(例如,EDN)应仅允许此类值作为枚举输入值。否则,对于大多数不这样做的传输序列化,字符串可以解释为具有相同名称的枚举输入值。

类型验证

如果枚举类型定义不正确,则可能无效。

  1. 枚举类型必须定义一个或多个唯一的枚举值。

3.9.1枚举扩展

枚举类型扩展用于表示从某些原始枚举类型扩展而来的枚举类型。例如,这可以用于表示额外的本地数据,或者由本身是另一个 GraphQL 服务的扩展的 GraphQL 服务使用。

类型验证

如果枚举类型扩展定义不正确,则可能无效。

  1. 命名的类型必须已定义,并且必须是枚举类型。
  2. 枚举类型扩展的所有值都必须是唯一的。
  3. 枚举类型扩展的所有值都不得已经是原始枚举的值。
  4. 提供的任何指令都不得已应用于原始枚举类型。

3.10输入对象

字段可以接受参数来配置其行为。这些输入通常是标量或枚举,但它们有时需要表示更复杂的值。

GraphQL 输入对象定义了一组输入字段;输入字段可以是标量、枚举或其他输入对象。这允许参数接受任意复杂的结构。

在此示例中,名为 Point2D 的输入对象描述了 xy 输入

Example № 74input Point2D {
  x: Float
  y: Float
}
上面定义的 GraphQL 对象类型(ObjectTypeDefinition)不适合在此处重用,因为对象类型可以包含定义参数或包含对接口和联合的引用的字段,这两者都不适合用作输入参数。因此,输入对象在系统中具有单独的类型。

结果强制转换

输入对象永远不是有效的结果。输入对象类型不能是对象或接口字段的返回类型。

输入强制转换

输入对象的值应为输入对象字面量或由变量提供无序映射,否则必须抛出查询错误。在任何一种情况下,输入对象字面量或无序映射都不得包含任何名称不由该输入对象类型的字段定义的条目,否则必须抛出错误。

强制转换的结果是一个无序映射,其中包含输入对象类型定义的每个字段的条目,以及存在值的字段的条目。结果映射使用以下规则构建

以下是输入对象类型的输入强制转换示例,其中包含 String 字段 a 和必需的(非空)Int! 字段 b

Example № 75input 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

类型验证

  1. 输入对象类型必须定义一个或多个输入字段。
  2. 对于输入对象类型的每个输入字段
    1. 输入字段必须在该输入对象类型中具有唯一的名称;任何两个输入字段都不得共享相同的名称。
    2. 输入字段的名称不得以字符 "__"(两个下划线)开头。
    3. 输入字段必须接受类型,其中 IsInputType(inputFieldType) 返回 true

3.10.1输入对象扩展

输入对象类型扩展用于表示从某些原始输入对象类型扩展而来的输入对象类型。例如,这可以由本身是另一个 GraphQL 服务的扩展的 GraphQL 服务使用。

类型验证

如果输入对象类型扩展定义不正确,则可能无效。

  1. 命名的类型必须已定义,并且必须是输入对象类型。
  2. 输入对象类型扩展的所有字段都必须具有唯一的名称。
  3. 输入对象类型扩展的所有字段都不得已经是原始输入对象的字段。
  4. 提供的任何指令都不得已应用于原始输入对象类型。

3.11列表

GraphQL 列表是一种特殊的集合类型,它声明列表中每个项的类型(称为列表的项类型)。列表值序列化为有序列表,其中列表中的每个项都按照项类型进行序列化。要表示字段使用列表类型,项类型用方括号括起来,如下所示:pets: [Pet]

结果强制转换

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] 错误:项值不正确
[[Int]] 1 [[1]]
[[Int]] null null

3.12非空

默认情况下,GraphQL 中的所有类型都是可为空的;null 值对于上述所有类型都是有效的响应。要声明不允许为空的类型,可以使用 GraphQL 非空类型。此类型包装了一个底层类型,并且此类型的行为与该包装类型完全相同,但 null 不是包装类型的有效响应除外。尾随感叹号用于表示使用非空类型的字段,如下所示:name: String!

可为空与可选

字段在查询上下文中始终是可选的,可以省略字段,查询仍然有效。但是,如果查询返回非空类型的字段,则永远不会返回 null 值。

输入(例如字段参数)默认情况下始终是可选的。但是,非空输入类型是必需的。除了不接受值 null 之外,它也不接受省略。为了简单起见,可为空的类型始终是可选的,而非空类型始终是必需的。

结果强制转换

在上述所有结果强制转换中,null 被认为是有效值。要强制转换非空类型的结果,应执行包装类型的强制转换。如果该结果不是 null,则强制转换非空类型的结果就是该结果。如果该结果是 null,则必须引发字段错误。

当在非空值上引发字段错误时,错误会传播到父字段。有关此过程的更多信息,请参阅执行部分中的“错误和非空性”。

输入强制转换

如果未提供非空类型的参数或输入对象字段,或者提供了字面量值 null,或者提供了运行时未提供值的变量,或者提供了值 null,则必须引发查询错误。

如果提供给非空类型的值是字面量值(而不是 null)或非空变量值,则使用包装类型的输入强制转换对其进行强制转换。

非空参数不能省略

Counter Example № 76{
  fieldWithNonNullArg
}

null 不能提供给非空参数

Counter Example № 77{
  fieldWithNonNullArg(nonNullArg: null)
}

可为空类型的变量不能提供给非空参数

Example № 78query withNullableVariable($var: String) {
  fieldWithNonNullArg(nonNullArg: $var)
}
验证部分将为非空输入类型提供可为空变量类型定义为无效。

类型验证

  1. 非空类型不得包装另一个非空类型。

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, 错误] [1, 2, null] (带有记录的错误)
[Int]! [1, 2, 3] [1, 2, 3]
[Int]! null 错误:值不能为空
[Int]! [1, 2, null] [1, 2, null]
[Int]! [1, 2, 错误] [1, 2, null] (带有记录的错误)
[Int!] [1, 2, 3] [1, 2, 3]
[Int!] null null
[Int!] [1, 2, null] null (带有记录的强制转换错误)
[Int!] [1, 2, 错误] null (带有记录的错误)
[Int!]! [1, 2, 3] [1, 2, 3]
[Int!]! null 错误:值不能为空
[Int!]! [1, 2, null] 错误:项不能为空
[Int!]! [1, 2, 错误] 错误:项中发生错误

3.13指令

ExecutableDirectiveLocation
QUERY
MUTATION
SUBSCRIPTION
FIELD
FRAGMENT_DEFINITION
FRAGMENT_SPREAD
INLINE_FRAGMENT
TypeSystemDirectiveLocation
SCHEMA
SCALAR
OBJECT
FIELD_DEFINITION
ARGUMENT_DEFINITION
INTERFACE
UNION
ENUM
ENUM_VALUE
INPUT_OBJECT
INPUT_FIELD_DEFINITION

GraphQL 模式描述了指令,这些指令用于注释 GraphQL 文档的各个部分,以指示验证器、执行器或客户端工具(例如代码生成器)应以不同的方式对其进行评估。

GraphQL 实现应提供 @skip@include 指令。

如果 GraphQL 实现支持类型系统定义语言,则在表示模式的已弃用部分时,必须提供 @deprecated 指令。

指令必须仅在声明它们所属的位置使用。在此示例中,定义了一个指令,该指令可用于注释片段定义

Example № 79directive @example on FIELD

fragment SomeFragment on SomeType {
  field @example
}

指令位置可以使用可选的前导 | 字符定义,以帮助在表示更长的可能位置列表时进行格式化

Example № 80directive @example on
  | FIELD
  | FRAGMENT_SPREAD
  | INLINE_FRAGMENT

指令还可以用于注释类型系统定义语言,这对于提供额外的元数据以生成 GraphQL 执行服务、生成客户端生成的运行时代码或 GraphQL 语义的许多其他有用扩展可能是一个有用的工具。

在此示例中,指令 @example 注释了字段和参数定义

Example № 81directive @example on FIELD_DEFINITION | ARGUMENT_DEFINITION

type SomeType {
  field(arg: Int @example): String @example
}

在定义指令时,它不得直接或间接地引用自身

Counter Example № 82directive @invalidExample(arg: String @invalidExample) on ARGUMENT_DEFINITION

验证

  1. 指令定义不得包含使用直接引用自身的指令。
  2. 指令定义不得包含使用间接引用自身的指令,即通过引用传递包含对此指令引用的类型或指令。
  3. 指令的名称不得以字符 "__"(两个下划线)开头。
  4. 对于指令的每个参数
    1. 该参数不得具有以字符 "__"(两个下划线)开头的名称。
    2. 该参数必须接受一个类型,其中 IsInputType(argumentType) 返回 true

3.13.1@skip

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

@skip 指令可以为字段、片段扩展和内联片段提供,并允许在执行期间根据 if 参数的描述进行条件排除。

在此示例中,只有当变量 $someTest 的值为 false 时,才会查询 experimentalField

Example № 83query 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 № 84query myQuery($someTest: Boolean) {
  experimentalField @include(if: $someTest)
}
@skip@include 指令之间没有优先顺序。如果同一个字段或片段上同时提供了 @skip@include 指令,则必须仅当 @skip 条件为假 @include 条件为真时才查询。反之,如果 @skip 条件为真 @include 条件为假,则字段或片段不得被查询。

3.13.3@deprecated

directive @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE

@deprecated 指令在类型系统定义语言中使用,以指示 GraphQL 服务模式中已弃用的部分,例如类型上已弃用的字段或已弃用的枚举值。

弃用说明包含弃用原因,该原因使用 Markdown 语法格式化(如 CommonMark 所指定)。

在此类型定义示例中,oldField 已被弃用,推荐使用 newField

Example № 85type ExampleType {
  newField: String
  oldField: String @deprecated(reason: "Use `newField`.")
}

4内省

GraphQL 服务器支持对其模式进行内省。此模式本身使用 GraphQL 查询,从而为工具构建创建了一个强大的平台。

以一个简单的应用程序的查询示例为例。在这种情况下,有一个 User 类型,包含三个字段:id、name 和 birthday。

例如,给定一个具有以下类型定义的服务器

Example № 86type User {
  id: String
  name: String
  birthday: Date
}

查询

Example № 87{
  __type(name: "User") {
    name
    fields {
      name
      type {
        name
      }
    }
  }
}

将返回

Example № 88{
  "__type": {
    "name": "User",
    "fields": [
      {
        "name": "id",
        "type": { "name": "String" }
      },
      {
        "name": "name",
        "type": { "name": "String" }
      },
      {
        "name": "birthday",
        "type": { "name": "Date" }
      },
    ]
  }
}

4.1保留名称

GraphQL 内省系统所需的类型和字段,如果与用户定义的类型和字段在同一上下文中使用,则以双下划线 "__" 为前缀。 这是为了避免与用户定义的 GraphQL 类型发生命名冲突。 相反,GraphQL 类型系统作者不得定义任何以两个前导下划线开头的类型、字段、参数或任何其他类型系统工件。

4.2文档

内省系统中的所有类型都提供一个 description 字段,类型为 String,以便类型设计者可以发布文档以及功能。 GraphQL 服务器可以使用 Markdown 语法(如 CommonMark 所指定)返回 description 字段。 因此,建议任何显示 description 的工具都使用兼容 CommonMark 的 Markdown 渲染器。

4.3弃用

为了支持向后兼容性的管理,GraphQL 字段和枚举值可以指示它们是否已弃用 (isDeprecated: Boolean) 以及弃用原因的描述 (deprecationReason: String)。

使用 GraphQL 内省构建的工具应通过信息隐藏或面向开发人员的警告来劝阻已弃用的使用,从而尊重弃用。

4.4类型名称内省

当针对任何 Object、Interface 或 Union 进行查询时,GraphQL 支持在查询中的任何点通过元字段 __typename: String! 进行类型名称内省。 它返回当前正在查询的对象类型的名称。

这最常用于针对 Interface 或 Union 类型进行查询,以识别已返回的可能类型的实际类型。

此字段是隐式的,不会出现在任何已定义类型中的字段列表中。

4.5模式内省

模式内省系统可以从元字段 __schema__type 访问,这些元字段可以从查询操作根类型访问。

__schema: __Schema!
__type(name: String!): __Type

这些字段是隐式的,不会出现在查询操作的根类型的字段列表中。

GraphQL 模式内省系统的模式

type __Schema {
  types: [__Type!]!
  queryType: __Type!
  mutationType: __Type
  subscriptionType: __Type
  directives: [__Directive!]!
}

type __Type {
  kind: __TypeKind!
  name: String
  description: String

  # OBJECT and INTERFACE only
  fields(includeDeprecated: Boolean = false): [__Field!]

  # OBJECT only
  interfaces: [__Type!]

  # INTERFACE and UNION only
  possibleTypes: [__Type!]

  # ENUM only
  enumValues(includeDeprecated: Boolean = false): [__EnumValue!]

  # INPUT_OBJECT only
  inputFields: [__InputValue!]

  # NON_NULL and LIST only
  ofType: __Type
}

type __Field {
  name: String!
  description: String
  args: [__InputValue!]!
  type: __Type!
  isDeprecated: Boolean!
  deprecationReason: String
}

type __InputValue {
  name: String!
  description: String
  type: __Type!
  defaultValue: String
}

type __EnumValue {
  name: String!
  description: String
  isDeprecated: Boolean!
  deprecationReason: String
}

enum __TypeKind {
  SCALAR
  OBJECT
  INTERFACE
  UNION
  ENUM
  INPUT_OBJECT
  LIST
  NON_NULL
}

type __Directive {
  name: String!
  description: String
  locations: [__DirectiveLocation!]!
  args: [__InputValue!]!
}

enum __DirectiveLocation {
  QUERY
  MUTATION
  SUBSCRIPTION
  FIELD
  FRAGMENT_DEFINITION
  FRAGMENT_SPREAD
  INLINE_FRAGMENT
  SCHEMA
  SCALAR
  OBJECT
  FIELD_DEFINITION
  ARGUMENT_DEFINITION
  INTERFACE
  UNION
  ENUM
  ENUM_VALUE
  INPUT_OBJECT
  INPUT_FIELD_DEFINITION
}

4.5.1__Type 类型

__Type 是类型内省系统的核心。它表示系统中的标量、接口、对象类型、联合、枚举。

__Type 还表示类型修饰符,用于修改它引用的类型 (ofType: __Type)。 这就是我们表示列表、非空类型及其组合的方式。

4.5.2类型种类

有几种不同的类型种类。在每种种类中,实际上都有不同的有效字段。这些种类在 __TypeKind 枚举中列出。

4.5.2.1标量

表示标量类型,例如 Int、String 和 Boolean。标量不能有字段。

GraphQL 类型设计者应在任何标量的 description 字段中描述数据格式和标量强制规则。

字段

  • kind 必须返回 __TypeKind.SCALAR
  • name 必须返回一个 String。
  • description 可以返回一个 String 或 null
  • 所有其他字段必须返回 null
4.5.2.2对象

对象类型表示字段集合的具体实例。内省类型(例如 __Type__Field 等)是对象的示例。

字段

  • kind 必须返回 __TypeKind.OBJECT
  • name 必须返回一个 String。
  • description 可以返回一个 String 或 null
  • fields:此类型上可查询的字段集合。
    • 接受参数 includeDeprecated,默认为 false。如果为 true,则也会返回已弃用的字段。
  • interfaces:对象实现的接口集合。
  • 所有其他字段必须返回 null
4.5.2.3联合

联合是一种抽象类型,其中未声明公共字段。联合的可能类型在 possibleTypes 中显式列出。类型可以成为联合的一部分,而无需修改该类型。

字段

  • kind 必须返回 __TypeKind.UNION
  • name 必须返回一个 String。
  • description 可以返回一个 String 或 null
  • possibleTypes 返回可以在此联合中表示的类型列表。它们必须是对象类型。
  • 所有其他字段必须返回 null
4.5.2.4接口

接口是一种抽象类型,其中声明了公共字段。任何实现接口的类型都必须定义所有字段,名称和类型完全匹配。此接口的实现都在 possibleTypes 中显式列出。

字段

  • kind 必须返回 __TypeKind.INTERFACE
  • name 必须返回一个 String。
  • description 可以返回一个 String 或 null
  • fields:此接口要求的字段集合。
    • 接受参数 includeDeprecated,默认为 false。如果为 true,则也会返回已弃用的字段。
  • possibleTypes 返回实现此接口的类型列表。它们必须是对象类型。
  • 所有其他字段必须返回 null
4.5.2.5枚举

枚举是特殊的标量,只能有一组定义的值。

字段

  • kind 必须返回 __TypeKind.ENUM
  • name 必须返回一个 String。
  • description 可以返回一个 String 或 null
  • enumValuesEnumValue 的列表。必须至少有一个,并且它们必须具有唯一的名称。
    • 接受参数 includeDeprecated,默认为 false。如果为 true,则也会返回已弃用的枚举值。
  • 所有其他字段必须返回 null
4.5.2.6输入对象

输入对象是复合类型,用作查询的输入,定义为命名输入值的列表。

例如,输入对象 Point 可以定义为

Example № 89input Point {
  x: Int
  y: Int
}

字段

  • kind 必须返回 __TypeKind.INPUT_OBJECT
  • name 必须返回一个 String。
  • description 可以返回一个 String 或 null
  • inputFieldsInputValue 的列表。
  • 所有其他字段必须返回 null
4.5.2.7列表

列表表示 GraphQL 中的值序列。List 类型是一种类型修饰符:它将另一个类型实例包装在 ofType 字段中,该字段定义列表中每个项的类型。

字段

  • kind 必须返回 __TypeKind.LIST
  • ofType:任何类型。
  • 所有其他字段必须返回 null
4.5.2.8非空

GraphQL 类型是可为空的。值 null 是字段类型的有效响应。

非空类型是一种类型修饰符:它将另一个类型实例包装在 ofType 字段中。非空类型不允许将 null 作为响应,并指示参数和输入对象字段的必需输入。

  • kind 必须返回 __TypeKind.NON_NULL
  • ofType:除 Non‐null 之外的任何类型。
  • 所有其他字段必须返回 null

4.5.3__Field 类型

__Field 类型表示 Object 或 Interface 类型中的每个字段。

字段

  • name 必须返回一个 String
  • description 可以返回一个 String 或 null
  • args 返回一个 __InputValue 列表,表示此字段接受的参数。
  • type 必须返回一个 __Type,表示此字段返回的值的类型。
  • 如果此字段不应再使用,则 isDeprecated 返回 true,否则返回 false
  • deprecationReason 可选地提供此字段被弃用的原因。

4.5.4__InputValue 类型

__InputValue 类型表示字段和指令参数以及输入对象的 inputFields

字段

  • name 必须返回一个 String
  • description 可以返回一个 String 或 null
  • type 必须返回一个 __Type,表示此输入值期望的类型。
  • defaultValue 可以返回运行时未提供值的情况下,此输入值使用的默认值的 String 编码(使用 GraphQL 语言)。如果此输入值没有默认值,则返回 null

4.5.5__EnumValue 类型

__EnumValue 类型表示枚举的可能值之一。

字段

  • name 必须返回一个 String
  • description 可以返回一个 String 或 null
  • 如果此字段不应再使用,则 isDeprecated 返回 true,否则返回 false
  • deprecationReason 可选地提供此字段被弃用的原因。

4.5.6__Directive 类型

__Directive 类型表示服务器支持的 Directive。

字段

  • name 必须返回一个 String
  • description 可以返回一个 String 或 null
  • locations 返回一个 __DirectiveLocation 列表,表示此指令可以放置的有效位置。
  • args 返回一个 __InputValue 列表,表示此指令接受的参数。

5验证

GraphQL 不仅验证请求在语法上是否正确,还确保在给定 GraphQL 模式的上下文中它是明确且无错误的。

无效的请求在技术上仍然是可执行的,并且将始终产生由 Execution 部分中的算法定义的稳定结果,但是相对于包含验证错误的请求,该结果可能是模棱两可、令人惊讶或意外的,因此执行应仅针对有效的请求发生。

通常,验证在执行之前立即在请求的上下文中执行,但是,如果已知完全相同的请求以前已经过验证,则 GraphQL 服务可以执行请求而无需显式验证它。 例如:请求可以在开发期间进行验证(前提是以后不会更改),或者服务可以验证一次请求并记住结果,以避免将来再次验证相同的请求。 任何客户端或开发时工具都应报告验证错误,并且不允许制定或执行已知在给定时间点无效的请求。

类型系统演变

随着 GraphQL 类型系统模式通过添加新类型和新字段随时间演变,先前有效的请求可能后来变得无效。任何可能导致先前有效的请求变为无效的更改都被认为是破坏性更改。 鼓励 GraphQL 服务和模式维护者避免破坏性更改,但是为了对这些破坏性更改更具弹性,复杂的 GraphQL 系统可能仍然允许执行在某些时候已知没有任何验证错误并且此后没有更改的请求。

示例

对于此模式的本节,我们将假设以下类型系统,以便演示示例

Example № 90type Query {
  dog: 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!
}

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

5.1文档

5.1.1可执行定义

形式化规范

解释性文本

GraphQL 执行将仅考虑可执行定义 Operation 和 Fragment。 类型系统定义和扩展是不可执行的,并且在执行期间不被考虑。

为避免歧义,包含 TypeSystemDefinition 的文档对于执行无效。

不打算直接执行的 GraphQL 文档可能包含 TypeSystemDefinition

例如,以下文档对于执行无效,因为原始执行模式可能不知道提供的类型扩展

Counter Example № 91query getDogName {
  dog {
    name
    color
  }
}

extend type Dog {
  color: String
}

5.2操作

5.2.1命名操作定义

5.2.1.1操作名称唯一性

形式化规范

  • 对于文档中的每个操作定义 operation
  • operationNameoperation 的名称。
  • 如果 operationName 存在
    • operations 为文档中所有名为 operationName 的操作定义。
    • operations 必须是包含一个元素的集合。

解释性文本

每个命名操作定义在其名称引用时,在文档中必须是唯一的。

例如,以下文档是有效的

Example № 92query getDogName {
  dog {
    name
  }
}

query getOwnerName {
  dog {
    owner {
      name
    }
  }
}

而此文档是无效的

Counter Example № 93query getName {
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}

即使每个操作的类型都不同,它也是无效的

Counter Example № 94query dogOperation {
  dog {
    name
  }
}

mutation dogOperation {
  mutateDog {
    id
  }
}

5.2.2匿名操作定义

5.2.2.1单独的匿名操作

形式化规范

  • operations 为文档中的所有操作定义。
  • anonymous 为文档中的所有匿名操作定义。
  • 如果 operations 是包含超过 1 个元素的集合
    • anonymous 必须为空。

解释性文本

当文档中仅存在一个查询操作时,GraphQL 允许使用简写形式来定义查询操作。

例如,以下文档是有效的

Example № 95{
  dog {
    name
  }
}

而此文档是无效的

Counter Example № 96{
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}

5.2.3订阅操作定义

5.2.3.1单一根字段

形式化规范

  • 对于文档中的每个订阅操作定义 subscription
  • subscriptionTypeschema 中的根 Subscription 类型。
  • selectionSetsubscription 上的顶层选择集。
  • variableValues 为空集。
  • groupedFieldSetCollectFields(subscriptionType, selectionSet, variableValues) 的结果。
  • groupedFieldSet 必须恰好包含一个条目。

解释性文本

订阅操作必须恰好有一个根字段。

有效示例

Example № 97subscription sub {
  newMessage {
    body
    sender
  }
}
Example № 98subscription sub {
  ...newMessageFields
}

fragment newMessageFields on Subscription {
  newMessage {
    body
    sender
  }
}

无效示例

Counter Example № 99subscription sub {
  newMessage {
    body
    sender
  }
  disallowedSecondRootField
}
Counter Example № 100subscription sub {
  ...multipleSubscriptions
}

fragment multipleSubscriptions on Subscription {
  newMessage {
    body
    sender
  }
  disallowedSecondRootField
}

内省字段也会被计算在内。以下示例也是无效的

Counter Example № 101subscription sub {
  newMessage {
    body
    sender
  }
  __typename
}
虽然每个订阅必须恰好有一个根字段,但文档可以包含任意数量的操作,每个操作可以包含不同的根字段。执行时,包含多个订阅操作的文档必须提供操作名称,如 GetOperation() 中所述。

5.3字段

5.3.1对象、接口和联合类型上的字段选择

形式化规范

  • 对于文档中的每个 selection
  • fieldNameselection 的目标字段
  • fieldName 必须在作用域内的类型上定义

解释性文本

字段选择的目标字段必须在选择集的作用域类型上定义。别名名称没有限制。

例如,以下片段将无法通过验证

Counter Example № 102fragment fieldNotDefined on Dog {
  meowVolume
}

fragment aliasedLyingFieldTargetNotDefined on Dog {
  barkVolume: kawVolume
}

对于接口,只能对字段进行直接字段选择。具体实现者的字段与给定接口类型选择集的有效性无关。

例如,以下是有效的

Example № 103fragment interfaceFieldSelection on Pet {
  name
}

以下是无效的

Counter Example № 104fragment definedOnImplementorsButNotInterface on Pet {
  nickname
}

由于联合不定义字段,因此除非使用元字段 __typename,否则不能直接从联合类型选择集中选择字段。来自联合类型选择集的字段只能通过片段间接查询。

例如,以下是有效的

Example № 105fragment inDirectFieldSelectionOnUnion on CatOrDog {
  __typename
  ... on Pet {
    name
  }
  ... on Dog {
    barkVolume
  }
}

但以下是无效的

Counter Example № 106fragment directFieldSelectionOnUnion on CatOrDog {
  name
  barkVolume
}

5.3.2字段选择合并

形式化规范

  • set 为 GraphQL 文档中定义的任何选择集。
  • FieldsInSetCanMerge(set) 必须为真。
FieldsInSetCanMerge(set)
  1. fieldsForNameset 中具有给定响应名称的选择集合,包括访问片段和内联片段。
  2. 给定 fieldsForName 中每对成员 fieldAfieldB
    1. SameResponseShape(fieldA, fieldB) 必须为真。
    2. 如果 fieldAfieldB 的父类型相等,或者其中任何一个不是对象类型
      1. fieldAfieldB 必须具有相同的字段名称。
      2. fieldAfieldB 必须具有相同的参数集。
      3. mergedSet 为添加 fieldA 的选择集和 fieldB 的选择集的结果。
      4. FieldsInSetCanMerge(mergedSet) 必须为真。
SameResponseShape(fieldA, fieldB)
  1. typeAfieldA 的返回类型。
  2. typeBfieldB 的返回类型。
  3. 如果 typeAtypeB 为 Non‐Null。
    1. 如果 typeAtypeB 可为空,则返回 false。
    2. typeAtypeA 的可空类型
    3. typeBtypeB 的可空类型
  4. 如果 typeAtypeB 为 List。
    1. 如果 typeAtypeB 不是 List,则返回 false。
    2. typeAtypeA 的项类型
    3. typeBtypeB 的项类型
    4. 从步骤 3 重复。
  5. 如果 typeAtypeB 为 Scalar 或 Enum。
    1. 如果 typeAtypeB 是相同的类型,则返回 true,否则返回 false。
  6. 如果 typeAtypeB 不是复合类型,则返回 false。
  7. mergedSet 为添加 fieldA 的选择集和 fieldB 的选择集的结果。
  8. fieldsForNamemergedSet 中具有给定响应名称的选择集合,包括访问片段和内联片段。
  9. 给定 fieldsForName 中每对成员 subfieldAsubfieldB
    1. 如果 SameResponseShape(subfieldA, subfieldB) 为 false,则返回 false。
  10. 返回 true。

解释性文本

如果在执行期间遇到具有相同响应名称的多个字段选择,则要执行的字段和参数以及结果值应是明确的。 因此,对于可能对同一对象都遇到的任何两个字段选择,只有当它们等效时才有效。

在执行期间,具有相同响应名称的字段的同步执行是通过 MergeSelectionSets()CollectFields() 完成的。

对于简单的手写 GraphQL,此规则显然是明显的开发人员错误,但是嵌套片段会使手动检测变得困难。

以下选择正确合并

Example № 107fragment mergeIdenticalFields on Dog {
  name
  name
}

fragment mergeIdenticalAliasesAndFields on Dog {
  otherName: name
  otherName: name
}

以下选择无法合并

Counter Example № 108fragment conflictingBecauseAlias on Dog {
  name: nickname
  name
}

如果参数相同,则相同的参数也会合并。值和变量都可以正确合并。

例如,以下选择正确合并

Example № 109fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
  doesKnowCommand(dogCommand: SIT)
  doesKnowCommand(dogCommand: SIT)
}

fragment mergeIdenticalFieldsWithIdenticalValues on Dog {
  doesKnowCommand(dogCommand: $dogCommand)
  doesKnowCommand(dogCommand: $dogCommand)
}

以下选择无法正确合并

Counter Example № 110fragment 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 № 111fragment 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 可能是 StringInt

Counter Example № 112fragment conflictingDifferingResponses on Pet {
  ... on Dog {
    someValue: nickname
  }
  ... on Cat {
    someValue: meowVolume
  }
}

5.3.3叶子字段选择

形式化规范

  • 对于文档中的每个 selection
  • selectionTypeselection 的结果类型
  • 如果 selectionType 是标量或枚举
    • 该选择的子选择集必须为空
  • 如果 selectionType 是接口、联合或对象
    • 该选择的子选择集不得为空

解释性文本

永远不允许对标量或枚举进行字段选择,因为它们是任何 GraphQL 查询的叶子节点。

以下是有效的。

Example № 113fragment scalarSelection on Dog {
  barkVolume
}

以下是无效的。

Counter Example № 114fragment scalarSelectionsNotAllowedOnInt on Dog {
  barkVolume {
    sinceWhen
  }
}

相反,GraphQL 查询的叶子字段选择必须是标量或枚举类型。 不允许对没有子字段的对象、接口和联合进行叶子选择。

让我们假设对模式的查询根类型进行以下添加

Example № 115extend type Query {
  human: Human
  pet: Pet
  catOrDog: CatOrDog
}

以下示例是无效的

Counter Example № 116query directQueryOnObjectWithoutSubFields {
  human
}

query directQueryOnInterfaceWithoutSubFields {
  pet
}

query directQueryOnUnionWithoutSubFields {
  catOrDog
}

5.4参数

参数提供给字段和指令。 以下验证规则在两种情况下均适用。

5.4.1参数名称

形式化规范

  • 对于文档中的每个 argument
  • argumentNameargument 的名称。
  • argumentDefinition 为父字段或名为 argumentName 的定义提供的参数定义。
  • argumentDefinition 必须存在。

解释性文本

提供给字段或指令的每个参数都必须在字段或指令的可能参数集中定义。

例如,以下是有效的

Example № 117fragment argOnRequiredArg on Dog {
  doesKnowCommand(dogCommand: SIT)
}

fragment argOnOptional on Dog {
  isHousetrained(atOtherHomes: true) @include(if: true)
}

以下是无效的,因为 command 未在 DogCommand 上定义。

Counter Example № 118fragment invalidArgName on Dog {
  doesKnowCommand(command: CLEAN_UP_HOUSE)
}

这也是无效的,因为 unless 未在 @include 上定义。

Counter Example № 119fragment invalidArgName on Dog {
  isHousetrained(atOtherHomes: true) @include(unless: false)
}

为了探索更复杂的参数示例,让我们将以下内容添加到我们的类型系统中

Example № 120type Arguments {
  multipleReqs(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 № 121fragment multipleArgs on Arguments {
  multipleReqs(x: 1, y: 2)
}

fragment multipleArgsReverseOrder on Arguments {
  multipleReqs(y: 1, x: 2)
}

5.4.2参数唯一性

字段和指令将参数视为参数名称到值的映射。参数集中具有相同名称的多个参数是模棱两可且无效的。

形式化规范

  • 对于文档中的每个 argument
  • argumentNameargument 的名称。
  • arguments 为参数集中所有名为 argumentName 的参数,其中参数集包含 argument
  • arguments 必须是仅包含 argument 的集合。
5.4.2.1必需参数
  • 对于文档中的每个字段或指令。
  • arguments 为字段或指令提供的参数。
  • argumentDefinitions 为该字段或指令的参数定义集。
  • 对于 argumentDefinitions 中的每个 argumentDefinition
    • typeargumentDefinition 的预期类型。
    • defaultValueargumentDefinition 的默认值。
    • 如果 type 为 Non‐Null 且 defaultValue 不存在
      • argumentNameargumentDefinition 的名称。
      • argumentarguments 中名为 argumentName 的参数
      • argument 必须存在。
      • valueargument 的值。
      • value 不得为 null 字面量。

解释性文本

参数可以是必需的。如果参数类型为非空且没有默认值,则该参数是必需的。 否则,该参数是可选的。

例如,以下是有效的

Example № 122fragment goodBooleanArg on Arguments {
  booleanArgField(booleanArg: true)
}

fragment goodNonNullArg on Arguments {
  nonNullBooleanArgField(nonNullBooleanArg: true)
}

可以从具有可空参数的字段中省略参数。

因此,以下查询是有效的

Example № 123fragment goodBooleanArgDefault on Arguments {
  booleanArgField
}

但这在必需参数上是无效的。

Counter Example № 124fragment missingRequiredArg on Arguments {
  nonNullBooleanArgField
}

提供显式值 null 也是无效的,因为必需参数始终具有非空类型。

Counter Example № 125fragment missingRequiredArg on Arguments {
  nonNullBooleanArgField(nonNullBooleanArg: null)
}

5.5片段

5.5.1片段声明

5.5.1.1片段名称唯一性

形式化规范

  • 对于文档中的每个片段定义 fragment
  • fragmentNamefragment 的名称。
  • fragments 为文档中所有名为 fragmentName 的片段定义。
  • fragments 必须是包含一个元素的集合。

解释性文本

片段定义在片段展开中按名称引用。为避免歧义,每个片段的名称在文档中必须是唯一的。

内联片段不被视为片段定义,并且不受此验证规则的影响。

例如,以下文档是有效的

Example № 126{
  dog {
    ...fragmentOne
    ...fragmentTwo
  }
}

fragment fragmentOne on Dog {
  name
}

fragment fragmentTwo on Dog {
  owner {
    name
  }
}

而此文档是无效的

Counter Example № 127{
  dog {
    ...fragmentOne
  }
}

fragment fragmentOne on Dog {
  name
}

fragment fragmentOne on Dog {
  owner {
    name
  }
}
5.5.1.2片段展开类型存在性

形式化规范

  • 对于文档中的每个命名展开 namedSpread
  • fragmentnamedSpread 的目标
  • fragment 的目标类型必须在模式中定义

解释性文本

片段必须在模式中存在的类型上指定。这适用于命名片段和内联片段。如果它们未在模式中定义,则查询将无法验证。

例如,以下片段是有效的

Example № 128fragment correctType on Dog {
  name
}

fragment inlineFragment on Dog {
  ... on Dog {
    name
  }
}

fragment inlineFragment2 on Dog {
  ... @include(if: true) {
    name
  }
}

以下片段无法验证

Counter Example № 129fragment notOnExistingType on NotInSchema {
  name
}

fragment inlineNotExistingType on Dog {
  ... on NotInSchema {
    name
  }
}
5.5.1.3关于复合类型的片段

形式化规范

  • 对于文档中定义的每个片段
  • 片段的目标类型必须是 UNIONINTERFACEOBJECT 类型。

解释性文本

片段只能在联合类型、接口类型和对象类型上声明。它们在标量类型上无效。它们只能应用于非叶子字段。此规则适用于内联片段和命名片段。

以下片段声明是有效的

Example № 130fragment fragOnObject on Dog {
  name
}

fragment fragOnInterface on Pet {
  name
}

fragment fragOnUnion on CatOrDog {
  ... on Dog {
    name
  }
}

以下片段声明是无效的

Counter Example № 131fragment fragOnScalar on Int {
  something
}

fragment inlineFragOnScalar on Dog {
  ... on Boolean {
    somethingElse
  }
}
5.5.1.4片段必须被使用

形式化规范

  • 对于文档中定义的每个片段
  • 片段必须是文档中至少一个扩展的目标

解释性文本

定义的片段必须在文档中使用。

例如,以下是一个无效的文档

Counter Example № 132fragment nameFragment on Dog { # unused
  name
}

{
  dog {
    name
  }
}

5.5.2片段扩展

字段选择也通过将片段相互扩展来确定。目标片段的选择集与引用目标片段的级别的选择集进行并集运算。

5.5.2.1片段扩展目标已定义

形式化规范

  • 对于文档中的每个 namedSpread
  • fragmentnamedSpread 的目标
  • 片段必须在文档中定义

解释性文本

命名片段扩展必须引用文档中定义的片段。如果扩展的目标未定义,则会发生验证错误。

Counter Example № 133{
  dog {
    ...undefinedFragment
  }
}
5.5.2.2片段扩展不得形成循环

形式化规范

  • 对于文档中的每个 fragmentDefinition
  • visited 为空集。
  • DetectCycles(fragmentDefinition, visited)

DetectCycles(fragmentDefinition, visited) :

  • spreadsfragmentDefinition 的所有片段扩展后代
  • 对于 spreads 中的每个 spread
    • visited 不得包含 spread
    • nextVisited 为包含 spreadvisited 成员的集合
    • nextFragmentDefinitionspread 的目标
    • DetectCycles(nextFragmentDefinition, nextVisited)

解释性文本

片段扩展图不得形成任何循环,包括自身扩展。 否则,操作可能会无限扩展或在底层数据中的循环上无限执行。

这会使导致无限扩展的片段无效

Counter Example № 134{
  dog {
    ...nameFragment
  }
}

fragment nameFragment on Dog {
  name
  ...barkVolumeFragment
}

fragment barkVolumeFragment on Dog {
  barkVolume
  ...nameFragment
}

如果以上片段被内联,这将导致无限大

Example № 135{
  dog {
    name
    barkVolume
    name
    barkVolume
    name
    barkVolume
    name
    # forever...
  }
}

这也会使在针对循环数据执行时导致无限递归的片段无效

Counter Example № 136{
  dog {
    ...dogFragment
  }
}

fragment dogFragment on Dog {
  name
  owner {
    ...ownerFragment
  }
}

fragment ownerFragment on Dog {
  name
  pets {
    ...dogFragment
  }
}
5.5.2.3片段扩展是可能的

形式化规范

  • 对于文档中定义的每个扩展(命名或内联)。
  • fragmentspread 的目标
  • fragmentTypefragment 的类型条件
  • parentType 为包含 spread 的选择集的类型
  • applicableTypesGetPossibleTypes(fragmentType)GetPossibleTypes(parentType) 的交集
  • applicableTypes 不得为空。
GetPossibleTypes(type)
  1. 如果 type 是对象类型,则返回包含 type 的集合
  2. 如果 type 是接口类型,则返回实现 type 的类型集合
  3. 如果 type 是联合类型,则返回 type 的可能类型集合

解释性文本

片段在类型上声明,并且仅当运行时对象类型与类型条件匹配时才适用。 它们也在父类型的上下文中扩展。 只有当片段的类型条件可以在父类型中应用时,片段扩展才有效。

5.5.2.3.1对象作用域中的对象扩展

在对象类型的作用域中,唯一有效的对象类型片段扩展是应用于同一作用域类型的片段。

例如

Example № 137fragment dogFragment on Dog {
  ... on Dog {
    barkVolume
  }
}

以下是无效的

Counter Example № 138fragment catInDogFragmentInvalid on Dog {
  ... on Cat {
    meowVolume
  }
}
5.5.2.3.2对象作用域中的抽象扩展

在对象类型的作用域中,如果对象类型实现了接口或属于联合类型的成员,则可以使用联合类型或接口类型扩展。

例如

Example № 139fragment petNameFragment on Pet {
  name
}

fragment interfaceWithinObjectFragment on Dog {
  ...petNameFragment
}

是有效的,因为 Dog 实现了 Pet 接口。

同样地

Example № 140fragment catOrDogNameFragment on CatOrDog {
  ... on Cat {
    meowVolume
  }
}

fragment unionWithObjectFragment on Dog {
  ...catOrDogNameFragment
}

是有效的,因为 DogCatOrDog 联合类型的成员。 值得注意的是,如果检查 CatOrDogNameFragment 的内容,您可能会注意到永远不会返回有效的结果。 但是,我们不将其指定为无效,因为我们只考虑片段声明,而不是其主体。

5.5.2.3.3抽象作用域中的对象扩展

联合类型或接口类型扩展可以在对象类型片段的上下文中使用,但前提是对象类型是该接口或联合类型的可能类型之一。

例如,以下片段是有效的

Example № 141fragment petFragment on Pet {
  name
  ... on Dog {
    barkVolume
  }
}

fragment catOrDogFragment on CatOrDog {
  ... on Cat {
    meowVolume
  }
}

petFragment 是有效的,因为 Dog 实现了 Pet 接口。 catOrDogFragment 是有效的,因为 CatCatOrDog 联合类型的成员。

相比之下,以下片段是无效的

Counter Example № 142fragment sentientFragment on Sentient {
  ... on Dog {
    barkVolume
  }
}

fragment humanOrAlienFragment on HumanOrAlien {
  ... on Cat {
    meowVolume
  }
}

Dog 没有实现 Sentient 接口,因此 sentientFragment 永远无法返回有意义的结果。 因此,该片段无效。 同样,Cat 不是 HumanOrAlien 联合类型的成员,它也永远无法返回有意义的结果,使其无效。

5.5.2.3.4抽象作用域中的抽象扩展

联合类型或接口类型片段可以在彼此之间使用。 只要存在至少一个对象类型,该对象类型存在于作用域和扩展的可能类型的交集中,则该扩展被认为是有效的。

因此,例如

Example № 143fragment unionWithInterface on Pet {
  ...dogOrHumanFragment
}

fragment dogOrHumanFragment on DogOrHuman {
  ... on Dog {
    barkVolume
  }
}

被认为是有效的,因为 Dog 实现了 Pet 接口并且是 DogOrHuman 联合类型的成员。

然而

Counter Example № 144fragment nonIntersectingInterfaces on Pet {
  ...sentientFragment
}

fragment sentientFragment on Sentient {
  name
}

是无效的,因为不存在同时实现 PetSentient 接口的类型。

5.6

5.6.1正确类型的值

格式规范

  • 对于文档中的每个输入值 value
    • type 为在 value 所在位置期望的类型。
    • value 必须可强制转换为 type

解释性文本

字面量值必须与它们所在位置期望的类型兼容,根据类型系统章节中定义的强制规则。

位置中期望的类型包括为参数提供值定义的类型、为输入对象字段提供值定义的类型,以及为变量定义提供默认值定义的类型。

以下示例是值字面量的有效用法

Example № 145fragment 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: ComplexInput = { name: "Fido" }) {
  findDog(complex: $search)
}

不可强制转换的值(例如将字符串转换为整数)是无效的。 以下示例是无效的

Counter Example № 146fragment stringIntoInt on Arguments {
  intArgField(intArg: "123")
}

query badComplexValue {
  findDog(complex: { name: 123 })
}

5.6.2输入对象字段名称

形式化规范

  • 对于文档中的每个输入对象字段 inputField
  • inputFieldNameinputField 的名称。
  • inputFieldDefinition 为父输入对象类型(名为 inputFieldName)提供的输入字段定义。
  • inputFieldDefinition 必须存在。

解释性文本

输入对象值中提供的每个输入字段都必须在输入对象预期类型的可能字段集中定义。

例如,以下示例输入对象是有效的

Example № 147{
  findDog(complex: { name: "Fido" })
}

而以下示例输入对象使用了字段“favoriteCookieFlavor”,该字段未在预期类型上定义

Counter Example № 148{
  findDog(complex: { favoriteCookieFlavor: "Bacon" })
}

5.6.3输入对象字段唯一性

形式化规范

  • 对于文档中的每个输入对象值 inputObject
  • 对于 inputObject 中的每个 inputField
    • nameinputField 的名称。
    • fieldsinputObject 中所有名为 name 的输入对象字段。
    • fields 必须是仅包含 inputField 的集合。

解释性文本

输入对象不得包含多个同名字段,否则会存在歧义,其中包括语法的忽略部分。

例如,以下查询将无法通过验证。

Counter Example № 149{
  field(arg: { field: true, field: false })
}

5.6.4输入对象必填字段

形式化规范

  • 对于文档中的每个输入对象。
    • fields 为该输入对象提供的字段。
    • fieldDefinitions 为该输入对象的输入字段定义集。
  • 对于 fieldDefinitions 中的每个 fieldDefinition
    • typefieldDefinition 的预期类型。
    • defaultValuefieldDefinition 的默认值。
    • 如果 type 为 Non‐Null 且 defaultValue 不存在
      • fieldNamefieldDefinition 的名称。
      • fieldfields 中名为 fieldName 的输入字段
      • field 必须存在。
      • valuefield 的值。
      • value 不得为 null 字面量。

解释性文本

输入对象字段可能是必需的。 就像字段可能具有必需的参数一样,输入对象也可能具有必需的字段。 如果输入字段具有非空类型且没有默认值,则它是必需的。 否则,输入对象字段是可选的。

5.7指令

5.7.1指令已定义

形式化规范

  • 对于文档中的每个 directive
  • directiveNamedirective 的名称。
  • directiveDefinition 为名为 directiveName 的指令。
  • directiveDefinition 必须存在。

解释性文本

GraphQL 服务器定义它们支持哪些指令。 对于指令的每次使用,该指令都必须在该服务器上可用。

5.7.2指令位于有效位置

形式化规范

  • 对于文档中的每个 directive
  • directiveNamedirective 的名称。
  • directiveDefinition 为名为 directiveName 的指令。
  • locationsdirectiveDefinition 的有效位置。
  • adjacent 为指令影响的 AST 节点。
  • adjacent 必须由 locations 中的一个项表示。

解释性文本

GraphQL 服务器定义它们支持哪些指令以及在何处支持它们。 对于指令的每次使用,该指令必须在服务器已声明支持的位置使用。

例如,以下查询将无法通过验证,因为 @skip 未提供 QUERY 作为有效位置。

Counter Example № 150query @skip(if: $foo) {
  field
}

5.7.3每个位置的指令都是唯一的

形式化规范

  • 对于文档中指令可以应用的每个 location
    • directives 为应用于 location 的指令集。
    • 对于 directives 中的每个 directive
      • directiveNamedirective 的名称。
      • namedDirectivesdirectives 中所有名为 directiveName 的指令集。
      • namedDirectives 必须是只包含一个元素的集合。

解释性文本

指令用于描述它们应用的定义上的一些元数据或行为更改。 当使用多个同名指令时,预期的元数据或行为变得不明确,因此每个位置仅允许使用一个指令。

例如,以下查询将无法通过验证,因为 @skip 已对同一字段使用了两次

Counter Example № 151query ($foo: Boolean = true, $bar: Boolean = false) {
  field @skip(if: $foo) @skip(if: $bar)
}

但是,以下示例是有效的,因为 @skip 每个位置仅使用一次,尽管在查询中和在同一命名字段上使用了两次

Example № 152query ($foo: Boolean = true, $bar: Boolean = false) {
  field @skip(if: $foo) {
    subfieldA
  }
  field @skip(if: $bar) {
    subfieldB
  }
}

5.8变量

5.8.1变量唯一性

形式化规范

  • 对于文档中的每个 operation
    • 对于 operation 上定义的每个 variable
      • variableNamevariable 的名称
      • variablesoperation 上所有名为 variableName 的变量集
      • variables 必须是只包含一个元素的集合

解释性文本

如果任何操作定义了多个同名变量,则它是模糊且无效的。 即使重复变量的类型相同,它也是无效的。

Counter Example № 153query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

多个操作定义同名变量是有效的。 如果两个操作引用相同的片段,则实际上可能是必要的

Example № 154query A($atOtherHomes: Boolean) {
  ...HouseTrainedFragment
}

query B($atOtherHomes: Boolean) {
  ...HouseTrainedFragment
}

fragment HouseTrainedFragment {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

5.8.2变量是输入类型

形式化规范

  • 对于 document 中的每个 operation
  • 对于每个 operation 上的每个 variable
    • variableTypevariable 的类型
    • IsInputType(variableType) 必须为 true

解释性文本

变量只能是输入类型。 对象、联合类型和接口类型不能用作输入。

对于这些示例,请考虑以下类型系统添加项

Example № 155input ComplexInput { name: String, owner: String }

extend type Query {
  findDog(complex: ComplexInput): Dog
  booleanList(booleanListArg: [Boolean!]): Boolean
}

以下查询是有效的

Example № 156query takesBoolean($atOtherHomes: Boolean) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

query takesComplexInput($complexInput: ComplexInput) {
  findDog(complex: $complexInput) {
    name
  }
}

query TakesListOfBooleanBang($booleans: [Boolean!]) {
  booleanList(booleanListArg: $booleans)
}

以下查询是无效的

Counter Example № 157query 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 № 158query variableIsDefined($atOtherHomes: Boolean) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

是有效的。 $atOtherHomes 由操作定义。

相比之下,以下查询是无效的

Counter Example № 159query variableIsNotDefined {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

$atOtherHomes 未由操作定义。

片段使此规则变得复杂。 操作传递包含的任何片段都可以访问该操作定义的变量。 片段可以出现在多个操作中,因此变量用法必须与所有这些操作中的变量定义相对应。

例如,以下是有效的

Example № 160query variableIsDefinedUsedInSingleFragment($atOtherHomes: Boolean) {
  dog {
    ...isHousetrainedFragment
  }
}

fragment isHousetrainedFragment on Dog {
  isHousetrained(atOtherHomes: $atOtherHomes)
}

因为 isHousetrainedFragment 在操作 variableIsDefinedUsedInSingleFragment 的上下文中使用,并且变量由该操作定义。

另一方面,如果片段包含在未定义引用变量的操作中,则查询无效。

Counter Example № 161query variableIsNotDefinedUsedInSingleFragment {
  dog {
    ...isHousetrainedFragment
  }
}

fragment isHousetrainedFragment on Dog {
  isHousetrained(atOtherHomes: $atOtherHomes)
}

这也适用于传递性,因此以下也失败

Counter Example № 162query variableIsNotDefinedUsedInNestedFragment {
  dog {
    ...outerHousetrainedFragment
  }
}

fragment outerHousetrainedFragment on Dog {
  ...isHousetrainedFragment
}

fragment isHousetrainedFragment on Dog {
  isHousetrained(atOtherHomes: $atOtherHomes)
}

变量必须在使用片段的所有操作中定义。

Example № 163query housetrainedQueryOne($atOtherHomes: Boolean) {
  dog {
    ...isHousetrainedFragment
  }
}

query housetrainedQueryTwo($atOtherHomes: Boolean) {
  dog {
    ...isHousetrainedFragment
  }
}

fragment isHousetrainedFragment on Dog {
  isHousetrained(atOtherHomes: $atOtherHomes)
}

但是,以下内容无法通过验证

Counter Example № 164query 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 № 165query variableUnused($atOtherHomes: Boolean) {
  dog {
    isHousetrained
  }
}

因为 $atOtherHomes 未被引用。

这些规则也适用于传递片段扩展

Example № 166query variableUsedInFragment($atOtherHomes: Boolean) {
  dog {
    ...isHousetrainedFragment
  }
}

fragment isHousetrainedFragment on Dog {
  isHousetrained(atOtherHomes: $atOtherHomes)
}

以上是有效的,因为 $atOtherHomesisHousetrainedFragment 中使用,而 isHousetrainedFragmentvariableUsedInFragment 包含。

如果该片段没有对 $atOtherHomes 的引用,则它将无效

Counter Example № 167query variableNotUsedWithinFragment($atOtherHomes: Boolean) {
  dog {
    ...isHousetrainedWithoutVariableFragment
  }
}

fragment isHousetrainedWithoutVariableFragment on Dog {
  isHousetrained
}

文档中的所有操作都必须使用其所有变量。

因此,以下文档无法通过验证。

Counter Example № 168query 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
    • variableNamevariableUsage 的名称。
    • variableDefinitionoperation 中定义的名为 variableNameVariableDefinition
    • IsVariableUsageAllowed(variableDefinition, variableUsage) 必须为 true
IsVariableUsageAllowed(variableDefinition, variableUsage)
  1. variableTypevariableDefinition 的预期类型。
  2. locationTypevariableUsage 所在位置的 ArgumentObjectFieldListValue 条目的预期类型。
  3. 如果 locationType 是非空类型 AND variableType 不是非空类型
    1. 如果 variableDefinition 存在默认值且不是 null 值,则令 hasNonNullVariableDefaultValuetrue
    2. 如果 variableUsage 所在位置的 ArgumentObjectField 存在默认值,则令 hasLocationDefaultValuetrue
    3. 如果 hasNonNullVariableDefaultValue 不为 true AND hasLocationDefaultValue 不为 true,则返回 false
    4. nullableLocationTypelocationType 的解包可空类型。
    5. 返回 AreTypesCompatible(variableType, nullableLocationType)
  4. 返回 AreTypesCompatible(variableType, locationType)
AreTypesCompatible(variableType, locationType)
  1. 如果 locationType 是非空类型
    1. 如果 variableType 不是非空类型,则返回 false
    2. nullableLocationTypelocationType 的解包可空类型。
    3. nullableVariableTypevariableType 的解包可空类型。
    4. 返回 AreTypesCompatible(nullableVariableType, nullableLocationType)
  2. 否则,如果 variableType 是非空类型
    1. nullableVariableTypevariableType 的可空类型。
    2. 返回 AreTypesCompatible(nullableVariableType, locationType)
  3. 否则,如果 locationType 是列表类型
    1. 如果 variableType 不是列表类型,则返回 false
    2. itemLocationTypelocationType 的解包项类型。
    3. itemVariableTypevariableType 的解包项类型。
    4. 返回 AreTypesCompatible(itemVariableType, itemLocationType)
  4. 否则,如果 variableType 是列表类型,则返回 false
  5. 如果 variableTypelocationType 相同,则返回 true,否则返回 false

解释性文本

变量用法必须与传递给它们的参数兼容。

当变量在类型完全不匹配的上下文中使用,或者变量中的可空类型传递给非空参数类型时,会发生验证失败。

类型必须匹配

Counter Example № 169query intCannotGoIntoBoolean($intArg: Int) {
  arguments {
    booleanArgField(booleanArg: $intArg)
  }
}

类型为 Int 的 $intArg 不能用作类型为 Boolean 的 booleanArg 的参数。

列表基数也必须相同。 例如,列表不能传递给单数值。

Counter Example № 170query booleanListCannotGoIntoBoolean($booleanListArg: [Boolean]) {
  arguments {
    booleanArgField(booleanArg: $booleanListArg)
  }
}

空值性也必须受到尊重。 一般来说,可空变量不能传递给非空参数。

Counter Example № 171query booleanArgQuery($booleanArg: Boolean) {
  arguments {
    nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)
  }
}

对于列表类型,关于空值性的相同规则适用于外部类型和内部类型。 可空列表不能传递给非空列表,可空值列表不能传递给非空值列表。 以下是有效的

Example № 172query nonNullListToList($nonNullBooleanList: [Boolean]!) {
  arguments {
    booleanListArgField(booleanListArg: $nonNullBooleanList)
  }
}

但是,可空列表不能传递给非空列表

Counter Example № 173query listToNonNullList($booleanList: [Boolean]) {
  arguments {
    nonNullBooleanListField(nonNullBooleanListArg: $booleanList)
  }
}

这将无法通过验证,因为 [T] 不能传递给 [T]!。 同样,[T] 不能传递给 [T!]

当存在默认值时允许可选变量

变量类型兼容性的一个显着例外是允许将具有可空类型的变量定义提供给非空位置,只要该变量或该位置提供默认值即可。

Example № 174query booleanArgQueryWithDefault($booleanArg: Boolean) {
  arguments {
    optionalNonNullBooleanArgField(optionalBooleanArg: $booleanArg)
  }
}

在上面的示例中,允许在提供默认值的非空参数中使用可选变量。

Example № 175query booleanArgQueryWithDefault($booleanArg: Boolean = true) {
  arguments {
    nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)
  }
}

在上面的示例中,变量提供了默认值,并且可以在非空参数中使用。 为了与本规范的早期版本兼容,显式支持此行为。 GraphQL 创作工具可能希望将其报告为警告,并建议将 Boolean 替换为 Boolean!

null 仍然可以在运行时提供给这样的变量。 如果为非空参数提供 null 值,则必须产生字段错误。

6执行

GraphQL 通过执行从请求生成响应。

执行请求由以下几部分信息组成

鉴于这些信息,ExecuteRequest() 的结果会生成响应,该响应将根据下面的“响应”部分进行格式化。

6.1执行请求

为了执行请求,执行器必须具有已解析的 文档 和要运行的选定操作名称(如果文档定义了多个操作),否则文档应仅包含单个操作。请求的结果由根据以下“执行操作”部分执行此操作的结果确定。

ExecuteRequest(schema, document, operationName, variableValues, initialValue)
  1. operationGetOperation(document, operationName) 的结果。
  2. coercedVariableValuesCoerceVariableValues(schema, operation, variableValues) 的结果。
  3. 如果 operation 是查询操作
    1. 返回 ExecuteQuery(operation, schema, coercedVariableValues, initialValue)
  4. 否则,如果 operation 是变更操作
    1. 返回 ExecuteMutation(operation, schema, coercedVariableValues, initialValue)
  5. 否则,如果 operation 是订阅操作
    1. 返回 Subscribe(operation, schema, coercedVariableValues, initialValue)
GetOperation(document, operationName)
  1. 如果 operationNamenull
    1. 如果 document 正好包含一个操作。
      1. 返回 document 中包含的 Operation。
    2. 否则,产生一个需要 operationName 的查询错误。
  2. 否则
    1. operationdocument 中名为 operationName 的 Operation。
    2. 如果未找到 operation,则产生一个查询错误。
    3. 返回 operation

6.1.1验证请求

正如“验证”部分所述,只有通过所有验证规则的请求才应执行。如果已知验证错误,则应在响应的“errors”列表中报告这些错误,并且请求必须失败且不执行。

通常,验证是在紧靠执行之前的请求上下文中执行的,但是,如果已知完全相同的请求之前已验证过,则 GraphQL 服务可以在不立即验证请求的情况下执行请求。GraphQL 服务应仅执行在某个时候已知没有任何验证错误,并且此后未更改的请求。

例如:请求可以在开发期间进行验证,前提是它之后没有更改,或者服务可以验证一次请求并记住结果,以避免将来再次验证相同的请求。

6.1.2强制变量值

如果操作定义了任何变量,则需要使用变量声明类型的输入强制规则来强制这些变量的值。如果在变量值的输入强制期间遇到查询错误,则操作将失败且不执行。

CoerceVariableValues(schema, operation, variableValues)
  1. coercedValues 为一个空的无序 Map。
  2. variableDefinitionsoperation 定义的变量。
  3. 对于 variableDefinitions 中的每个 variableDefinition
    1. variableNamevariableDefinition 的名称。
    2. variableTypevariableDefinition 的预期类型。
    3. 断言:IsInputType(variableType) 必须为 true
    4. defaultValuevariableDefinition 的默认值。
    5. 如果 variableValues 为名称 variableName 提供了值,则设 hasValuetrue
    6. valuevariableValues 中为名称 variableName 提供的值。
    7. 如果 hasValue 不是 truedefaultValue 存在(包括 null
      1. 向名为 variableNamecoercedValues 添加一个条目,其值为 defaultValue
    8. 否则,如果 variableType 是 Non‐Nullable 类型,并且 hasValue 不是 truevaluenull,则抛出查询错误。
    9. 否则,如果 hasValue 为 true
      1. 如果 valuenull
        1. 向名为 variableNamecoercedValues 添加一个条目,其值为 null
      2. 否则
        1. 如果 value 无法根据 variableType 的输入强制规则进行强制,则抛出查询错误。
        2. coercedValue 为根据 variableType 的输入强制规则强制 value 的结果。
        3. 向名为 variableNamecoercedValues 添加一个条目,其值为 coercedValue
  4. 返回 coercedValues
此算法与 CoerceArgumentValues() 非常相似。

6.2执行操作

类型系统(如规范的“类型系统”部分所述)必须提供查询根对象类型。如果支持变更或订阅,则还必须分别提供变更或订阅根对象类型。

6.2.1查询

如果操作是查询,则操作的结果是使用查询根对象类型执行查询的顶层选择集的结果。

在执行查询时,可以提供初始值。

ExecuteQuery(query, schema, variableValues, initialValue)
  1. queryTypeschema 中的根 Query 类型。
  2. 断言:queryType 是 Object 类型。
  3. selectionSetquery 中的顶层选择集。
  4. data正常(允许并行化)运行 ExecuteSelectionSet(selectionSet, queryType, initialValue, variableValues) 的结果。
  5. errors 为执行选择集时产生的任何字段错误
  6. 返回一个包含 dataerrors 的无序 map。

6.2.2变更

如果操作是变更,则操作的结果是在变更根对象类型上执行变更的顶层选择集的结果。此选择集应按顺序执行。

预期变更操作中的顶层字段会对底层数据系统执行副作用。按顺序执行提供的变更可确保在这些副作用期间避免竞争条件。

ExecuteMutation(mutation, schema, variableValues, initialValue)
  1. mutationTypeschema 中的根 Mutation 类型。
  2. 断言:mutationType 是 Object 类型。
  3. selectionSetmutation 中的顶层选择集。
  4. data按顺序运行 ExecuteSelectionSet(selectionSet, mutationType, initialValue, variableValues) 的结果。
  5. errors 为执行选择集时产生的任何字段错误
  6. 返回一个包含 dataerrors 的无序 map。

6.2.3订阅

如果操作是订阅,则结果是一个名为“响应流”的事件流,其中事件流中的每个事件都是在底层“源流”上为每个新事件执行操作的结果。

执行订阅会在服务器上创建一个持久函数,该函数将底层源流映射到返回的响应流。

Subscribe(subscription, schema, variableValues, initialValue)
  1. sourceStream 为运行 CreateSourceEventStream(subscription, schema, variableValues, initialValue) 的结果。
  2. responseStream 为运行 MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues) 的结果
  3. 返回 responseStream
在大型订阅系统中,Subscribe()ExecuteSubscriptionEvent() 算法可以在单独的服务上运行,以保持可预测的扩展属性。请参阅下面关于大规模支持订阅的部分。

作为一个示例,考虑一个聊天应用程序。要订阅发布到聊天室的新消息,客户端发送如下请求

Example № 176subscription NewMessages {
  newMessage(roomId: 123) {
    sender
    text
  }
}

当客户端订阅时,每当有新消息发布到 ID 为“123”的聊天室时,将评估“sender”和“text”的选择,并将其发布到客户端,例如

Example № 177{
  "data": {
    "newMessage": {
      "sender": "Hagrid",
      "text": "You're a wizard!"
    }
  }
}

“发布到聊天室的新消息”可以使用“发布-订阅”系统,其中聊天室 ID 是“主题”,每个“发布”都包含发送者和文本。

事件流

事件流表示随时间推移的可观察的离散事件序列。例如,“发布-订阅”系统可能会在“订阅主题”时产生事件流,并且对于该主题的每次“发布”,都会在该事件流上发生一个事件。事件流可能会产生无限的事件序列,也可能在任何时候完成。事件流可能会因错误而完成,或者仅仅是因为不会再发生更多事件。观察者可以在任何时候决定通过取消事件流来停止观察事件流,此后它必须不再从该事件流接收事件。

大规模支持订阅

对于任何 GraphQL 服务来说,支持订阅都是一个重大变化。查询和变更操作是无状态的,允许通过克隆 GraphQL 服务器实例进行扩展。相比之下,订阅是有状态的,需要在订阅的生命周期内维护 GraphQL 文档、变量和其他上下文。

考虑当服务中单台机器发生故障导致状态丢失时,系统的行为。通过拥有单独的专用服务来管理订阅状态和客户端连接,可以提高持久性和可用性。

交付无关

GraphQL 订阅不需要任何特定的序列化格式或传输机制。订阅指定了创建流、该流上每个有效负载的内容以及关闭该流的算法。有意地没有关于消息确认、缓冲、重发请求或任何其他服务质量 (QoS) 详细信息的规范。消息序列化、传输机制和服务质量详细信息应由实现服务选择。

6.2.3.1源流

源流表示事件序列,每个事件都将触发与该事件对应的 GraphQL 执行。与字段值解析一样,创建源流的逻辑是特定于应用程序的。

CreateSourceEventStream(subscription, schema, variableValues, initialValue)
  1. subscriptionTypeschema 中的根 Subscription 类型。
  2. 断言:subscriptionType 是 Object 类型。
  3. groupedFieldSetCollectFields(subscriptionType, selectionSet, variableValues) 的结果。
  4. 如果 groupedFieldSet 没有正好一个条目,则抛出查询错误。
  5. fieldsgroupedFieldSet 中第一个条目的值。
  6. fieldNamefields 中第一个条目的名称。注意:如果使用别名,则此值不受影响。
  7. fieldfields 中的第一个条目。
  8. argumentValuesCoerceArgumentValues(subscriptionType, field, variableValues) 的结果
  9. fieldStream 为运行 ResolveFieldEventStream(subscriptionType, initialValue, fieldName, argumentValues) 的结果。
  10. 返回 fieldStream
ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues)
  1. resolversubscriptionType 提供的内部函数,用于确定名为 fieldName 的订阅字段的已解析事件流。
  2. 返回调用 resolver 的结果,并提供 rootValueargumentValues
ResolveFieldEventStream() 算法与 ResolveFieldValue() 算法有意相似,以便在任何操作类型上定义解析器时实现一致性。
6.2.3.2响应流

底层源流中的每个事件都触发订阅选择集的执行,并将该事件用作根值。

MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)
  1. 返回一个新的事件流 responseStream,它按如下方式产生事件
  2. 对于 sourceStream 上的每个 event
    1. response 为运行 ExecuteSubscriptionEvent(subscription, schema, variableValues, event) 的结果。
    2. 产生一个包含 response 的事件。
  3. responseStream 完成时:完成此事件流。
ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue)
  1. subscriptionTypeschema 中的根 Subscription 类型。
  2. 断言:subscriptionType 是 Object 类型。
  3. selectionSetsubscription 中的顶层选择集。
  4. data正常(允许并行化)运行 ExecuteSelectionSet(selectionSet, subscriptionType, initialValue, variableValues) 的结果。
  5. errors 为执行选择集时产生的任何字段错误
  6. 返回一个包含 dataerrors 的无序 map。
ExecuteSubscriptionEvent() 算法与 ExecuteQuery() 算法有意相似,因为这是生成每个事件结果的方式。
6.2.3.3取消订阅

当客户端不再希望接收订阅的有效负载时,取消订阅会取消响应流。这反过来也可能取消源流。这也是清理订阅使用的任何其他资源的好机会。

Unsubscribe(responseStream)
  1. 取消 responseStream

6.3执行选择集

要执行选择集,需要知道正在评估的对象值和对象类型,以及它是否必须按顺序执行,或者是否可以并行执行。

首先,选择集被转换为分组字段集;然后,分组字段集中的每个表示字段都会在响应映射中生成一个条目。

ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues)
  1. groupedFieldSetCollectFields(objectType, selectionSet, variableValues) 的结果。
  2. resultMap 初始化为空的有序 map。
  3. 对于每个 groupedFieldSet 作为 responseKeyfields
    1. fieldNamefields 中第一个条目的名称。注意:如果使用别名,则此值不受影响。
    2. fieldType 为为 objectType 的字段 fieldName 定义的返回类型。
    3. 如果定义了 fieldType
      1. responseValueExecuteField(objectType, objectValue, fields, fieldType, variableValues)
      2. responseValue 设置为 resultMapresponseKey 的值。
  4. 返回 resultMap
resultMap 按字段在查询中出现的先后顺序排序。这在下面的“字段收集”部分中进行了更详细的解释。

错误和 Non‐Null 字段

如果在 ExecuteSelectionSet() 期间,具有 non‐null fieldType 的字段抛出字段错误,则该错误必须传播到整个选择集,如果允许,则解析为 null,或者进一步传播到父字段。

如果发生这种情况,则可以取消任何尚未执行或尚未产生值的同级字段,以避免不必要的工作。

有关此行为的更多信息,请参阅“字段执行”的错误和 Non‐Null 性部分。

6.3.1正常执行和顺序执行

通常,执行器可以按其选择的任何顺序(通常是并行)执行分组字段集中的条目。由于除顶层变更字段之外的字段的解析必须始终是无副作用且幂等的,因此执行顺序不得影响结果,因此服务器可以自由地按其认为最佳的任何顺序执行字段条目。

例如,给定以下要正常执行的分组字段集

Example № 178{
  birthday {
    month
  }
  address {
    street
  }
}

有效的 GraphQL 执行器可以按其选择的任何顺序解析这四个字段(当然,birthday 必须在 month 之前解析,address 必须在 street 之前解析)。

执行变更时,最顶层选择集中的选择将按顺序执行,从文本上第一个出现的字段开始。

按顺序执行分组字段集时,执行器必须按照分组字段集中提供的顺序考虑分组字段集中的每个条目。它必须确定结果映射中每个项目的对应条目,直到完成,然后才能继续处理分组字段集中的下一个项目

例如,给定以下要按顺序执行的选择集

Example № 179{
  changeBirthday(birthday: $newBirthday) {
    month
  }
  changeAddress(address: $newAddress) {
    street
  }
}

执行器必须按顺序执行

作为一个说明性示例,假设我们有一个变更字段 changeTheNumber,它返回一个包含一个字段 theNumber 的对象。如果我们按顺序执行以下选择集

Example № 180{
  first: changeTheNumber(newNumber: 1) {
    theNumber
  }
  second: changeTheNumber(newNumber: 3) {
    theNumber
  }
  third: changeTheNumber(newNumber: 2) {
    theNumber
  }
}

执行器将按顺序执行以下操作

  • 解析 changeTheNumber(newNumber: 1) 字段
  • 正常执行 first{ theNumber } 子选择集
  • 解析 changeTheNumber(newNumber: 3) 字段
  • 正常执行 second{ theNumber } 子选择集
  • 解析 changeTheNumber(newNumber: 2) 字段
  • 正常执行 third{ theNumber } 子选择集

正确的执行器必须为该选择集生成以下结果

Example № 181{
  "first": {
    "theNumber": 1
  },
  "second": {
    "theNumber": 3
  },
  "third": {
    "theNumber": 2
  }
}

6.3.2字段收集

在执行之前,通过调用 CollectFields() 将选择集转换为分组字段集。分组字段集中的每个条目都是共享响应键(别名,如果已定义,否则为字段名称)的字段列表。这确保了通过引用的片段包含的具有相同响应键的所有字段同时执行。

例如,收集此选择集的字段将收集字段 a 的两个实例和字段 b 的一个实例

Example № 182{
  a {
    subfield1
  }
  ...ExampleFragment
}

fragment ExampleFragment on Query {
  a {
    subfield2
  }
  b
}

通过执行维护 CollectFields() 生成的字段组的深度优先搜索顺序,确保字段以稳定且可预测的顺序出现在执行的响应中。

CollectFields(objectType, selectionSet, variableValues, visitedFragments)
  1. 如果未提供 visitedFragments,则将其初始化为空集。
  2. groupedFields 初始化为列表的空有序 map。
  3. 对于 selectionSet 中的每个 selection
    1. 如果 selection 提供了指令 @skip,则设 skipDirective 为该指令。
      1. 如果 skipDirectiveif 参数为 true 或为 variableValues 中值为 true 的变量,则继续处理 selectionSet 中的下一个 selection
    2. 如果 selection 提供了指令 @include,则设 includeDirective 为该指令。
      1. 如果 includeDirectiveif 参数不是 true 且不是 variableValues 中值为 true 的变量,则继续处理 selectionSet 中的下一个 selection
    3. 如果 selectionField
      1. responseKeyselection 的响应键(别名,如果已定义,否则为字段名称)。
      2. groupForResponseKeygroupedFieldsresponseKey 的列表;如果不存在此类列表,则将其创建为空列表。
      3. selection 附加到 groupForResponseKey
    4. 如果 selectionFragmentSpread
      1. fragmentSpreadNameselection 的名称。
      2. 如果 fragmentSpreadNamevisitedFragments 中,则继续处理 selectionSet 中的下一个 selection
      3. fragmentSpreadName 添加到 visitedFragments
      4. fragment 为当前 Document 中名称为 fragmentSpreadName 的 Fragment。
      5. 如果不存在此类 fragment,则继续处理 selectionSet 中的下一个 selection
      6. fragmentTypefragment 上的类型条件。
      7. 如果 DoesFragmentTypeApply(objectType, fragmentType) 为 false,则继续处理 selectionSet 中的下一个 selection
      8. fragmentSelectionSetfragment 的顶层选择集。
      9. fragmentGroupedFieldSet 为调用 CollectFields(objectType, fragmentSelectionSet, visitedFragments) 的结果。
      10. 对于 fragmentGroupedFieldSet 中的每个 fragmentGroup
        1. responseKeyfragmentGroup 中所有字段共享的响应键。
        2. groupForResponseKeygroupedFieldsresponseKey 的列表;如果不存在此类列表,则将其创建为空列表。
        3. fragmentGroup 中的所有项目附加到 groupForResponseKey
    5. 如果 selectionInlineFragment
      1. fragmentTypeselection 上的类型条件。
      2. 如果 fragmentType 不是 nullDoesFragmentTypeApply(objectType, fragmentType) 为 false,则继续处理 selectionSet 中的下一个 selection
      3. fragmentSelectionSetselection 的顶层选择集。
      4. fragmentGroupedFieldSet 为调用 CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments) 的结果。
      5. 对于 fragmentGroupedFieldSet 中的每个 fragmentGroup
        1. responseKeyfragmentGroup 中所有字段共享的响应键。
        2. groupForResponseKeygroupedFieldsresponseKey 的列表;如果不存在此类列表,则将其创建为空列表。
        3. fragmentGroup 中的所有项目附加到 groupForResponseKey
  4. 返回 groupedFields
DoesFragmentTypeApply(objectType, fragmentType)
  1. 如果 fragmentType 是 Object Type
    1. 如果 objectTypefragmentType 是同一类型,则返回 true,否则返回 false
  2. 如果 fragmentType 是 Interface Type
    1. 如果 objectTypefragmentType 的实现,则返回 true,否则返回 false
  3. 如果 fragmentType 是 Union
    1. 如果 objectTypefragmentType 的可能类型,则返回 true,否则返回 false

6.4执行字段

在选定 objectType 上定义的分组字段集中请求的每个字段都将在响应映射中产生一个条目。字段执行首先强制任何提供的参数值,然后解析字段的值,最后通过递归执行另一个选择集或强制标量值来完成该值。

ExecuteField(objectType, objectValue, fieldType, fields, variableValues)
  1. fieldfields 中的第一个条目。
  2. fieldNamefield 的字段名称。
  3. argumentValuesCoerceArgumentValues(objectType, field, variableValues) 的结果
  4. resolvedValueResolveFieldValue(objectType, objectValue, fieldName, argumentValues)
  5. 返回 CompleteValue(fieldType, fields, resolvedValue, variableValues) 的结果。

6.4.1强制字段参数

字段可以包含参数,这些参数提供给底层运行时,以便正确生成值。这些参数由类型系统中的字段定义为具有特定的输入类型。

在查询中的每个参数位置,可以是字面量 Value,也可以是在运行时提供的 Variable

CoerceArgumentValues(objectType, field, variableValues)
  1. coercedValues 为一个空的无序 Map。
  2. argumentValuesfield 中提供的参数值。
  3. fieldNamefield 的名称。
  4. argumentDefinitionsobjectType 为名为 fieldName 的字段定义的参数。
  5. 对于 argumentDefinitions 中的每个 argumentDefinition
    1. argumentNameargumentDefinition 的名称。
    2. argumentTypeargumentDefinition 的预期类型。
    3. defaultValueargumentDefinition 的默认值。
    4. 如果 argumentValues 为名称 argumentName 提供了值,则设 hasValuetrue
    5. argumentValueargumentValues 中为名称 argumentName 提供的值。
    6. 如果 argumentValueVariable
      1. variableNameargumentValue 的名称。
      2. 如果 variableValues 为名称 variableName 提供了值,则设 hasValuetrue
      3. valuevariableValues 中为名称 variableName 提供的值。
    7. 否则,设 valueargumentValue
    8. 如果 hasValue 不是 truedefaultValue 存在(包括 null
      1. 向名为 argumentNamecoercedValues 添加一个条目,其值为 defaultValue
    9. Otherwise if argumentType 是一个 Non‐Nullable 类型,并且 hasValue 不是 truevaluenull,则抛出一个字段错误。
    10. 否则,如果 hasValue 为 true
      1. 如果 valuenull
        1. 添加一个条目到名为 argumentNamecoercedValues 中,其值为 null
      2. 否则,如果 argumentValue 是一个 Variable
        1. 添加一个条目到名为 argumentNamecoercedValues 中,其值为 value
      3. 否则
        1. 如果 value 无法根据 variableType 的输入强制规则进行强制转换,则抛出一个字段错误。
        2. coercedValue 为根据 variableType 的输入强制规则强制 value 的结果。
        3. 添加一个条目到名为 argumentNamecoercedValues 中,其值为 coercedValue
  6. 返回 coercedValues
变量值不会被强制转换,因为它们预计在 CoerceVariableValues() 中执行操作之前被强制转换,并且有效的查询必须只允许使用适当类型的变量。

6.4.2值解析

虽然几乎所有 GraphQL 的执行都可以进行通用描述,但最终暴露 GraphQL 接口的内部系统必须提供值。这通过 ResolveFieldValue 公开,它为一个给定类型的字段和一个实际值生成一个值。

例如,这可能会接受 objectType Personfield "soulMate",以及代表约翰·列侬的 objectValue。它应该产生代表小野洋子的值。

ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)
  1. resolverobjectType 提供的内部函数,用于确定名为 fieldName 的字段的解析值。
  2. 返回调用 resolver 的结果,提供 objectValueargumentValues
由于 resolver 依赖于读取底层数据库或联网服务来生成值,因此通常是异步的。这需要 GraphQL 执行器的其余部分来处理异步执行流程。

6.4.3值完成

在解析字段的值之后,通过确保其符合预期的返回类型来完成它。如果返回类型是另一个 Object 类型,则字段执行过程继续递归。

CompleteValue(fieldType, fields, result, variableValues)
  1. 如果 fieldType 是一个 Non‐Null 类型
    1. innerTypefieldType 的内部类型。
    2. completedResult 为调用 CompleteValue(innerType, fields, result, variableValues) 的结果。
    3. 如果 completedResultnull,则抛出一个字段错误。
    4. 返回 completedResult
  2. 如果 resultnull (或另一个类似于 null 的内部值,例如 undefinedNaN),则返回 null
  3. 如果 fieldType 是一个 List 类型
    1. 如果 result 不是值的集合,则抛出一个字段错误。
    2. innerTypefieldType 的内部类型。
    3. 返回一个列表,其中每个列表项都是调用 CompleteValue(innerType, fields, resultItem, variableValues) 的结果,其中 resultItemresult 中的每个项目。
  4. 如果 fieldType 是一个 Scalar 或 Enum 类型
    1. 返回“强制转换” result 的结果,确保它是一个 fieldType 的合法值,否则为 null
  5. 如果 fieldType 是一个 Object、Interface 或 Union 类型
    1. 如果 fieldType 是一个 Object 类型。
      1. objectTypefieldType
    2. 否则,如果 fieldType 是一个 Interface 或 Union 类型。
      1. objectTypeResolveAbstractType(fieldType, result)
    3. subSelectionSet 为调用 MergeSelectionSets(fields) 的结果。
    4. 返回正常评估 ExecuteSelectionSet(subSelectionSet, objectType, result, variableValues) 的结果(允许并行化)。

解析抽象类型

当完成一个具有抽象返回类型的字段时,即 Interface 或 Union 返回类型,首先必须将抽象类型解析为相关的 Object 类型。此确定由内部系统使用任何适当的方式进行。

在面向对象的环境(如 Java 或 C#)中,确定 objectValue 的 Object 类型的常用方法是使用 objectValue 的类名。
ResolveAbstractType(abstractType, objectValue)
  1. 返回调用类型系统提供的内部方法的结果,该方法用于确定给定值 objectValueabstractType 的 Object 类型。

合并选择集

当并行执行多个同名字段时,它们的选择集在完成值时合并在一起,以便继续执行子选择集。

一个示例查询,说明具有相同名称和子选择的并行字段。

Example № 183{
  me {
    firstName
  }
  me {
    lastName
  }
}

在解析 me 的值之后,选择集合并在一起,以便可以为一个值解析 firstNamelastName

MergeSelectionSets(fields)
  1. selectionSet 为一个空列表。
  2. 对于 fields 中的每个 field
    1. fieldSelectionSetfield 的选择集。
    2. 如果 fieldSelectionSet 为空或为空,则继续到下一个字段。
    3. fieldSelectionSet 中的所有选择附加到 selectionSet
  3. 返回 selectionSet

6.4.4错误和 Non-Nullability

如果在解析字段时抛出错误,则应将其视为字段返回 null,并且必须将错误添加到响应中的 "errors" 列表中。

如果解析字段的结果是 null(要么是因为解析字段的函数返回 null,要么是因为发生了错误),并且该字段是 Non-Null 类型,则会抛出一个字段错误。该错误必须添加到响应中的 "errors" 列表中。

如果字段由于已添加到响应中的 "errors" 列表中的错误而返回 null,则 "errors" 列表不得进一步受到影响。也就是说,每个字段的错误列表中应只添加一个错误。

由于 Non-Null 类型字段不能为 null,因此字段错误会传播到父字段以进行处理。如果父字段可以为 null,则它解析为 null,否则,如果它是 Non-Null 类型,则字段错误会进一步传播到其父字段。

如果 List 类型包装了 Non-Null 类型,并且该列表中的一个元素解析为 null,则整个列表必须解析为 null。如果 List 类型也包装在 Non-Null 中,则字段错误将继续向上传播。

如果从请求的根到字段错误的源的所有字段都返回 Non-Null 类型,则响应中的 "data" 条目应为 null

7响应

当 GraphQL 服务器收到请求时,它必须返回一个格式良好的响应。服务器的响应描述了成功执行请求操作的结果,并描述了请求期间遇到的任何错误。

在字段错误发生在被替换为 null 的字段上的情况下,响应可能同时包含部分响应和遇到的错误。

7.1响应格式

GraphQL 操作的响应必须是一个映射。

如果操作遇到任何错误,则响应映射必须包含一个键为 errors 的条目。此条目的值在“错误”部分中描述。如果操作完成时没有遇到任何错误,则不得存在此条目。

如果操作包括执行,则响应映射必须包含一个键为 data 的条目。此条目的值在“数据”部分中描述。如果操作在执行之前失败,由于语法错误、缺少信息或验证错误,则不得存在此条目。

响应映射也可能包含一个键为 extensions 的条目。如果设置了此条目,则其值必须是一个映射。此条目为实现者保留,用于以他们认为合适的方式扩展协议,因此对其内容没有额外的限制。

为了确保协议的未来更改不会破坏现有的服务器和客户端,顶级响应映射不得包含除上述三个之外的任何条目。

errors 出现在响应中时,将其序列化时最好首先出现,以便在调试期间更清楚地了解响应中是否存在错误。

7.1.1数据

响应中的 data 条目将是请求操作的执行结果。如果操作是查询,则此输出将是模式的查询根类型的对象;如果操作是 mutation,则此输出将是模式的 mutation 根类型的对象。

如果在执行开始之前遇到错误,则 data 条目不应出现在结果中。

如果在执行期间遇到阻止有效响应的错误,则响应中的 data 条目应为 null

7.1.2错误

响应中的 errors 条目是一个非空错误列表,其中每个错误都是一个映射。

如果在请求的操作期间未遇到任何错误,则 errors 条目不应出现在结果中。

如果响应中的 data 条目不存在,则响应中的 errors 条目不得为空。它必须至少包含一个错误。它包含的错误应指示为什么无法返回数据。

如果响应中的 data 条目存在(包括如果它是值 null),则响应中的 errors 条目可能包含执行期间发生的任何错误。如果执行期间发生错误,则应包含这些错误。

错误结果格式

每个错误都必须包含一个键为 message 的条目,其中包含错误的字符串描述,旨在为开发人员提供指导,以理解和纠正错误。

如果错误可以与请求的 GraphQL 文档中的特定点相关联,则它应包含一个键为 locations 的条目,其中包含位置列表,其中每个位置都是一个具有键 linecolumn 的映射,两者都是从 1 开始的正数,用于描述关联的语法元素的开始位置。

如果错误可以与 GraphQL 结果中的特定字段相关联,则它必须包含一个键为 path 的条目,该条目详细说明了遇到错误的响应字段的路径。这允许客户端识别 null 结果是故意的还是由运行时错误引起的。

此字段应是一个路径段列表,从响应的根开始,到与错误关联的字段结束。表示字段的路径段应为字符串,表示列表索引的路径段应为 0 索引整数。如果错误发生在别名字段中,则错误的路径应使用别名,因为它表示响应中的路径,而不是查询中的路径。

例如,如果在以下查询中获取其中一个朋友的名字失败

Example № 184{
  hero(episode: $episode) {
    name
    heroFriends: friends {
      id
      name
    }
  }
}

响应可能如下所示

Example № 185{
  "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 № 186{
  "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 № 187{
  "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 № 188{
  "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.2序列化格式

GraphQL 不要求特定的序列化格式。但是,客户端应使用支持 GraphQL 响应中主要原语的序列化格式。特别是,序列化格式必须至少支持以下四个原语的表示

序列化格式还应支持以下原语,每个原语代表一个常见的 GraphQL 标量类型,但是,如果任何原语不直接支持,则可以使用字符串或更简单的原语作为替代

这并非旨在详尽列出序列化格式可以编码的内容。例如,表示日期、时间、URI 或具有不同精度的数字的自定义标量可以使用给定序列化格式可能支持的任何相关格式表示。

7.2.1JSON 序列化

JSON 是 GraphQL 最常见的序列化格式。尽管如上所述,GraphQL 不要求特定的序列化格式。

当使用 JSON 作为 GraphQL 响应的序列化时,应使用以下 JSON 值来编码相关的 GraphQL 值

GraphQL 值JSON 值
映射对象
列表数组
Nullnull
字符串字符串
布尔值truefalse
整数数字
浮点数数字
枚举值字符串
为了保持一致性和便于表示法,本文档通篇以 JSON 格式给出响应示例。

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" } 编码相同的值,但它们也具有可观察到的不同属性顺序。

这不违反 JSON 规范,因为客户端仍然可以将响应中的对象解释为无序映射并获得有效的值。

A附录:符号约定

本规范文档包含许多用于描述技术概念(例如语言语法和语义以及运行时算法)的符号约定。

本附录旨在更详细地解释这些符号,以避免歧义。

A.1上下文无关文法

上下文无关文法由许多产生式组成。每个产生式都有一个称为“非终结符”的抽象符号作为其左侧,以及零个或多个可能的非终结符序列和/或终结符字符作为其右侧。

从单个目标非终结符开始,上下文无关文法描述了一种语言:可以通过重复将目标序列中的任何非终结符替换为其定义的一个序列,直到所有非终结符都被终结符字符替换为止,来描述的可能字符序列的集合。

终结符在本文档中以等宽字体以两种形式表示:特定的 Unicode 字符或 Unicode 字符序列(例如 =terminal),以及由正则表达式定义的 Unicode 字符模式(例如 /[0-9]+/)。

非终结符产生式规则在本文档中使用以下表示法表示具有单个定义的非终结符

同时使用以下表示法表示具有多个定义的产生式

NonTerminalWithManyDefinitions
OtherNonTerminalterminal
terminal

定义可以引用自身,这描述了重复序列,例如

A.2词法和句法文法

GraphQL 语言在句法文法中定义,其中终结符是标记。标记在词法文法中定义,词法文法匹配源字符的模式。解析源 Unicode 字符序列的结果生成 GraphQL AST。

词法文法产生式通过终结符 Unicode 字符的模式来描述非终结符“标记”。在词法文法产生式中的任何终结符 Unicode 字符之间,不得出现“空白”或其他忽略的字符。词法文法产生式以双冒号 :: 定义区分。

Word
/[A-Za-z]+/

句法文法产生式通过终结符标记的模式来描述非终结符“规则”。空白和其他忽略的字符可以出现在任何终结符标记之前或之后。句法文法产生式以单冒号 : 定义区分。

Sentence
NounVerb

A.3文法符号

本规范使用一些额外的符号来描述常见模式,例如可选或重复模式,或非终结符定义的参数化变更。本节解释这些简写符号及其在上下文无关文法中的扩展定义。

约束

文法产生式可以使用短语“but not”,然后指示要排除的扩展,来指定不允许某些扩展。

例如,产生式

SafeName
NameSevenCarlinWords

表示非终结符 SafeName 可以被任何可以替换 Name 的字符序列替换,前提是相同的字符序列不能替换 SevenCarlinWords

文法也可以在“but not”之后列出多个由“or”分隔的限制。

例如

可选性和列表

下标后缀 “Symbolopt” 是两种可能序列的简写,一种包含该符号,另一种排除该符号。

例如

Sentence
NounVerbAdverbopt

是以下内容的简写

Sentence
NounVerb
NounVerbAdverb

下标后缀 “Symbollist” 是一个或多个该符号的列表的简写。

例如

Book
CoverPagelistCover

是以下内容的简写

Book
CoverPage_listCover

参数化文法产生式

花括号中的符号定义下标后缀参数 “SymbolParam” 是两个符号定义的简写,一个附加了该参数名称,另一个没有。符号上的相同下标后缀是该定义的变体的简写。如果参数以“?”开头,则在具有相同参数的符号定义中使用该形式的符号。当分别以“[+Param]”和“[~Param]”为前缀时,可以有条件地包含或排除某些可能的序列。

例如

ExampleParam
A
BParam
CParam
ParamD
ParamE

是以下内容的简写

Example
A
B_param
C
E
Example_param
A
B_param
C_param
D

A.4文法语义

本规范以算法步骤列表的形式描述了许多文法产生式的语义值。

例如,这描述了解析器应如何解释字符串文字

StringValue
""
  1. 返回一个空的 Unicode 字符序列。
StringValue
  1. 返回所有 StringCharacter Unicode 字符值的 Unicode 字符序列。

A.5算法

本规范描述了静态和运行时语义使用的一些算法,它们以类似于函数的语法定义,包括算法的名称和它接受的参数,以及要按顺序列出的算法步骤列表。每个步骤都可以建立对其他值的引用,检查各种条件,调用其他算法,并最终返回一个值,该值表示为提供的参数的算法结果。

例如,以下示例描述了一个名为 Fibonacci 的算法,它接受一个参数 number。该算法的步骤生成斐波那契数列中的下一个数字

Fibonacci(number)
  1. 如果 number0
    1. 返回 1
  2. 如果 number1
    1. 返回 2
  3. previousNumbernumber - 1
  4. previousPreviousNumbernumber - 2
  5. 返回 Fibonacci(previousNumber) + Fibonacci(previousPreviousNumber)
本文档中描述的算法易于理解。鼓励实施者包括等效但优化的实现。

B附录:文法摘要

SourceCharacter
/[\u0009\u000A\u000D\u0020-\uFFFF]/

B.1忽略的标记

UnicodeBOM
字节顺序标记 (Byte Order Mark) (U+FEFF)
WhiteSpace
水平制表符 (Horizontal Tab) (U+0009)
空格 (Space) (U+0020)
LineTerminator
换行符 (New Line) (U+000A)
回车符 (Carriage Return) (U+000D)换行符 (New Line) (U+000A)
回车符 (Carriage Return) (U+000D)换行符 (New Line) (U+000A)

B.2词法标记

Punctuator
!$()...:=@[]{|}
Name
/[_A-Za-z][_0-9A-Za-z]*/
数字
0123456789
StringValue
"字符串字符列表可选"
"""块字符串字符列表可选"""
转义 Unicode
/[0-9A-Fa-f]{4}/
转义字符
"\/bfnrt
块字符串值被解释为排除空白的初始行和尾行以及与 BlockStringValue() 的统一缩进。

B.3文档

OperationType
querymutationsubscription
ArgumentsConst
(ArgumentConstlist)
布尔值
truefalse
空值
null
枚举值
名称truefalsenull
列表值常量
[]
[条件常量列表]
对象值常量
{}
{对象字段条件常量列表}
指令常量
指令条件常量列表
指令常量
@名称参数条件常量可选
SchemaExtension
extendschemaDirectivesConstopt{OperationTypeDefinitionlist}
extendschemaDirectivesConst
ExecutableDirectiveLocation
QUERY
MUTATION
SUBSCRIPTION
FIELD
FRAGMENT_DEFINITION
FRAGMENT_SPREAD
INLINE_FRAGMENT
TypeSystemDirectiveLocation
SCHEMA
SCALAR
OBJECT
FIELD_DEFINITION
ARGUMENT_DEFINITION
INTERFACE
UNION
ENUM
ENUM_VALUE
INPUT_OBJECT
INPUT_FIELD_DEFINITION

§索引

  1. 别名
  2. AreTypesCompatible
  3. Argument
  4. Arguments
  5. ArgumentsDefinition
  6. 块字符串字符
  7. BlockStringValue
  8. 布尔值
  9. CoerceArgumentValues
  10. CoerceVariableValues
  11. CollectFields
  12. Comma
  13. Comment
  14. CommentChar
  15. CompleteValue
  16. 创建源事件流
  17. 默认值
  18. Definition
  19. 描述
  20. 数字
  21. 指令
  22. DirectiveDefinition
  23. DirectiveLocation
  24. DirectiveLocations
  25. 指令集
  26. Document
  27. 片段类型是否适用
  28. EnumTypeDefinition
  29. EnumTypeExtension
  30. 枚举值
  31. EnumValueDefinition
  32. EnumValuesDefinition
  33. 转义字符
  34. 转义 Unicode
  35. ExecutableDefinition
  36. ExecutableDirectiveLocation
  37. 执行字段
  38. 执行变更
  39. 执行查询
  40. 执行请求
  41. 执行选择集
  42. 执行订阅事件
  43. 指数指示符
  44. 指数部分
  45. Field
  46. FieldDefinition
  47. FieldsDefinition
  48. 集合中的字段可以合并
  49. FloatValue
  50. 小数部分
  51. FragmentDefinition
  52. 片段名称
  53. FragmentSpread
  54. 获取操作
  55. 获取可能类型
  56. Ignored
  57. ImplementsInterfaces
  58. InlineFragment
  59. InputFieldsDefinition
  60. InputObjectTypeDefinition
  61. InputObjectTypeExtension
  62. InputValueDefinition
  63. IntValue
  64. 整数部分
  65. InterfaceTypeDefinition
  66. InterfaceTypeExtension
  67. 是否为输入类型
  68. 是否为输出类型
  69. 是否允许变量使用
  70. LineTerminator
  71. 列表类型
  72. 列表值
  73. 将源映射到响应事件
  74. 合并选择集
  75. Name
  76. 命名类型
  77. 负号
  78. 非空类型
  79. 非零数字
  80. 空值
  81. 对象字段
  82. ObjectTypeDefinition
  83. ObjectTypeExtension
  84. 对象值
  85. OperationDefinition
  86. OperationType
  87. Punctuator
  88. 解析抽象类型
  89. 解析字段事件流
  90. 解析字段值
  91. 根操作类型定义
  92. 相同的响应形状
  93. ScalarTypeDefinition
  94. ScalarTypeExtension
  95. SchemaDefinition
  96. SchemaExtension
  97. Selection
  98. SelectionSet
  99. 符号
  100. SourceCharacter
  101. 字符串字符
  102. StringValue
  103. 订阅
  104. Token
  105. 类型
  106. 类型条件
  107. TypeDefinition
  108. TypeExtension
  109. TypeSystemDefinition
  110. TypeSystemDirectiveLocation
  111. TypeSystemExtension
  112. UnicodeBOM
  113. UnionMemberTypes
  114. UnionTypeDefinition
  115. UnionTypeExtension
  116. 取消订阅
  117. 变量
  118. 变量定义
  119. 变量定义
  120. WhiteSpace
  1. 1概述
  2. 2语言
    1. 2.1源文本
      1. 2.1.1Unicode
      2. 2.1.2空白字符
      3. 2.1.3行终止符
      4. 2.1.4注释
      5. 2.1.5无关紧要的逗号
      6. 2.1.6词法标记
      7. 2.1.7忽略的标记
      8. 2.1.8标点符号
      9. 2.1.9名称
    2. 2.2文档
    3. 2.3操作
    4. 2.4选择集
    5. 2.5字段
    6. 2.6参数
    7. 2.7字段别名
    8. 2.8片段
      1. 2.8.1类型条件
      2. 2.8.2内联片段
    9. 2.9输入值
      1. 2.9.1整数值
      2. 2.9.2浮点数值
      3. 2.9.3布尔值
      4. 2.9.4字符串值
      5. 2.9.5空值
      6. 2.9.6枚举值
      7. 2.9.7列表值
      8. 2.9.8输入对象值
    10. 2.10变量
    11. 2.11类型引用
    12. 2.12指令
  3. 3类型系统
    1. 3.1类型系统扩展
    2. 3.2模式
      1. 3.2.1根操作类型
      2. 3.2.2模式扩展
    3. 3.3描述
    4. 3.4类型
      1. 3.4.1包装类型
      2. 3.4.2输入和输出类型
      3. 3.4.3类型扩展
    5. 3.5标量
      1. 3.5.1Int
      2. 3.5.2Float
      3. 3.5.3String
      4. 3.5.4Boolean
      5. 3.5.5ID
      6. 3.5.6标量扩展
    6. 3.6对象
      1. 3.6.1字段参数
      2. 3.6.2字段弃用
      3. 3.6.3对象扩展
    7. 3.7接口
      1. 3.7.1接口扩展
    8. 3.8联合
      1. 3.8.1联合扩展
    9. 3.9枚举
      1. 3.9.1枚举扩展
    10. 3.10输入对象
      1. 3.10.1输入对象扩展
    11. 3.11列表
    12. 3.12非空
      1. 3.12.1组合列表和非空
    13. 3.13指令
      1. 3.13.1@skip
      2. 3.13.2@include
      3. 3.13.3@deprecated
  4. 4内省
    1. 4.1保留名称
    2. 4.2文档
    3. 4.3弃用
    4. 4.4类型名称内省
    5. 4.5模式内省
      1. 4.5.1__Type 类型
      2. 4.5.2类型种类
        1. 4.5.2.1标量
        2. 4.5.2.2对象
        3. 4.5.2.3联合
        4. 4.5.2.4接口
        5. 4.5.2.5枚举
        6. 4.5.2.6输入对象
        7. 4.5.2.7列表
        8. 4.5.2.8非空
      3. 4.5.3__Field 类型
      4. 4.5.4__InputValue 类型
      5. 4.5.5__EnumValue 类型
      6. 4.5.6__Directive 类型
  5. 5验证
    1. 5.1文档
      1. 5.1.1可执行定义
    2. 5.2操作
      1. 5.2.1命名操作定义
        1. 5.2.1.1操作名称唯一性
      2. 5.2.2匿名操作定义
        1. 5.2.2.1单独的匿名操作
      3. 5.2.3订阅操作定义
        1. 5.2.3.1单个根字段
    3. 5.3字段
      1. 5.3.1对象、接口和联合类型上的字段选择
      2. 5.3.2字段选择合并
      3. 5.3.3叶字段选择
    4. 5.4参数
      1. 5.4.1参数名称
      2. 5.4.2参数唯一性
        1. 5.4.2.1必需参数
    5. 5.5片段
      1. 5.5.1片段声明
        1. 5.5.1.1片段名称唯一性
        2. 5.5.1.2片段扩展类型存在性
        3. 5.5.1.3复合类型上的片段
        4. 5.5.1.4片段必须被使用
      2. 5.5.2片段扩展
        1. 5.5.2.1片段扩展目标已定义
        2. 5.5.2.2片段扩展不得形成循环
        3. 5.5.2.3片段扩展是可能的
          1. 5.5.2.3.1对象范围内的对象扩展
          2. 5.5.2.3.2对象范围内的抽象扩展
          3. 5.5.2.3.3抽象范围内的对象扩展
          4. 5.5.2.3.4抽象范围内的抽象扩展
    6. 5.6
      1. 5.6.1正确类型的值
      2. 5.6.2输入对象字段名称
      3. 5.6.3输入对象字段唯一性
      4. 5.6.4输入对象必需字段
    7. 5.7指令
      1. 5.7.1指令已定义
      2. 5.7.2指令位于有效位置
      3. 5.7.3每个位置的指令是唯一的
    8. 5.8变量
      1. 5.8.1变量唯一性
      2. 5.8.2变量是输入类型
      3. 5.8.3所有变量使用已定义
      4. 5.8.4所有变量已使用
      5. 5.8.5所有变量用法均被允许
  6. 6执行
    1. 6.1执行请求
      1. 6.1.1验证请求
      2. 6.1.2强制变量值
    2. 6.2执行操作
      1. 6.2.1查询
      2. 6.2.2变更
      3. 6.2.3订阅
        1. 6.2.3.1源流
        2. 6.2.3.2响应流
        3. 6.2.3.3取消订阅
    3. 6.3执行选择集
      1. 6.3.1正常和串行执行
      2. 6.3.2字段集合
    4. 6.4执行字段
      1. 6.4.1强制字段参数
      2. 6.4.2值解析
      3. 6.4.3值完成
      4. 6.4.4错误和非空性
  7. 7响应
    1. 7.1响应格式
      1. 7.1.1数据
      2. 7.1.2错误
    2. 7.2序列化格式
      1. 7.2.1JSON 序列化
      2. 7.2.2序列化映射排序
  8. A附录:符号约定
    1. A.1上下文无关文法
    2. A.2词法和句法文法
    3. A.3文法符号
    4. A.4文法语义
    5. A.5算法
  9. B附录:文法总结
    1. B.1忽略的标记
    2. B.2词法标记
    3. B.3文档
  10. §索引