GraphQL

工作草案 – 2016 年 4 月

简介

这是 GraphQL 的 RFC 草案规范,GraphQL 是 Facebook 在 2012 年创建的一种查询语言,用于描述客户端-服务器应用程序的数据模型的功能和需求。此标准的制定工作始于 2015 年。GraphQL 是一种新的且不断发展的语言,尚未完成。本规范的未来版本将继续进行重大改进。

版权声明

版权所有 (c) 2015, Facebook, Inc. 保留所有权利。

在满足以下条件的前提下,允许以源代码和二进制形式重新分发和使用,无论是否经过修改:

本软件由版权所有者和贡献者“按原样”提供,并且不承担任何明示或暗示的保证,包括但不限于对适销性和适用于特定用途的暗示保证。在任何情况下,版权所有者或贡献者均不对任何直接、间接、附带、特殊、惩戒性或后果性损害(包括但不限于采购替代商品或服务; 失去使用、数据或利润;或业务中断),无论因何种原因引起,也无论基于何种责任理论,无论是合同、严格责任还是侵权行为(包括疏忽或其他),以任何方式因使用本软件而引起,即使已被告知可能发生此类损害。

1概述

GraphQL 是一种查询语言,旨在通过提供直观且灵活的语法和系统来构建客户端应用程序,以描述其数据需求和交互。

例如,以下 GraphQL 请求将从 Facebook 的 GraphQL 实现中接收 id 为 4 的用户的姓名。

{
  user(id: 4) {
    name
  }
}

这将生成结果数据(JSON 格式):

{
  "user": {
    "name": "Mark Zuckerberg"
  }
}

GraphQL 不是一种能够进行任意计算的编程语言,而是一种用于查询应用服务器的语言,这些应用服务器的功能在本规范中定义。GraphQL 不强制应用服务器实现它时必须使用特定的编程语言或存储系统。相反,应用服务器将其功能映射到 GraphQL 编码的统一语言、类型系统和理念。这为产品开发提供了统一的友好界面,并为工具构建提供了强大的平台。

GraphQL 具有许多设计原则:

由于这些原则,GraphQL 是构建客户端应用程序的强大而高效的环境。针对正在运行的 GraphQL 服务器(在优质工具的支持下)构建应用程序的产品开发人员和设计师可以快速变得高效,而无需阅读大量文档,也几乎无需或根本无需正式培训。为了实现这种体验,必须有人构建这些服务器和工具。

以下正式规范可作为这些构建者的参考。它描述了语言及其语法、类型系统和用于查询它的内省系统,以及执行和验证引擎及其背后的算法。本规范的目标是为 GraphQL 工具、客户端库和服务器实现的生态系统提供基础和框架,该生态系统跨越组织和平台,并且尚未构建。我们期待与社区合作来实现这一目标。

2语言

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

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

2.1源文本

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

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

2.1.1Unicode

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

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

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

2.1.2空格

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

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

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

2.1.3行终止符

LineTerminator
换行符 (U+000A)
回车符 (U+000D)换行符 (U+000A)
回车符 (U+000D)换行符 (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 查询文档只有在包含操作时才能被服务器执行。但是,不包含操作的文档仍然可以被解析和验证,以允许客户端跨多个文档表示单个请求。

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

2.3操作

OperationType
querymutation

GraphQL 建模的操作有两种类型:

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

例如,以下变更操作可能“喜欢”一个故事,然后检索新的点赞数:

mutation {
  likeStory(storyID: 12345) {
    story {
      likeCount
    }
  }
}

查询简写

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

例如,以下未命名的查询操作是通过查询简写编写的。

{
  field
}
以下许多示例将使用查询简写语法。

2.4选择集

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

{
  id
  firstName
  lastName
}

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

2.5字段

选择集主要由字段组成。字段描述可在选择集中请求的一个离散的信息片段。

某些字段描述复杂的数据或与其他数据的关系。为了进一步探索这些数据,字段本身可以包含一个选择集,从而允许深度嵌套的请求。所有 GraphQL 操作都必须将其选择指定到返回标量值的字段,以确保形状明确的响应。

例如,以下操作选择复杂数据和关系的字段,一直到标量值。

{
  me {
    id
    firstName
    lastName
    birthday {
      month
      day
    }
    friends {
      name
    }
  }
}

操作的顶层选择集中的字段通常表示您的应用程序及其当前查看者全局可访问的某些信息。这些顶级字段的一些典型示例包括对当前登录的查看者的引用,或访问由唯一标识符引用的某些类型的数据。

# `me` could represent the currently logged in viewer.
{
  me {
    name
  }
}

# `user` represents one of many users in a graph of data, referred to by a
# unique identifier.
{
  user(id: 4) {
    name
  }
}

2.6参数

字段在概念上是返回值的函数,有时接受改变其行为的参数。这些参数通常直接映射到 GraphQL 服务器实现中的函数参数。

在此示例中,我们要查询特定用户(通过 id 参数请求)及其特定 size 的个人资料图片:

{
  user(id: 4) {
    id
    name
    profilePic(size: 100)
  }
}

一个给定字段可以存在多个参数:

{
  user(id: 4) {
    id
    name
    profilePic(width: 100, height: 50)
  }
}

参数是无序的

可以以任何语法顺序提供参数,并保持相同的语义意义。

以下两个查询在语义上是相同的:

{
  picture(width: 200, height: 100)
}
{
  picture(height: 100, width: 200)
}

2.7字段别名

默认情况下,响应对象中的键将使用查询的字段名称。但是,您可以通过指定别名来定义不同的名称。

在此示例中,我们可以获取两个不同大小的个人资料图片,并确保结果对象不会有重复的键:

{
  user(id: 4) {
    id
    name
    smallPic: profilePic(size: 64)
    bigPic: profilePic(size: 1024)
  }
}

这将返回结果:

{
  "user": {
    "id": 4,
    "name": "Mark Zuckerberg",
    "smallPic": "https://cdn.site.io/pic-4-64.jpg",
    "bigPic": "https://cdn.site.io/pic-4-1024.jpg"
  }
}

由于查询的顶层是一个字段,因此也可以为其指定别名:

{
  zuck: user(id: 4) {
    id
    name
  }
}

返回结果:

{
  "zuck": {
    "id": 4,
    "name": "Mark Zuckerberg"
  }
}

如果提供了别名,则字段的响应键是其别名,否则是字段的名称。

2.8片段

片段是 GraphQL 中主要的组合单元。

片段允许重用字段的常见重复选择,从而减少文档中重复的文本。当针对接口或联合进行查询时,内联片段可以直接在选择中使用,以便根据类型条件进行条件化。

例如,如果我们想要获取有关共同朋友以及某些用户的朋友的一些常见信息:

query noFragments {
  user(id: 4) {
    friends(first: 10) {
      id
      name
      profilePic(size: 50)
    }
    mutualFriends(first: 10) {
      id
      name
      profilePic(size: 50)
    }
  }
}

重复的字段可以提取到片段中,并由父片段或查询组成。

query withFragments {
  user(id: 4) {
    friends(first: 10) {
      ...friendFields
    }
    mutualFriends(first: 10) {
      ...friendFields
    }
  }
}

fragment friendFields on User {
  id
  name
  profilePic(size: 50)
}

片段通过使用扩展运算符 (...) 消耗。片段选择的所有字段将添加到与片段调用相同的级别的查询字段选择中。这通过多层片段扩展发生。

例如:

query 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 数据模型上的以下查询中:

query 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

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

2.8.2内联片段

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

query inlineFragmentTyping {
  profiles(handles: ["zuck", "cocacola"]) {
    handle
    ... on User {
      friends {
        count
      }
    }
    ... on Page {
      likers {
        count
      }
    }
  }
}

内联片段也可以用于将指令应用于一组字段。如果省略了 TypeCondition,则内联片段被认为与封闭上下文的类型相同。

query inlineFragmentNoType($expandedInfo: Boolean) {
  user(handle: "zuck") {
    id
    name
    ... @include(if: $expandedInfo) {
      firstName
      lastName
      birthday
    }
  }
}

2.9输入值

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

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

2.9.1Int 值

数字
0123456789

Int 数字的指定不带小数点或指数(例如 1)。

2.9.2Float 值

Float 数字包含小数点(例如 1.0)或指数(例如 1e50)或两者(例如 6.0221413e23)。

2.9.3布尔值

布尔值
truefalse

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

2.9.4字符串值

转义的 Unicode
/[0-9A-Fa-f]{4}/
转义字符
"\/bfnrt

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

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

语义

StringValue
""
  1. 返回一个空的 Unicode 字符序列。
StringValue
  1. 返回所有 StringCharacter Unicode 字符值的 Unicode 字符序列。
字符串字符
  1. 返回由 UTF16 十六进制标识符 EscapedUnicode 表示的字符值。

2.9.5枚举值

枚举值
Nametruefalsenull

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

为了避免混淆,枚举值不能为 “null”。GraphQL 不提供字面量值来表示 null 的概念。

2.9.6列表值

列表值常量
[]
[Value常量列表]

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

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

语义

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

2.9.7输入对象值

对象值常量
{}
{ObjectField常量列表}

输入对象字面量值是用花括号 { } 包裹的键值对输入值的无序列表。对象字面量的值可以是任何输入值字面量或变量(例如 { name: "Hello world", score: 1.0 })。我们将输入对象的字面量表示称为“对象字面量”。

输入对象字段是无序的

输入对象字段可以以任何语法顺序提供,并保持相同的语义含义。

以下两个查询在语义上是相同的:

{
  nearestThing(location: { lon: 12.43, lat: -53.211 })
}
{
  nearestThing(location: { lat: -53.211, lon: 12.43 })
}

语义

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

2.10变量

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

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

变量必须在操作的顶部定义,并在该操作的整个执行过程中都有效。

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

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

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

{
  "devicePicSize": 60
}

片段中的变量使用

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

2.11输入类型

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

语义

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

2.12指令

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

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

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

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

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

3类型系统

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

GraphQL 服务器的功能被称为该服务器的“模式”。模式是根据它支持的类型和指令来定义的。

给定的 GraphQL 模式本身必须在内部有效。本节描述了相关的此验证过程的规则。

GraphQL 模式由每种操作类型的根类型表示:查询和变更;这决定了这些操作在类型系统中开始的位置。

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

GraphQL 模式中的所有指令都必须具有唯一的名称。指令和类型可以共享相同的名称,因为它们之间没有歧义。

3.1类型

任何 GraphQL 模式的基本单元都是类型。GraphQL 中有八种类型的类型。

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

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

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

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

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

到目前为止,所有类型都被假定为既可为空又是单数的:例如,标量字符串返回 null 或单个字符串。类型系统可能希望定义它返回其他类型的列表;为此提供了 List 类型,并包装了另一种类型。类似地,Non-Null 类型包装了另一种类型,并表示结果永远不会为 null。这两种类型被称为“包装类型”;非包装类型被称为“基本类型”。包装类型具有底层的“基本类型”,通过不断解包类型直到找到基本类型为止。

最后,通常将复杂结构作为 GraphQL 查询的输入非常有用;Input Object 类型允许模式精确定义客户端在这些查询中期望的数据。

3.1.1标量

正如名称所暗示的那样,标量表示 GraphQL 中的原始值。GraphQL 响应采用分层树的形式;这些树上的叶子是 GraphQL 标量。

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

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

结果强制转换

GraphQL 服务器在准备给定标量类型的字段时,必须遵守标量类型描述的约定,或者通过强制转换值,或者产生错误。

例如,GraphQL 服务器可能正在准备类型为 Int 的标量字段,并遇到浮点数。由于服务器绝不能通过产生非整数来破坏约定,因此服务器应截断小数值,仅产生整数值。如果服务器遇到布尔值 true,则应返回 1。如果服务器遇到字符串,则可能会尝试解析字符串以获得十进制整数值。如果服务器遇到某些无法合理强制转换为 Int 的值,则必须引发字段错误。

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

输入强制转换

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

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

内置标量

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

3.1.1.1Int

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

结果强制转换

GraphQL 服务器应尽可能将非 int 原始值强制转换为 Int,否则它们必须引发字段错误。例如,对于浮点数 1.0 可能返回 1,或者对于字符串 "2" 返回 2

输入强制转换

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

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

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

结果强制转换

GraphQL 服务器应尽可能将非浮点原始值强制转换为 Float,否则它们必须引发字段错误。例如,对于整数 1 可能返回 1.0,或者对于字符串 "2" 返回 2.0

输入强制转换

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

3.1.1.3字符串

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

结果强制转换

GraphQL 服务器应尽可能将非字符串原始值强制转换为 String,否则它们必须引发字段错误。例如,对于布尔值 true 可能返回字符串 "true",或者对于整数 1 返回字符串 "1"

输入强制转换

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

3.1.1.4布尔型

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

结果强制转换

GraphQL 服务器应尽可能将非布尔原始值强制转换为 Boolean,否则它们必须引发字段错误。例如,对于任何非零数字,可能返回 true

输入强制转换

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

3.1.1.5ID

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

结果强制转换

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

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

输入强制转换

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

3.1.2对象

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

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

例如,类型 Person 可以描述为

type Person {
  name: String
  age: Int
  picture: Url
}

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

对象值的查询必须至少选择一个字段。字段的选择将产生一个有序映射,其中包含查询对象的精确子集,并按照它们被查询的顺序排列。只有在对象类型上声明的字段才能在该对象上有效地查询。

例如,选择 Person 的所有字段

{
  name
  age
  picture
}

将产生对象

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

而选择字段的子集

{
  age
  name
}

必须只产生该子集

{
  "age": 30,
  "name": "Mark Zuckerberg"
}

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

例如,Person 类型可能包含 relationship

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

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

{
  name
  relationship
}

但是,此示例是有效的

{
  name
  relationship {
    name
  }
}

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

{
  "name": "Mark Zuckerberg",
  "relationship": {
    "name": "Priscilla Chan"
  }
}

字段排序

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

支持有序映射的响应格式(例如 JSON)必须保持此排序。不支持有序映射的响应格式可以忽略此排序。

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

{
  foo
  ...Frag
  qux
}

fragment Frag on Query {
  bar
  baz
}

产生有序结果

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

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

{
  foo
  ...Ignored
  ...Matching
  bar
}

fragment Ignored on UnknownType {
  qux
  baz
}

fragment Matching on Query {
  bar
  qux
  foo
}

产生有序结果

{
  "foo": 1,
  "bar": 2,
  "qux": 3
}

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

{
  foo @skip(if: true)
  bar
  foo
}

产生有序结果

{
  "bar": 1,
  "foo": 2
}

结果强制转换

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

输入强制转换

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

3.1.2.1对象字段参数

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

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

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

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

此示例查询

{
  name
  picture(size: 600)
}

可能会产生结果

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

对象字段参数的类型可以是任何输入类型。

3.1.2.2对象字段弃用

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

3.1.2.3对象类型验证

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

  1. 对象类型的字段在该对象类型中必须具有唯一的名称;任何两个字段都不能共享相同的名称。
  2. 对象类型必须是它实现的所有接口的超集。
    1. 对于接口中定义的每个字段,对象类型必须包含同名的字段。
      1. 对象字段的类型必须等于或为接口字段的子类型(协变)。
        1. 如果对象字段类型等于(与接口字段类型相同的类型)接口字段类型,则它是有效的子类型。
        2. 如果对象字段类型是对象类型,并且接口字段类型是接口类型或联合类型,并且对象字段类型是接口字段类型的可能类型,则对象字段类型是有效的子类型。
        3. 如果对象字段类型是列表类型,并且接口字段类型也是列表类型,并且对象字段类型的列表项类型是接口字段类型的列表项类型的有效子类型,则对象字段类型是有效的子类型。
        4. 如果对象字段类型是接口字段类型的有效子类型的非空变体,则对象字段类型是有效的子类型。
      2. 对于接口字段中定义的每个参数,对象字段必须包含同名的参数。
        1. 对象字段参数必须接受与接口字段参数相同的类型(不变)。
      3. 对象字段可以包含接口字段中未定义的其他参数,但任何附加参数都不能是必需的。

3.1.3接口

GraphQL 接口表示具名字段及其参数的列表。GraphQL 对象可以实现一个接口,从而保证它们将包含指定的字段。

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

例如,一个接口可以描述一个必需的字段,然后诸如 PersonBusiness 之类的类型可以实现此接口。

interface NamedEntity {
  name: String
}

type Person implements NamedEntity {
  name: String
  age: Int
}

type Business implements NamedEntity {
  name: String
  employeeCount: Int
}

当期望多种对象类型之一,但应保证某些字段时,产生接口的字段很有用。

继续该示例,Contact 可能引用 NamedEntity

type Contact {
  entity: NamedEntity
  phoneNumber: String
  address: String
}

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

{
  entity {
    name
  }
  phoneNumber
}

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

{
  entity {
    name
    age
  }
  phoneNumber
}

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

{
  entity {
    name
    ... on Person {
      age
    }
  },
  phoneNumber
}

结果强制转换

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

输入强制转换

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

3.1.3.1接口类型验证

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

  1. 接口类型的字段在该接口类型内必须具有唯一的名称;任何两个字段都不得共享相同的名称。

3.1.4联合

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

对于接口和对象,只能直接查询类型上定义的那些字段;要查询接口上的其他字段,必须使用类型化的片段。这与联合相同,但是联合不定义任何字段,因此如果不使用类型化的片段,则无法在此类型上查询任何字段。

例如,我们可能有以下类型系统

union SearchResult = Photo | Person

type Person {
  name: String
  age: Int
}

type Photo {
  height: Int
  width: Int
}

type SearchQuery {
  firstSearchResult: SearchResult
}

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

{
  firstSearchResult {
    name
    height
  }
}

相反,查询应为

{
  firstSearchResult {
    ... on Person {
      name
    }
    ... on Photo {
      height
    }
  }
}

结果强制转换

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

输入强制转换

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

3.1.4.1联合类型验证

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

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

3.1.5枚举

GraphQL 枚举是标量类型的一种变体,它表示一组有限的可能值之一。

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

结果强制转换

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

输入强制转换

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

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

3.1.6输入对象

字段可以定义客户端在查询中传递的参数,以配置其行为。这些输入可以是字符串或枚举,但有时它们需要比这更复杂。

上面定义的 Object 类型不适合在此处重用,因为 Object 可以包含表达循环引用或对接口和联合引用的字段,这两种都不适合用作输入参数。因此,输入对象在系统中具有单独的类型。

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

结果强制转换

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

输入强制转换

输入对象的输入应为无序映射,否则应抛出错误。强制转换的结果是无序映射,每个输入字段都有一个条目,其键是输入字段的名称。强制转换映射中条目的值是对输入中具有相同键的条目的值进行输入强制转换的结果;如果输入没有相应的条目,则该值是对 null 进行强制转换的结果。上面的输入强制转换应根据输入字段声明的类型的输入强制转换规则执行。

3.1.7列表

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

结果强制转换

GraphQL 服务器必须返回有序列表作为列表类型的结果。列表中的每个项都必须是项类型的结果强制转换的结果。如果无法进行合理的强制转换,则必须引发字段错误。特别是,如果返回非列表,则强制转换应失败,因为这表明类型系统和实现之间的期望不匹配。

输入强制转换

当预期作为输入时,仅当列表中的每个项都可以被列表的项类型接受时,才接受列表值。

如果作为列表类型的输入传递的值不是列表,则应将其强制转换为大小为一的列表,其中传递的值是列表中的唯一项。这是为了允许接受“可变参数”的输入将其输入类型声明为列表;如果仅传递一个参数(常见情况),则客户端可以直接传递该值,而无需构造列表。

3.1.8非空

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

结果强制转换

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

输入强制转换

如果未提供非空类型的参数,则必须引发查询错误。

如果为非空类型的参数提供了文字值,则使用包装类型的输入强制转换对其进行强制转换。

如果为非空类型的参数提供了变量,则如果运行时提供的值未提供或在提供的表示形式(通常为 JSON)中为 null,则必须引发查询错误。否则,强制转换的值是使用包装类型的输入强制转换的结果。

null 不是 GraphQL 中的值,因此查询不能像这样
{
  field(arg: null)
}

指示参数为 null。相反,参数仅在省略时才为 null

{
  field
}

或者,如果传递了运行时未提供值的可为空类型变量

query withNullableVariable($var: String) {
  field(arg: $var)
}

3.2指令

GraphQL 模式包括执行引擎支持的指令列表。

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

3.2.1@skip

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

在此示例中,仅当为 $someTest 提供 false 值时,才会查询 experimentalField

query myQuery($someTest: Boolean) {
  experimentalField @skip(if: $someTest)
}

3.2.2@include

可以为字段、片段展开和内联片段提供 @include 指令,并允许在执行期间根据 if 参数描述有条件地包含。

在此示例中,仅当为 $someTest 提供 true 值时,才会查询 experimentalField

query myQuery($someTest: Boolean) {
  experimentalField @include(if: $someTest)
}
@skip@include 都不优先于另一个。如果 @skip@include 指令都在同一字段或片段上提供,则仅当 @skip 条件为 false 且 @include 条件为 true 时,才必须查询它。反过来说,如果 @skip 条件为 true @include 条件为 false,则不得查询字段或片段。

3.3初始类型

GraphQL 模式包括类型,指示查询和 mutation 操作从哪里开始。这提供了类型系统的初始入口点。查询类型必须始终提供,并且是对象基本类型。mutation 类型是可选的;如果为 null,则表示系统不支持 mutation。如果提供,则必须是对象基本类型。

查询类型上的字段指示在 GraphQL 查询的顶层有哪些字段可用。例如,像这样的基本 GraphQL 查询

query getMe {
  me
}

当为查询起始类型提供的类型具有名为“me”的字段时,它是有效的。类似地

mutation setName {
  setName(name: "Zuck") {
    newName
  }
}

当为 mutation 起始类型提供的类型不为 null,并且具有名为“setName”且带有名为“name”的字符串参数的字段时,它是有效的。

4内省

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

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

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

type User {
  id: String
  name: String
  birthday: Date
}

查询

{
  __type(name: "User") {
    name
    fields {
      name
      type {
        name
      }
    }
  }
}

将返回

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

4.1通用原则

4.1.1命名约定

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

4.1.2文档

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

4.1.3弃用

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

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

4.1.4类型名称内省

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

当针对接口或联合类型进行查询以识别已返回的可能类型的实际类型时,这最常用。

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

4.2模式内省

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

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

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

GraphQL 模式内省系统的模式

type __Schema {
  types: [__Type!]!
  queryType: __Type!
  mutationType: __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
}

4.2.1__Type 类型

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

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

4.2.2类型种类

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

4.2.2.1标量

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

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

字段

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

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

字段

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

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

字段

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

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

字段

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

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

字段

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

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

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

type Point {
  x: Int
  y: Int
}

字段

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

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

字段

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

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

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

  • kind 必须返回 __TypeKind.NON_NULL
  • ofType:除了非空类型之外的任何类型。
  • 所有其他字段必须返回 null
4.2.2.9组合列表和非空

列表和非空可以组合,表示更复杂的类型。

如果列表的修饰类型是非空,则该列表可能不包含任何 null 项。

如果非空的修饰类型是列表,则不接受 null,但接受空列表。

如果列表的修饰类型是列表,则第一个列表中的每个项都是第二个列表类型的另一个列表。

非空类型不能修饰另一个非空类型。

4.2.3__Field 类型

__Field 类型表示对象或接口类型中的每个字段。

字段

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

4.2.4__InputValue 类型

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

字段

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

4.2.5__Directive 类型

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

字段

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

5验证

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

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

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

类型系统演变

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

示例

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

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

type QueryRoot {
  dog: Dog
}

5.1操作

5.1.1具名操作定义

5.1.1.1操作名称唯一性

形式规范

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

解释性文本

当通过名称引用时,每个具名操作定义在文档中必须是唯一的。

例如,以下文档是有效的

query getDogName {
  dog {
    name
  }
}

query getOwnerName {
  dog {
    owner {
      name
    }
  }
}

而此文档是无效的

query getName {
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}

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

query dogOperation {
  dog {
    name
  }
}

mutation dogOperation {
  mutateDog {
    id
  }
}

5.1.2匿名操作定义

5.1.2.1单独的匿名操作

形式规范

  • operations 为文档中的所有操作定义。
  • anonymous 为文档中的所有匿名操作定义。
  • 如果 operations 是一组,且多于 1 个元素
    • anonymous 必须为空。

解释性文本

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

例如,以下文档是有效的

{
  dog {
    name
  }
}

而此文档是无效的

{
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}

5.2字段

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

形式规范

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

解释性文本

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

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

fragment fieldNotDefined on Dog {
  meowVolume
}

fragment aliasedLyingFieldTargetNotDefined on Dog {
  barkVolume: kawVolume
}

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

例如,以下是有效的

fragment interfaceFieldSelection on Pet {
  name
}

而以下是无效的

fragment definedOnImplementorsButNotInterface on Pet {
  nickname
}

因为联合不定义字段,所以不得直接从联合类型选择集中选择字段,但元字段 __typename 除外。来自联合类型选择集的字段必须仅通过片段间接查询。

例如,以下是有效的

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

但以下是无效的

fragment directFieldSelectionOnUnion on CatOrDog {
  name
  barkVolume
}

5.2.2字段选择合并

形式规范

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

解释性文本

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

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

以下选择正确合并

fragment mergeIdenticalFields on Dog {
  name
  name
}

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

以下无法合并

fragment conflictingBecauseAlias on Dog {
  name: nickname
  name
}

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

例如,以下正确合并

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

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

以下无法正确合并

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

以下字段不会合并在一起,但是两者都不能针对同一对象遇到,因此它们是安全的

fragment 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

fragment conflictingDifferingResponses on Pet {
  ... on Dog {
    someValue: nickname
  }
  ... on Cat {
    someValue: meowVolume
  }
}

5.2.3叶子字段选择

形式规范

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

解释性文本

永远不允许对标量进行字段选择:标量是任何 GraphQL 查询的叶节点。

以下是有效的。

fragment scalarSelection on Dog {
  barkVolume
}

以下是无效的。

fragment scalarSelectionsNotAllowedOnBoolean on Dog {
  barkVolume {
    sinceWhen
  }
}

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

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

extend type QueryRoot {
  human: Human
  pet: Pet
  catOrDog: CatOrDog
}

以下示例是无效的

query directQueryOnObjectWithoutSubFields {
  human
}

query directQueryOnInterfaceWithoutSubFields {
  pet
}

query directQueryOnUnionWithoutSubFields {
  catOrDog
}

5.3参数

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

5.3.1参数名称

形式规范

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

解释性文本

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

例如,以下是有效的

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

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

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

fragment invalidArgName on Dog {
  doesKnowCommand(command: CLEAN_UP_HOUSE)
}

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

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

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

type 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]
}

extend type QueryRoot {
  arguments: Arguments
}

参数中的顺序无关紧要。因此,以下两个示例都是有效的。

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

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

5.3.2参数唯一性

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

形式规范

  • 对于文档中的每个 argument
  • argumentNameargument 的名称。
  • arguments 为参数集中所有名为 argumentName 的参数,该参数集包含 argument
  • arguments 必须是仅包含 argument 的集合。

5.3.3参数值类型正确性

5.3.3.1兼容值

形式规范

  • 对于文档中的每个 argument
  • valueargument 的值
  • 如果 value 不是变量
    • argumentNameargument 的名称。
    • argumentDefinition 为父字段或名为 argumentName 的定义提供的参数定义。
    • typeargumentDefinition 期望的类型。
    • literalArgument 的类型必须可强制转换为 type

解释性文本

字面量值必须与它们所提供的参数定义的类型兼容,具体规则请参见类型系统章节中定义的强制转换规则。

例如,Int 可以被强制转换为 Float。

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

fragment coercedIntIntoFloatArg on Arguments {
  floatArgField(floatArg: 1)
}

不可强制转换的转换,例如字符串到整数。因此,以下示例是无效的。

fragment stringIntoInt on Arguments {
  intArgField(intArg: "3")
}
5.3.3.2必需参数
  • 对于文档中的每个字段或指令。
  • arguments 为字段或指令提供的参数。
  • argumentDefinitions 为该字段或指令的参数定义集。
  • 对于 argumentDefinitions 中的每个 definition
    • typedefinition 的预期类型
    • 如果 type 是 Non‐Null
      • argumentNamedefinition 的名称
      • argumentarguments 中名为 argumentName 的参数
      • argument 必须存在。

解释性文本

参数可以是必需的。如果参数的类型是非空类型,则参数是必需的。如果不是非空类型,则参数是可选的。

例如,以下是有效的

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

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

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

因此,以下查询是有效的

fragment goodBooleanArgDefault on Arguments {
  booleanArgField
}

但这在非空参数上是无效的。

fragment missingRequiredArg on Arguments {
  nonNullBooleanArgField
}

5.4片段

5.4.1片段声明

5.4.1.1片段名称唯一性

形式规范

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

解释性文本

片段定义通过名称在片段扩展中被引用。为了避免歧义,每个片段的名称在文档中必须是唯一的。

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

例如,以下文档是有效的

{
  dog {
    ...fragmentOne
    ...fragmentTwo
  }
}

fragment fragmentOne on Dog {
  name
}

fragment fragmentTwo on Dog {
  owner {
    name
  }
}

而此文档是无效的

{
  dog {
    ...fragmentOne
  }
}

fragment fragmentOne on Dog {
  name
}

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

形式规范

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

解释性文本

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

例如,以下片段是有效的

fragment correctType on Dog {
  name
}

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

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

以下片段无法通过验证

fragment notOnExistingType on NotInSchema {
  name
}

fragment inlineNotExistingType on Dog {
  ... on NotInSchema {
    name
  }
}
5.4.1.3复合类型上的片段

形式规范

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

解释性文本

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

以下片段声明是有效的

fragment fragOnObject on Dog {
  name
}

fragment fragOnInterface on Pet {
  name
}

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

以下片段声明是无效的

fragment fragOnScalar on Int {
  something
}

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

形式规范

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

解释性文本

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

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

fragment nameFragment on Dog { # unused
  name
}

{
  dog {
    name
  }
}

5.4.2片段扩展

字段选择也由将片段相互扩展来决定。目标片段的选择集与引用目标片段的级别的选择集合并。

5.4.2.1片段扩展目标已定义

形式规范

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

解释性文本

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

{
  dog {
    ...undefinedFragment
  }
}
5.4.2.2片段扩展不得形成循环

形式规范

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

DetectCycles(fragmentDefinition, visited) :

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

解释性文本

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

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

{
  dog {
    ...nameFragment
  }
}

fragment nameFragment on Dog {
  name
  ...barkVolumeFragment
}

fragment barkVolumeFragment on Dog {
  barkVolume
  ...nameFragment
}

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

{
  dog {
    name
    barkVolume
    name
    barkVolume
    name
    barkVolume
    name
    # forever...
  }
}

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

{
  dog {
    ...dogFragment
  }
}

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

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

形式规范

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

解释性文本

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

5.4.2.3.1对象范围内的对象扩展

在对象类型的范围内,唯一有效的对象类型片段扩展是应用于范围内同一类型的扩展。

例如:

fragment dogFragment on Dog {
  ... on Dog {
    barkVolume
  }
}

而以下是无效的

fragment catInDogFragmentInvalid on Dog {
  ... on Cat {
    meowVolume
  }
}
5.4.2.3.2对象范围内的抽象扩展

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

例如:

fragment petNameFragment on Pet {
  name
}

fragment interfaceWithinObjectFragment on Dog {
  ...petNameFragment
}

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

同样

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

fragment unionWithObjectFragment on Dog {
  ...catOrDogNameFragment
}

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

5.4.2.3.3抽象范围内的对象扩展

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

例如,以下片段是有效的

fragment petFragment on Pet {
  name
  ... on Dog {
    barkVolume
  }
}

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

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

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

fragment sentientFragment on Sentient {
  ... on Dog {
    barkVolume
  }
}

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

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

5.4.2.3.4抽象范围内的抽象扩展

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

例如

fragment unionWithInterface on Pet {
  ...dogOrHumanFragment
}

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

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

但是

fragment nonIntersectingInterfaces on Pet {
  ...sentientFragment
}

fragment sentientFragment on Sentient {
  name
}

是无效的,因为不存在既实现 Pet 又实现 Sentient 的类型。

5.5

5.5.1输入对象字段唯一性

形式规范

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

解释性文本

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

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

{
  field(arg: { field: true, field: false })
}

5.6指令

5.6.1指令已定义

形式规范

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

解释性文本

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

5.6.2指令位于有效位置

形式规范

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

解释性文本

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

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

query @skip(if: $foo) {
  field
}

5.7变量

5.7.1变量唯一性

形式规范

  • 对于文档中的每个 operation
    • 对于在 operation 上定义的每个 variable
      • variableNamevariable 的名称
      • variablesoperation 上所有名为 variableName 的变量的集合
      • variables 必须是由一个变量组成的集合

解释性文本

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

query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

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

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

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

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

5.7.2变量默认值类型正确

形式规范

  • 对于文档中的每个 operation
  • 对于每个 operation 上的每个 variable
    • variableTypevariable 的类型
    • 如果 variableType 是非空类型,则它不能具有默认值
    • 如果 variable 具有默认值,则它必须与 variableType 的类型相同或能够强制转换为 variableType

解释性文本

操作定义的变量如果其类型不是非空类型,则允许定义默认值。

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

query houseTrainedQuery($atOtherHomes: Boolean = true) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

但是,如果变量被定义为非空类型,则默认值是无法访问的。因此,诸如以下查询将无法通过验证

query houseTrainedQuery($atOtherHomes: Boolean! = true) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

默认值必须与变量的类型兼容。类型必须匹配,或者它们必须可强制转换为该类型。

不匹配的类型将失败,例如在以下示例中

query houseTrainedQuery($atOtherHomes: Boolean = "true") {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

但是,如果类型是可强制转换的,则查询将通过验证。

例如:

query intToFloatQuery($floatVar: Float = 1) {
  arguments {
    floatArgField(floatArg: $floatVar)
  }
}

5.7.3变量是输入类型

形式规范

  • 对于 document 中的每个 operation
  • 对于每个 operation 上的每个 variable
    • variableTypevariable 的类型
    • variableTypeLISTNON_NULL
      • variableTypevariableType 的引用类型
    • variableType 必须是 SCALARENUMINPUT_OBJECT 类型

解释性文本

变量只能是标量、枚举、输入对象或这些类型的列表和非空变体。这些被称为输入类型。对象、联合类型和接口类型不能用作输入。

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

input ComplexInput { name: String, owner: String }

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

以下查询是有效的

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

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

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

以下查询是无效的

query takesCat($cat: Cat) {
  # ...
}

query takesDogBang($dog: Dog!) {
  # ...
}

query takesListOfPet($pets: [Pet]) {
  # ...
}

query takesCatOrDog($catOrDog: CatOrDog) {
  # ...
}

5.7.4所有变量使用已定义

形式规范

  • 对于文档中的每个 operation
    • 对于范围内的每个 variableUsage,变量必须是操作的变量列表。
    • fragments 为该操作传递引用的每个片段
    • 对于 fragments 中的每个 fragment
      • 对于 fragment 范围内的每个 variableUsage,变量必须是 operation 的变量列表。

解释性文本

变量的作用域是基于每个操作的。这意味着在操作上下文中使用的任何变量都必须在该操作的顶层定义

例如:

query variableIsDefined($atOtherHomes: Boolean) {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

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

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

query variableIsNotDefined {
  dog {
    isHousetrained(atOtherHomes: $atOtherHomes)
  }
}

$atOtherHomes 未由操作定义。

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

例如,以下是有效的

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

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

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

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

query variableIsNotDefinedUsedInSingleFragment {
  dog {
    ...isHousetrainedFragment
  }
}

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

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

query variableIsNotDefinedUsedInNestedFragment {
  dog {
    ...outerHousetrainedFragment
  }
}

fragment outerHousetrainedFragment on Dog {
  ...isHousetrainedFragment
}

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

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

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

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

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

但是,以下操作无法通过验证

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

query housetrainedQueryTwoNotDefined {
  dog {
    ...isHousetrainedFragment
  }
}

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

这是因为 housetrainedQueryTwoNotDefined 未定义变量 $atOtherHomes,但该变量被 isHousetrainedFragment 使用,而 isHousetrainedFragment 包含在该操作中。

5.7.5所有变量已使用

形式规范

  • 对于文档中的每个 operation
  • variables 为该 operation 定义的变量
  • variables 中的每个 variable 必须在操作范围本身或该操作传递引用的任何片段中至少使用一次。

解释性文本

操作定义的所有变量都必须在该操作或该操作传递包含的片段中使用。未使用的变量会导致验证错误。

例如,以下操作是无效的

query variableUnused($atOtherHomes: Boolean) {
  dog {
    isHousetrained
  }
}

因为 $atOtherHomes 未被引用。

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

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

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

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

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

query variableNotUsedWithinFragment($atOtherHomes: Boolean) {
  ...isHousetrainedWithoutVariableFragment
}

fragment isHousetrainedWithoutVariableFragment on Dog {
  isHousetrained
}

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

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

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

query queryWithExtraVar($atOtherHomes: Boolean, $extra: Int) {
  dog {
    ...isHousetrainedFragment
  }
}

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

此文档无效,因为 queryWithExtraVar 定义了一个无关的变量。

5.7.6所有变量使用均被允许

形式规范

  • 对于 document 中的每个 operation
  • variableUsages 为传递包含在 operation 中的所有用法
  • 对于 variableUsages 中的每个 variableUsage
    • variableType 为操作中变量定义的类型
    • argumentType 为变量传递到的参数的类型。
    • 如果变量定义定义了默认值,则设 hasDefault 为 true。
    • AreTypesCompatible(argumentType, variableType, hasDefault) 必须为 true
  • AreTypesCompatible(argumentType, variableType, hasDefault)
    • 如果 hasDefault 为 true,则将 variableType 视为非空类型。
    • 如果 argumentTypevariableType 的内部类型不同,则返回 false
    • 如果 argumentTypevariableType 具有不同的列表维度,则返回 false
    • 如果 variableType 的任何列表级别不是非空类型,并且 argument 中对应的级别是非空类型,则类型不兼容。

解释性文本

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

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

类型必须匹配

query intCannotGoIntoBoolean($intArg: Int) {
  arguments {
    booleanArgField(booleanArg: $intArg)
  }
}

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

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

query booleanListCannotGoIntoBoolean($booleanListArg: [Boolean]) {
  arguments {
    booleanArgField(booleanArg: $booleanListArg)
  }
}

空值性也必须受到尊重。通常,可为空的变量不能传递给非空参数。

query booleanArgQuery($booleanArg: Boolean) {
  arguments {
    nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)
  }
}

一个值得注意的例外是提供默认参数时。实际上,它们被视为非空值。

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

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

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

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

query listToNonNullList($booleanList: [Boolean]) {
  arguments {
    nonNullBooleanListField(nonNullBooleanListArg: $booleanList)
  }
}

这将导致验证失败,因为 [T] 不能传递给 [T]!

同样,[T] 不能传递给 [T!]

6执行

本节介绍 GraphQL 如何从请求生成响应。

6.1评估请求

要评估请求,执行器必须具有已解析的 Document(如本规范的“查询语言”部分所定义)以及要运行的选定操作名称(如果文档定义了多个操作)。

执行器应在 Document 中查找具有给定操作名称的 Operation。如果不存在此类操作,则执行器应抛出错误。如果找到该操作,则评估请求的结果应是根据“评估操作”部分评估该操作的结果。

6.2操作验证

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

通常,验证在请求的上下文中立即在执行之前执行,但是,如果已知完全相同的请求之前已通过验证,则 GraphQL 服务可能会在不显式验证的情况下执行请求。例如:请求可以在开发期间进行验证,前提是它之后没有更改,或者服务可以验证请求一次并记住结果,以避免将来再次验证相同的请求。

GraphQL 服务应仅执行在某个时刻已知没有验证错误并且自那时以来没有更改的请求。

6.3强制变量值

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

6.4评估操作

类型系统(如本规范的“类型系统”部分所述)必须提供“查询根”和“变更根”对象。

如果操作是变更操作,则操作的结果是在“变更根”对象上评估变更操作的顶级选择集的结果。此选择集应串行评估。

如果操作是查询操作,则操作的结果是在“查询根”对象上评估查询操作的顶级选择集的结果。

6.5评估选择集

要评估选择集,执行器需要知道评估该集的对象以及它是否正在串行评估。

如果在 null 对象上评估选择集,则评估选择集的结果为 null

否则,选择集将转换为分组字段集;分组字段集中的每个条目都是共享 responseKey 的字段列表。

通过调用 CollectFields 并将 visitedFragments 初始化为空列表,将选择集转换为分组字段集。

CollectFields(objectType, selectionSet, visitedFragments)
  1. groupedFields 初始化为空的有序列表列表。
  2. 对于 selectionSet 中的每个 selection
    1. 如果 selection 提供了指令 @skip,则设 skipDirective 为该指令。
      1. 如果 skipDirectiveif 参数为 true,则继续处理 selectionSet 中的下一个 selection
    2. 如果 selection 提供了指令 @include,则设 includeDirective 为该指令。
      1. 如果 includeDirectiveif 参数为 false,则继续处理 selectionSet 中的下一个 selection
    3. 如果 selectionField
      1. responseKeyselection 的响应键。
      2. groupForResponseKeygroupedFieldsresponseKey 的列表;如果不存在此类列表,则创建一个空列表。
      3. selection 附加到 groupForResponseKey
    4. 如果 selectionFragmentSpread
      1. fragmentSpreadNameselection 的名称。
      2. 如果 fragmentSpreadNamevisitedFragments 中,则继续处理 selectionSet 中的下一个 selection
      3. fragmentSpreadName 添加到 visitedFragments
      4. fragment 为当前文档中名称为 fragmentSpreadName 的片段。
      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, visitedFragments) 的结果。
      5. 对于 fragmentGroupedFieldSet 中的每个 fragmentGroup
        1. responseKeyfragmentGroup 中所有字段共享的响应键
        2. groupForResponseKeygroupedFieldsresponseKey 的列表;如果不存在此类列表,则创建一个空列表。
        3. fragmentGroup 中的所有项附加到 groupForResponseKey
  3. 返回 groupedFields
doesFragmentTypeApply(objectType, fragmentType)
  1. 如果 fragmentType 是对象类型
    1. 如果 objectTypefragmentType 是相同类型,则返回 true,否则返回 false
  2. 如果 fragmentType 是接口类型
    1. 如果 objectTypefragmentType 的实现,则返回 true,否则返回 false
  3. 如果 fragmentType 是联合类型
    1. 如果 objectTypefragmentType 的可能类型,则返回 true,否则返回 false

评估选择集的结果是评估相应的分组字段集的结果。如果选择集正在串行评估,则相应的分组字段集应串行评估,否则应正常评估。

6.6评估分组字段集

评估分组字段集的结果将是一个有序映射。对于分组字段集中的每个项,都会向结果有序映射添加一个条目,其中键是该条目的所有字段共享的响应键,值是评估这些字段的结果。

6.6.1字段条目

分组字段集中的每个条目都可能在结果映射中创建一个条目。结果映射中的该条目是对分组字段集中的相应条目调用 GetFieldEntry 的结果。GetFieldEntry 可以返回 null,这表示此条目在结果映射中不应存在。请注意,这与返回一个带有字符串键和空值的条目是不同的,后者表示应为该键添加一个条目,并且其值应为空。

GetFieldEntry 假定存在两个未在本规范的此部分中定义的函数。预计类型系统会提供这些方法

  • ResolveFieldOnObject,它接受对象类型、字段和对象,并返回在该对象上解析该字段的结果。
  • GetFieldTypeFromObjectType,它接受对象类型和字段,并返回该字段在对象类型上的类型;如果该字段在对象类型上无效,则返回 null
GetFieldEntry(objectType, object, fields)
  1. firstField 为有序列表 fields 中的第一个条目。请注意,fields 永远不为空,因为如果没有任何字段,则分组字段集中的条目将不存在。
  2. responseKeyfirstField 的响应键。
  3. fieldType 为调用 GetFieldTypeFromObjectType(objectType, firstField) 的结果。
  4. 如果 fieldTypenull,则返回 null,表示结果映射中不存在任何条目。
  5. resolvedObjectResolveFieldOnObject(objectType, object, fieldEntry)
  6. 如果 resolvedObjectnull,则返回 tuple(responseKey, null),表示结果映射中存在一个条目,其值为 null
  7. subSelectionSet 为调用 MergeSelectionSets(fields) 的结果。
  8. responseValue 为调用 CompleteValue(fieldType, resolvedObject, subSelectionSet) 的结果。
  9. 返回 tuple(responseKey, responseValue)
GetFieldTypeFromObjectType(objectType, firstField)
  1. 调用类型系统提供的方法,以确定给定对象类型上的字段类型。
ResolveFieldOnObject(objectType, object, firstField)
  1. 调用类型系统提供的方法,以确定给定对象上字段的解析结果。
MergeSelectionSets(fields)
  1. selectionSet 为一个空列表。
  2. 对于 fields 中的每个 field
    1. fieldSelectionSetfield 的选择集。
    2. 如果 fieldSelectionSet 为 null 或空,则继续到下一个字段。
    3. fieldSelectionSet 中的所有选择项追加到 selectionSet
  3. 返回 selectionSet
CompleteValue(fieldType, result, subSelectionSet)
  1. 如果 fieldType 是一个 Non‐Null 类型
    1. innerTypefieldType 的内部类型。
    2. completedResult 为调用 CompleteValue(innerType, result) 的结果。
    3. 如果 completedResultnull,则抛出一个字段错误。
    4. 返回 completedResult
  2. 如果 resultnull 或类似于 null 的值,例如 undefinedNaN,则返回 null
  3. 如果 fieldType 是一个 List 类型
    1. 如果 result 不是值的集合,则抛出一个字段错误。
    2. innerTypefieldType 的内部类型。
    3. 返回一个列表,其中每个条目都是调用 CompleteValue(innerType, resultItem) 的结果,其中 resultItemresult 中的每个条目。
  4. 如果 fieldType 是一个 Scalar 或 Enum 类型
    1. 返回“强制转换” result 的结果,确保它是 fieldType 的合法值,否则返回 null
  5. 如果 fieldType 是一个 Object、Interface 或 Union 类型
    1. 如果 fieldType 是一个 Object 类型。
      1. objectTypefieldType
    2. 否则,如果 fieldType 是一个 Interface 或 Union 类型。
      1. objectType 为 ResolveAbstractType(fieldType, result)。
    3. 返回在 objectType 上正常评估 subSelectionSet 的结果。
ResolveAbstractType(abstractType, objectValue)
  1. 返回调用类型系统提供的内部方法的结果,以确定给定值 objectValueabstractType 的 Object 类型。

6.6.2正常评估

当在没有串行执行顺序要求的情况下评估分组字段集时,执行器可以按其选择的任何顺序确定结果映射中的条目。由于除了顶级 mutation 字段之外的字段的解析始终是无副作用且幂等的,因此执行顺序不得影响结果,因此服务器可以自由地按其认为最佳的任何顺序评估字段条目。

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

{
  birthday {
    month
  }
  address {
    street
  }
}

有效的 GraphQL 执行器可以按其选择的任何顺序解析这四个字段。

6.6.3串行执行

请注意,根据以上各节,执行器将以串行执行顺序运行的唯一情况是在 mutation 操作的顶级选择集及其对应的分组字段集上。

当串行评估分组字段集时,执行器必须按照分组字段集中提供的顺序考虑分组字段集中的每个条目。它必须在继续处理分组字段集中的下一个条目之前,确定每个条目在结果映射中的对应条目并完成操作。

例如,给定以下要串行评估的选择集

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

执行器必须串行地执行以下操作

  • changeBirthday 运行 getFieldEntry,它在 CompleteValue 期间将正常评估 { month } 子选择集。
  • changeAddress 运行 getFieldEntry,它在 CompleteValue 期间将正常评估 { street } 子选择集。

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

{
  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 } 子选择集

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

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

6.6.4可空性

如果解析字段的结果为 null(无论是由于解析字段的函数返回 null 还是由于发生错误),并且该字段的类型为 Non-Null 类型,则会抛出一个字段错误。

如果字段为 null 是因为已添加到响应中的 "errors" 列表中的错误,则 "errors" 列表不得进一步受到影响。

如果字段解析函数返回 null,则必须将生成的字段错误添加到响应中的 "errors" 列表。

6.6.5错误处理

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

但是,如果该字段的类型为 Non-Null 类型,由于字段不能为 null,因此错误会传播到父字段进行处理。

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

7响应

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

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

7.1序列化格式

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

仅支持有序映射的序列化格式(例如 JSON)必须保留查询执行定义的顺序。仅支持无序映射的序列化格式可以省略此排序信息。

序列化格式可以支持以下原语,但是,字符串可以用作这些原语的替代品。

7.1.1JSON 序列化

JSON 是 GraphQL 的首选序列化格式,尽管如上所述,GraphQL 不要求特定的序列化格式。为了保持一致性和易于表示,整个规范中的响应示例均以 JSON 给出。特别是,在我们的 JSON 示例中,我们将使用以下 JSON 概念表示原语

GraphQL 值JSON 值
映射对象
列表数组
Nullnull
字符串字符串
布尔值truefalse
整数数字
浮点数数字
枚举值字符串

7.2响应格式

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

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

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

响应映射还可以包含一个键为 extensions 的条目。如果设置了此条目,则其值必须是映射。此条目保留供实现者随意扩展协议,因此对其内容没有其他限制。

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

7.2.1数据

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

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

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

7.2.2错误

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

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

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

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

GraphQL 服务器可以根据需要为错误提供其他条目,以生成更有帮助或机器可读的错误,但是,规范的未来版本可能会描述错误的附加条目。

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

如果响应中的 data 条目不为 null,则响应中的 errors 条目可能包含执行期间发生的任何错误。如果在执行期间发生错误,则应包含这些错误。

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算法

本规范描述了静态和运行时语义使用的一些算法,它们以类似函数的语法以及要采取的算法步骤列表的形式定义。

例如,这描述了在给定运行时 objectType 和片段的 fragmentType 的情况下,是否应将片段扩展到位

doesFragmentTypeApply(objectType, fragmentType)
  1. 如果 fragmentType 是对象类型
    1. 如果 objectTypefragmentType 是相同类型,则返回 true,否则返回 false
  2. 如果 fragmentType 是接口类型
    1. 如果 objectTypefragmentType 的实现,则返回 true,否则返回 false
  3. 如果 fragmentType 是联合类型
    1. 如果 objectTypefragmentType 的可能类型,则返回 true,否则返回 false

B附录:文法摘要

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

B.1忽略的标记

UnicodeBOM
字节顺序标记 (U+FEFF)
WhiteSpace
水平制表符 (U+0009)
空格 (U+0020)
LineTerminator
换行符 (U+000A)
回车符 (U+000D)换行符 (U+000A)
回车符 (U+000D)换行符 (U+000A)

B.2词法标记

Punctuator
!$()...:=@[]{|}
Name
/[_A-Za-z][_0-9A-Za-z]*/
数字
0123456789
转义的 Unicode
/[0-9A-Fa-f]{4}/
转义字符
"\/bfnrt

B.3查询文档

OperationType
querymutation
布尔值
truefalse
枚举值
Nametruefalsenull
列表值常量
[]
[Value常量列表]
对象值常量
{}
{ObjectField常量列表}

§索引

  1. Alias
  2. Argument
  3. Arguments
  4. 布尔值
  5. CollectFields
  6. Comma
  7. Comment
  8. CommentChar
  9. CompleteValue
  10. 默认值
  11. Definition
  12. 数字
  13. 指令
  14. 指令
  15. Document
  16. 枚举值
  17. 转义字符
  18. 转义的 Unicode
  19. 指数指示符
  20. 指数部分
  21. Field
  22. FieldsInSetCanMerge
  23. FloatValue
  24. 小数部分
  25. FragmentDefinition
  26. FragmentName
  27. FragmentSpread
  28. GetFieldEntry
  29. GetFieldTypeFromObjectType
  30. GetPossibleTypes
  31. Ignored
  32. InlineFragment
  33. IntValue
  34. 整数部分
  35. LineTerminator
  36. 列表类型
  37. 列表值
  38. MergeSelectionSets
  39. Name
  40. 命名类型
  41. 负号
  42. 非空类型
  43. 非零数字
  44. ObjectField
  45. 对象值
  46. OperationDefinition
  47. OperationType
  48. Punctuator
  49. ResolveAbstractType
  50. ResolveFieldOnObject
  51. SameResponseShape
  52. Selection
  53. SelectionSet
  54. 符号
  55. SourceCharacter
  56. 字符串字符
  57. StringValue
  58. Token
  59. 类型
  60. TypeCondition
  61. UnicodeBOM
  62. Value
  63. 变量
  64. 变量定义
  65. 变量定义
  66. WhiteSpace
  67. doesFragmentTypeApply
  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.1Int 值
      2. 2.9.2Float 值
      3. 2.9.3布尔值
      4. 2.9.4字符串值
      5. 2.9.5枚举值
      6. 2.9.6列表值
      7. 2.9.7输入对象值
    10. 2.10变量
    11. 2.11输入类型
    12. 2.12指令
  3. 3类型系统
    1. 3.1类型
      1. 3.1.1标量
        1. 3.1.1.1Int
        2. 3.1.1.2Float
        3. 3.1.1.3String
        4. 3.1.1.4Boolean
        5. 3.1.1.5ID
      2. 3.1.2对象
        1. 3.1.2.1对象字段参数
        2. 3.1.2.2对象字段弃用
        3. 3.1.2.3对象类型验证
      3. 3.1.3接口
        1. 3.1.3.1接口类型验证
      4. 3.1.4联合
        1. 3.1.4.1联合类型验证
      5. 3.1.5枚举
      6. 3.1.6输入对象
      7. 3.1.7列表
      8. 3.1.8非空
    2. 3.2指令
      1. 3.2.1@skip
      2. 3.2.2@include
    3. 3.3初始类型
  4. 4内省
    1. 4.1一般原则
      1. 4.1.1命名约定
      2. 4.1.2文档
      3. 4.1.3弃用
      4. 4.1.4类型名称内省
    2. 4.2模式内省
      1. 4.2.1__Type 类型
      2. 4.2.2类型种类
        1. 4.2.2.1标量
        2. 4.2.2.2对象
        3. 4.2.2.3联合
        4. 4.2.2.4接口
        5. 4.2.2.5枚举
        6. 4.2.2.6输入对象
        7. 4.2.2.7列表
        8. 4.2.2.8非空
        9. 4.2.2.9组合列表和非空
      3. 4.2.3__Field 类型
      4. 4.2.4__InputValue 类型
      5. 4.2.5__Directive 类型
  5. 5验证
    1. 5.1操作
      1. 5.1.1命名操作定义
        1. 5.1.1.1操作名称唯一性
      2. 5.1.2匿名操作定义
        1. 5.1.2.1单独的匿名操作
    2. 5.2字段
      1. 5.2.1对象、接口和联合类型上的字段选择
      2. 5.2.2字段选择合并
      3. 5.2.3叶子字段选择
    3. 5.3参数
      1. 5.3.1参数名称
      2. 5.3.2参数唯一性
      3. 5.3.3参数值类型正确性
        1. 5.3.3.1兼容值
        2. 5.3.3.2必需参数
    4. 5.4片段
      1. 5.4.1片段声明
        1. 5.4.1.1片段名称唯一性
        2. 5.4.1.2片段扩展类型存在性
        3. 5.4.1.3复合类型上的片段
        4. 5.4.1.4片段必须被使用
      2. 5.4.2片段扩展
        1. 5.4.2.1片段扩展目标已定义
        2. 5.4.2.2片段扩展不得形成循环
        3. 5.4.2.3片段扩展是可能的
          1. 5.4.2.3.1对象范围内的对象扩展
          2. 5.4.2.3.2对象范围内的抽象扩展
          3. 5.4.2.3.3抽象范围内的对象扩展
          4. 5.4.2.3.4抽象范围内的抽象扩展
    5. 5.5
      1. 5.5.1输入对象字段唯一性
    6. 5.6指令
      1. 5.6.1指令已定义
      2. 5.6.2指令位于有效位置
    7. 5.7变量
      1. 5.7.1变量唯一性
      2. 5.7.2变量默认值类型正确
      3. 5.7.3变量是输入类型
      4. 5.7.4所有变量使用已定义
      5. 5.7.5所有变量已使用
      6. 5.7.6允许所有变量用法
  6. 6执行
    1. 6.1评估请求
    2. 6.2操作验证
    3. 6.3强制变量值
    4. 6.4评估操作
    5. 6.5评估选择集
    6. 6.6评估分组字段集
      1. 6.6.1字段条目
      2. 6.6.2正常评估
      3. 6.6.3串行执行
      4. 6.6.4可为空性
      5. 6.6.5错误处理
  7. 7响应
    1. 7.1序列化格式
      1. 7.1.1JSON 序列化
    2. 7.2响应格式
      1. 7.2.1数据
      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. §索引