GraphQL

草案 – 2016十月

引言

这是一份GraphQL规范的征求意见稿草案,其中GraphQL是Facebook于2012年创造的一种用于描述CS应用中数据模型的能力和要求的语言。本标准的开发起始于2015年,GraphQL是一种在还在演进的未完成的新语言,其重大功能增强也会在本规范的未来版本中体现。

版权须知 (译者案:BSD开源协议)

版权所有 © 2015‐2016,Facebook股份有限公司。

在遵守以下条件的前提下,可再发布软件或以源代码及二进制形式使用软件,包括进行修改或不进行修改:

本软件为版权所有人和贡献者“按现状”为根据提供,不提供任何明确或暗示的保证,包括但不限于本软件针对特定用途的可售性及适用性的暗示保证。在任何情况下,版权所有人或其贡献者均不对因使用本软件而以任何方式产生的任何直接、间接、偶然、特殊、典型或因此而生的损失(包括但不限于采购替换产品或服务;使用价值、数据或利润的损失;或业务中断)而根据任何责任理论,包括合同、严格责任或侵权行为(包括疏忽或其他)承担任何责任,即使在已经提醒可能发生此类损失的情况下。

Copyright notice

Copyright (c) 2015‐2016, Facebook, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Contents
  1. 1Overview/概览
  2. 2Language/语言
    1. 2.1Source Text/源文本
      1. 2.1.1Unicode/统一码
      2. 2.1.2White Space/空白符
      3. 2.1.3Line Terminators/行终止符
      4. 2.1.4Comments/注释
      5. 2.1.5Insignificant Commas/无语义逗号
      6. 2.1.6Lexical Tokens/词法记号
      7. 2.1.7Ignored Tokens/无语义记号
      8. 2.1.8Punctuators/标点
      9. 2.1.9Names/命名
    2. 2.2Query Document/查询文档
    3. 2.3Operations/操作
    4. 2.4Selection Sets/选择集合
    5. 2.5Fields/字段
    6. 2.6Arguments/参数
    7. 2.7Field Alias/字段别名
    8. 2.8Fragments/片段
      1. 2.8.1Type Conditions/类型条件
      2. 2.8.2Inline Fragments/内联片段
    9. 2.9Input Values/输入值
      1. 2.9.1Int Value/整数值
      2. 2.9.2Float Value/浮点值
      3. 2.9.3Boolean Value/布尔值
      4. 2.9.4String Value/字符串值
      5. 2.9.5Null Value/空值
      6. 2.9.6Enum Value/枚举值
      7. 2.9.7List Value/列表值
      8. 2.9.8Input Object Values/输入型对象值
    10. 2.10Variables/变量
    11. 2.11Input Types/输入类型
    12. 2.12Directives/指令
  3. 3Type System/类型系统
    1. 3.1Types/类型
      1. 3.1.1Scalars/标量
        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.2Objects/对象
        1. 3.1.2.1Object Field Arguments/对象字段参数
        2. 3.1.2.2Object Field deprecation/对象字段弃用
        3. 3.1.2.3Object type validation/对象类型验证
      3. 3.1.3Interfaces/接口
        1. 3.1.3.1Interface type validation/接口类型验证
      4. 3.1.4Unions/联合
        1. 3.1.4.1Union type validation/联合类型验证
      5. 3.1.5Enums/枚举型
      6. 3.1.6Input Objects/输入对象
        1. 3.1.6.1Input Object type validation/输入对象类型验证
      7. 3.1.7Lists/列表型
      8. 3.1.8Non-Null/非空型
    2. 3.2Directives/指令
      1. 3.2.1@skip
      2. 3.2.2@include
    3. 3.3Initial types/初始类型
  4. 4Introspection/内省
    1. 4.1General Principles/基本原则
      1. 4.1.1Naming conventions/命名约定
      2. 4.1.2Documentation/文档
      3. 4.1.3Deprecation/弃用
      4. 4.1.4Type Name Introspection/类型命名内省
    2. 4.2Schema Introspection/Schema内省
      1. 4.2.1The __Type Type/__Type类型
      2. 4.2.2Type Kinds/类型种类
        1. 4.2.2.1Scalar/标量
        2. 4.2.2.2Object/对象
        3. 4.2.2.3Union/联合
        4. 4.2.2.4Interface/接口
        5. 4.2.2.5Enum/枚举型
        6. 4.2.2.6Input Object/输入对象
        7. 4.2.2.7List/列表
        8. 4.2.2.8Non-Null/非空
        9. 4.2.2.9Combining List and Non-Null/列表和非空的组合
      3. 4.2.3The __Field Type/__Field类型
      4. 4.2.4The __InputValue Type/__InputValue类型
      5. 4.2.5The __EnumValue Type/__EnumValue类型
      6. 4.2.6The __Directive Type/__Directive类型
  5. 5Validation/验证
    1. 5.1Operations/操作
      1. 5.1.1Named Operation Definitions/具名操作定义
        1. 5.1.1.1Operation Name Uniqueness/操作名唯一性
      2. 5.1.2Anonymous Operation Definitions/匿名操作定义
        1. 5.1.2.1Lone Anonymous Operation/单独匿名操作
      3. 5.1.3Subscription Operation Definitions/订阅操作定义
        1. 5.1.3.1Single root field/单个根级字段
    2. 5.2Fields/字段
      1. 5.2.1Field Selections on Objects, Interfaces, and Unions Types/对象、接口和联合上的字段选择
      2. 5.2.2Field Selection Merging/字段选择合并
      3. 5.2.3Leaf Field Selections/叶子节点选择
    3. 5.3Arguments/参数
      1. 5.3.1Argument Names/参数名
      2. 5.3.2Argument Uniqueness/参数唯一性
      3. 5.3.3Argument Values Type Correctness/参数值类型正确性
        1. 5.3.3.1Compatible Values/兼容值
        2. 5.3.3.2Required Non-Null Arguments/必要非空参数
    4. 5.4Fragments/片段
      1. 5.4.1Fragment Declarations/片段声明
        1. 5.4.1.1Fragment Name Uniqueness/片段名唯一性
        2. 5.4.1.2Fragment Spread Type Existence/片段解构类型存在性
        3. 5.4.1.3Fragments On Composite Types/组合类型上的片段
        4. 5.4.1.4Fragments Must Be Used/必须使用的片段
      2. 5.4.2Fragment Spreads/片段解构
        1. 5.4.2.1Fragment spread target defined/片段解构目标必须预先定义
        2. 5.4.2.2Fragment spreads must not form cycles/片段解构不可造成循环
        3. 5.4.2.3Fragment spread is possible/片段结构必须可行
          1. 5.4.2.3.1Object Spreads In Object Scope/对象范围内的对象解构
          2. 5.4.2.3.2Abstract Spreads in Object Scope/对象范围内的抽象解构
          3. 5.4.2.3.3Object Spreads In Abstract Scope/抽象范围内的对象解构
          4. 5.4.2.3.4Abstract Spreads in Abstract Scope/抽象范围内的抽象解构
    5. 5.5Values/值
      1. 5.5.1Input Object Field Uniqueness/输入对象字段唯一性
    6. 5.6Directives/指令
      1. 5.6.1Directives Are Defined/指令必须预先定义
      2. 5.6.2Directives Are In Valid Locations/指令必须在有效位置
      3. 5.6.3Directives Are Unique Per Location/每个位置的指令都必须唯一
    7. 5.7Variables/变量
      1. 5.7.1Variable Uniqueness/变量唯一性
      2. 5.7.2Variable Default Values Are Correctly Typed/变量默认值必须是正确的类型
      3. 5.7.3Variables Are Input Types/变量必须是输入类型
      4. 5.7.4All Variable Uses Defined/所有变量的使用必须预先定义
      5. 5.7.5All Variables Used/所有变量都必须被使用
      6. 5.7.6All Variable Usages are Allowed/所有变量都允许使用
  6. 6Execution/执行
    1. 6.1Executing Requests/执行请求
      1. 6.1.1Validating Requests/验证请求
      2. 6.1.2Coercing Variable Values/转换变量值
    2. 6.2Executing Operations/执行操作
      1. 6.2.1Query/查询
      2. 6.2.2Mutation/更改
      3. 6.2.3Subscription/订阅
        1. 6.2.3.1Source Stream/源流
        2. 6.2.3.2Response Stream/响应流
        3. 6.2.3.3Unsubscribe/退订
    3. 6.3Executing Selection Sets/执行选择集
      1. 6.3.1Normal and Serial Execution/正常序列执行
      2. 6.3.2Field Collection/字段集合
    4. 6.4Executing Fields/执行字段
      1. 6.4.1Coercing Field Arguments/转换字段参数
      2. 6.4.2Value Resolution/值解析
      3. 6.4.3Value Completion/值完成
      4. 6.4.4Errors and Non-Nullability/错误与非空
  7. 7Response/响应
    1. 7.1Serialization Format/序列化格式
      1. 7.1.1JSON Serialization/JSON序列化
    2. 7.2Response Format/响应格式
      1. 7.2.1Data/数据
      2. 7.2.2Errors/错误
  8. AAppendix: Notation Conventions/A.附录:符号约定
    1. A.1Context-Free Grammar/无上下文的语法
    2. A.2Lexical and Syntactical Grammar/词句上的语法
    3. A.3Grammar Notation/语法符号
    4. A.4Grammar Semantics/语法语义
    5. A.5Algorithms/算法
  9. BAppendix: Grammar Summary/A.附录:语法总结
    1. B.1Ignored Tokens/忽略符号
    2. B.2Lexical Tokens/词法符号
    3. B.3Query Document/查询文档

1Overview/概览

GraphQL语言致力于提供一种直观的弹性语法系统,用以描述客户端程序设计时的数据需求以及数据交互行为。

例如,下面这个GraphQL请求将会从Facebook的GraphQL实现中获取id为4的用户的名字。

{
  user(id: 4) {
    name
  }
}

将会产生如下JSON数据:

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

GraphQL并不能像编程语言一样执行任意计算,但能针对具有本规范所述能力的应用服务器进行查询。GraphQL的实现并不要求应用服务器使用特定的编程语言或者存储系统,而是只需要应用服务器将他们的能力映射为符合GraphQL编码原理的统一语言和类型系统。这样既为产品开发提供了友好的统一接口,又为工具建设提供了强大平台。

GraphQL有若干设计原则:

基于这些原则,GraphQL在建造客户端应用的时候就成了强大的生产环境。产品开发者和设计师在高质量工具的支撑下,无需阅读大量文档,只需一点或者无需正式训练就能根据GraphQL服务器建造客户端。当然为了完成这个目的,这些服务器和工具的建造者也必不可少。

下文的正式规范即作为这些建造者的参考指南,其描述了语言以及语法,接受查询的类型系统以及内省系统,执行引擎以及验证引擎的算法。本规范的目标是为GraphQL工具、客户端库、服务端实现提供了生态所需的基础和框架,无论组织还是平台,我们都希望和社区通力合作以完成上述目标。

2Language/语言

客户端使用GraphQL查询语言来请求GraphQL服务,我们称这些请求为文档,文档包含操作(queries/查询,mutations/更改,和subscriptions订阅)和片段(用于组合重用的共有单元)。

GraphQL文档的语法中,将终端符号视为记号,即独立词法单元。这些记号以词法方式定义,满足源字符模式(用::定义)。译者案:翻译中使用→表示

2.1Source Text/源文本

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

源字符 → /[\u0009\u000A\u000D\u0020-\uFFFF]/

GraphQL文档可表示为一序列的Unicode(统一码)字符,然而,除了少许例外,大部分GraphQL文档都是用ASCII非控制字符来表示,以便于尽量兼容已有工具、语言和序列化格式,并尽可能避免在编辑器和源代码管理的显示问题。

2.1.1Unicode/统一码

UnicodeBOM
Byte Order Mark (U+FEFF)

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

GraphQL的StringValue(字符串值)和Comment(备注)中可以使用非ASCII的Unicode字符。

BOM,又称字节顺序标记,是一个特殊的Unicode字符,它出现在文件的头部,以便程序用以确认当前文本流是Unicode编码,使用了大端还是小端,该用哪一种Unicode编码来转义。

2.1.2White Space/空白符

WhiteSpace
Horizontal Tab (U+0009)
Space (U+0020)

空白符 →

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

空白符出现在记号的前后,作为记号分隔使用,用于提升源文本的易读性。GraphQL查询文档的空白符可能出现在StringComment记号中,但并不会显著影响其语义。

GraphQL不采用Unicode的Zs类别字符作为空白符,以避免编辑器和源代码管理工具的误读。

2.1.3Line Terminators/行终止符

LineTerminator
New Line (U+000A)
Carriage Return (U+000D)New Line (U+000A)
Carriage Return (U+000D)New Line (U+000A)

行终止符 →

  • ”新行 (U+000A)”
  • ”回车 (U+000D)”
  • ”回车 (U+000D)” “新行 (U+000A)”

跟空白符类似,行终止符也是用于提升源文本的易读性,可出现在记号的前后,对GraphQL查询文档的语义无显著影响。行终止符不应该出现在其他记号之间。

语法报错时的行号应该由之前LineTerminator(行终止符)的总数来生成。

2.1.4Comments/注释

注释 → # 注释字符

注释字符 → 源字符,非行终止符

GraphQL查询文档可以包含以#开头的单行注释。

注释可使用除了LineTerminator(行终止符)以外的任意Unicode字符(码点)。所以可以说,注释包含了以#开头的除行终止符以外的所有字符。

注释与空白符类似,出现在任意记号后面、行终止符前面,对GraphQL查询文档的语义无显著影响。

2.1.5Insignificant Commas/无语义逗号

逗号 → ,

与空白符和行终止符类似,逗号(,)也是提升源文本的易读性、分隔词法记号,对GraphQL查询文档的语法语义上也无显著影响.

无语义逗号保证了无论有误逗号,都不影响文档的解读,在其他语言中这就可能是一个用户错误。为了源代码易读性和可维护性,列表会使用行终止符和末尾逗号作为分隔符,无语义逗号也有这个样式上的用途。

2.1.6Lexical Tokens/词法记号

记号 →

  • 标点
  • 命名
  • 整数值
  • 浮点值
  • 字符串型值

一个GraphQL文档由多种独立记号组成,本规范中使用源文本Unicode字符模式来定义这些记号。

在后文GraphQL查询文档句法中,记号将作为终结符使用。

2.1.7Ignored Tokens/无语义记号

无语义记号 →

  • Unicode字节顺序标记
  • 空白符
  • 行终止符
  • 注释
  • 逗号

在词法记号的前后可能会出现不定量的无语义记号,包括WhiteSpace(空白符)和Comment备注。源文档的无语义区域都是无语义影响的,但是无语义字符可能以一种有影响的方式出现在源字符词法记号之间,譬如String(字符串)可能包含空白字符。

在解析给定记号时,所有字符都不能被忽略,譬如FloatValue(浮点值)的字符中不允许出现空白符。 (译者案:本段无力,求大神指点)

2.1.8Punctuators/标点

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

标点 → ! $ ( ) ... : = @ [ ] { | } 之一

GraphQL文档使用了标点符号以描述结构,GraphQL是一种数据描述语言,而非编程语言,因此GraphQL缺乏用于描述数学表达式的标点符号。

2.1.9Names/命名

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

命名 → /[_A‐Za‐z][_0‐9A‐Za‐z]/*

GraphQL查询文档全是命名的产物:operations(操作),fields(字段),arguments(参数),directives(指令), fragments(片段)和variables(变量)。所有命名必须遵循以下格式:

GraphQL的命名是大小写敏感的,也就是说nameName,和NAME是不同的名字,下划线也具有影响,other_nameothername也是两个不同的名字。

GraphQL的命名限制在上述ASCII子集内,以便支持尽可能多的其他系统。

2.2Query Document/查询文档

文档 → 定义

定义 →

GraphQL查询文档描述了GraphQL服务收到的完整文件或者请求字符串。一个文档可以包含多个操作和片段的定义。一个查询文档只有包含操作时,服务器才能执行。但是无操作的文档也能被解析和验证,以让客户端提供单个跨文档请求。

如果一个文档只有一个操作,那这个操作可以不带命名或者以简写,省略掉query关键字和操作名。否则当一个查询文档包含多个操作时,每个操作都必须命名,并且在提交给服务器的时候,也要指明需要执行的目标操作。

2.3Operations/操作

OperationType
querymutationsubscription

操作定义 →

操作类型 →

每一个操作都以一个可选操作名和选择集合表示,例如这个mutation(更改)操作,对一个story点赞(like),然后获取了被点赞次数:

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

Query shorthand/查询简写

如果一个文档只包含一个查询操作,也不包含变量和指令,那么这个操作可以省略query关键字和操作名。例如,下面这个无名查询操作就写成了查询简写形式:

{
  field
}
注意,后文中很多案例都会使用查询简写格式。

2.4Selection Sets/选择集合

选择集合 → {选择}

选择 →

一个操作选择了他所需要的信息的集合,然后就会精确地得到他所要的信息,没有一点多余,避免了数据的多取或少取。

{
  id
  firstName
  lastName
}

这个query/查询中,idfirstNamelastName字段构成了选择集合,选择集合也能包含fragment/片段的引用。

2.5Fields/字段

字段 → 别名?具名参数?指令?选择集合?

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

有些字段描述了复杂的数据或者与其他数据的关联,为了进一步解明这种数据,一个字段可能包含一个选择集合,从而使能将请求嵌套起来。所有的GraphQL操作都必须依次深入嵌套,指明所有标量值字段,以保证响应数据的形态上没有歧义。

例如,这个操作选择了复杂数据和关联数据,并深入到嵌套内部,直到标量值字段。

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

一个操作中,顶层选择集合的字段通常表示对应用和观察者而言全局可见的信息。典型的案例有顶层字段指向当前登录的观察者,或者引用唯一id来取特定类型数据:

# `me` could represent the currently logged in viewer.`me`指代当前登录的观察者
{
  me {
    name
  }
}

# `user` represents one of many users in a graph of data, referred to by a
# unique identifier.
# `user`表示一个通过id来从图数据中取出来的用户
{
  user(id: 4) {
    name
  }
}

2.6Arguments/参数

参数 → 命名: 值

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

这个案例中,我们向查询特定用户(通过id参数请求)的特定尺寸档案照片(通过size参数):

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

许多参数也能存在于给定字段:

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

Arguments are unordered/参数无需顺序

参数可以以任意句法顺序排列,都表示同一种语义。

下列两个查询语义上都是一样的:

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

2.7Field Alias/字段别名

别名 → 命名

默认情况下,返回对象的键名会采用查询的字段名,然后你可以定义不同的键名,亦即别名。

案例中,我们获取了两个不同尺寸的档案照片,并保证了返回对象没有重复键名:

{
  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"
  }
}

顶级query/查询也是一个字段,所以它也可以使用别名:

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

得到这个结果:

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

如果使用了别名,那么返回对象的中字段的键名就是别名,否则就是字段名。

2.8Fragments/片段

片段解构/展开 → ... 片段名 指令?

片段定义 → fragment 片段名 类型条件 指令? 选择集

片段名 → 除了on以外的命名

片段是GraphQL组合拼装的基本单元,它通用选择集字段的重用得以实现,减少了文档中的重复文本。内联片段可以直接在选择集合内使用,通常用于interface(接口)或者union(联合)这种存在类型条件的场合。

例如,我们想要获取某个用户的朋友以及和他互为朋友的人的共通信息:

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

这些重复的字段可以提取进一个fragment(片段)中,然后被父级fragment(片段)或者query(查询)组合:

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.1Type Conditions/类型条件

类型条件 → on 具名类型

片段需要指定应用于的目标类型,在上述案例中,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根字段将会返回一个列表,其中的元素可能是Page或者User类型。当profiles内的对象是User类型时,friends会出现,而likers不会。反之当结果内的对象是Page时,likers会出现,friends则不会。

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

2.8.2Inline Fragments/内联片段

内联/行内片段 → ... 类型条件? 指令? 选择集合?

片段可以在选择集合内以内联格式定义,这用于根据运行时类型条件式地引入字段。这个特性的标准片段引入版本在query FragmentTyping中已经演示,我们也可以使用内联片段的方式来实现:

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

内联片段也用于将指令应用于一群字段的场景。如果省略了类型条件,片段则被视为等同于封装所在的上下文。

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

2.9Input Values/输入值

值 →

2.9.1Int Value/整数值

Digit
0123456789

指定整数不应该使用小数点或指数符号。(譬如:1)

2.9.2Float Value/浮点值

指定浮点数需要包含小数点(例如:1.0)或者指数符号(例如:1e50)或者两者(例如:6.0221413e23)。

2.9.3Boolean Value/布尔值

BooleanValue
truefalse

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

2.9.4String Value/字符串值

EscapedUnicode
/[0-9A-Fa-f]{4}/

字符串是一系列由双引号(")包起来的字符,譬如"Hello World"。字符串内的空白符和其他无语义字符都对字符串有影响。

字符串值字面量内是允许Unicode字符的,但是GraphQL源文本不允许ASCII控制符,因此如要使用这些字符,则需对其进行转义。

Semantics/语义

StringValue
""
  1. 返回空Unicode字符序列。
StringValue
  1. 返回StringCharacter的Unicode字符序列。
StringCharacter
  1. 返回EscapedUnicode在Unicode基本多文种平面内的16进制对应代码单元。译者案:详情查询Unicode字符平面映射
StringCharacter
  1. 返回EscapedCharacter在此对照表中的值。
转义后字符 字符单元值 字符名称
" U+0022 双引号
\ U+005C 反斜线
/ U+002F 正斜线
b U+0008 退格
f U+000C 换页符
n U+000A 换行符
r U+000D 回车符
t U+0009 水平制表符

2.9.5Null Value/空值

null代表空值。

GraphQL在语义上有两种方式来表示一个缺值:

  • 显式,使用字面量值:null
  • 隐式,不使用任何值。

例如:下列两个相似的字段查询并不是一样的:

{
  field(arg: null)
  field
}

前者显式使用了null给arg参数,后者隐式,没有给arg参数以值。两个形式会被不同解读,譬如一个mutation操作中,会表示成删除一个字段或者不改变一个字段。两者都不能用于非空输入类型参数。

在使用变量表示一个缺值的时候,也可以使用这两种方法,亦即显式提供null,或者隐式,不提供任何值。

2.9.6Enum Value/枚举值

EnumValue
Nametruefalsenull

枚举值表现为没有引号包裹的名称,规范建议使用全大写字母表示枚举值,枚举值仅用于准确枚举类型可用的上下文中,因此枚举类型命名上不必使用字面量。

2.9.7List Value/列表值

ListValueConst
[]
[ValueConstlist]

列表是包在方括号[ ]中的有序值序列,列表值可以是任意字面量值或者变量,譬如[1, 2, 3]

因为GraphQL中逗号是可选的,因此末尾逗号和重复逗号都是允许的,而不会代表空缺值。

Semantics/语义

ListValue
[]
  1. 返回空列表值。
ListValue
[Valuelist]
  1. 使inputList为空.
  2. 对于每一个Valuelist
    1. value变成Value求值后的值。
    2. value添加到inputList尾部。
  3. 返回inputList

2.9.8Input Object Values/输入型对象值

ObjectValueConst
{}
{ObjectFieldConstlist}

输入型对象是无需键值列表,使用花括号{ }包起来。对象的值可以是输入字面量值或者变量值(例如:{ name: "Hello world", score: 1.0 })。我们将输入型对象的字面量表示法称作“对象字面量”。

Input object fields are unordered/输入型对象的字段是无序的

输入型字段能以各种句法顺位排列,而表示相同的语义。

下面两个查询在语义上是一样的:

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

Semantics/语义

ObjectValue
{}
  1. 返回一个无字段的输入型对象。
ObjectValue
  1. 使inputObject为一个无字段的输入型对象。
  2. 对于inputObject中的每一个field
    1. 使fieldNamename
    2. 使fieldValuevalue求值后的值。
    3. inputObject添加一个字段,键为name,值为value
  3. 返回inputObject

2.10Variables/变量

变量 → $ 名称

变量定义 → 变量:类型 默认值?

默认值 → 值常量

GraphQL查询可以使用变量作为参数,已最大化查询重用,避免客户端运行时耗费巨大的字符串重建。

如果没有被定义为常量(例如DefaultValue),Variable就能被赋予一个输入类型。

变量必须在查询的顶部定义,并在整个操作的执行周期范围内可用。

下面例子中,我们想要根据特定设备大小获取一个对应大小的档案图片:

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

在向GraphQL请求的时候,这些参数的值也需要一并发送,以便执行期间用以替换。假设以JSON发送参数,我们请求大小为60宽度的档案照片:

{
  "devicePicSize": 60
}

Variable use within Fragments/片段内的参数

片段内也可以使用查询变量,变量在整个操作中拥有全局作用域,所以片段内变量也需要在顶部操作上定义,以便传递到片段上消费。如果一个参数被片段所引用,包含这个片段的操作并没有定义这个变量,那么这个操作将不会被执行。

2.11Input Types/输入类型

类型 →

列表类型 → [ 类型 ]

非空类型 →

GraphQL描述查询参数需要的类型为输入类型,可以是某种其他输入类型的列表或者其他输入类型的非空变体。

Semantics/语义

Type
  1. 使nameName的值。
  2. 使type为Schema中定义的类型的name
  3. type不可为null
  4. 返回type
Type
  1. 使itemTypeType求值后的值。
  2. 使type为一个列表类型,其中内部类型为itemType
  3. 返回type
Type
  1. 使nullableTypeType计算后的值。
  2. 使Type为非空类型,其中内部类型为nullableType
  3. 返回type

2.12Directives/指令

指令 → @名称 参数? 指令为GraphQL文档提供了另一种运行时执行行为和类型验证行为。

有时候,你需要改变GraphQL的执行行为,而参数并不满足要求,譬如条件性的包含或者跳过一个字段。指令通过向执行器描述附加信息来完成这种需求。

指令需要一个名字和一组参数,可以接受任意输入类型。

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

将来版本的GraphQL会加入可配置的执行能力,他们则可能表现为指令。

3Type System/类型系统

GraphQL的类型系统用于描述服务器的能力以及判断一个查询是否有效。类系统也描述了查询参数的输入类型,用于运行时检查有效性。

GraphQL服务器的能力是同schema来描述,schema使用其支持的类型和指令来定义。

一个给定GraphQL schema其自身首先必须要通过内部有效性验证,本章节将会讲述这个验证过程的相关规则。

一个GraphQL schema使用每种操作的根级类型表示:query/查询、mutation/更改和subscription/订阅,这表示schema是这三种操作开始的地方。

所有GraphQL schema内的类型都必须要有唯一的名字,任意两个类型都不应该有相同的名字,任意类型也不应该有和内建类型冲突的名字(包含Scalar/标量和Introspection/内省类型)。

所有GraphQL schema内的指令也必须拥有唯一的名字,指令和类型可以拥有相同的名字,因为两者之间并没有歧义。

所有schema内定义的类型和指令都不能以"__"(双下划线)开头命名,因为这是GraphQL内省系统专用。

3.1Types/类型

任何GraphQL Schema的最基本单元都是类型,GraphQL中有8种类型。

最基本的类型是Scalar/标量,一个标量代表一个原始值,例如字符串或者整数。有时候,一个标量字段的返回值可能是可枚举的,对应这种场景,GraphQL提速了Enum/枚举类型,其指定了响应结果的有效范围。

标量和枚举型组成了响应结果树的叶子节点,而中间的分支节点则是Object/对象类型,其定义了一套字段,每个字段是系统中的另一个类型,从而能够定义任意层次的类型层级。

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

Interface定义了一系列字段,Object类型通过实现了其中的字段来实现它。当类型系统表明要返回一个接口时,其返回的都是一个实现这个接口的类型。

Union定义了一个可能类型的列表,与接口相似,当系统表明要返回一个联合时,其返回的是联合中的一个类型。

这些类型都是可为空且为单数,譬如,一个标量字符串会返回一个null或者一个字符串。通常有需要表示某个类型的列表,于是GraphQL中提供了List类型,将其他类型封装在其中。类似的Non-Null类型也是封装其他类型,用以标注返回结果不可为空。这两种类型称为“封装类型”,非封装类型称为基础类型,每个封装类型里面都有一个基础类型,通过不断解封装来找到基础类型。

向GraphQL提供复杂结构作为输入参数是十分有用的,GraphQL为此提供了Input Object类型,让客户端从schema中获知服务端具体需要什么样的数据。

3.1.1Scalars/标量

如名字所示,GraphQL中一个标量代表这一个原始值,GraphQL的响应采用的是树形层级结构,其叶子节点即是标量。

所有GraphQL标量都能以字符串形式表示,虽然取决于所用的返回格式,可能有准确的原始类型来表示指定标量,服务器在适当的时候也应采取这种类型。

GraphQL提供了一些内建标量,类型系统也允许根据语义添加其他标量。假设GraphQL中要定义一个标量Time/时间,可将字符串转换成ISO‐8601的格式,当查询一个Time字段时,客户端可以使用ISO‐8601解析器,将这个字段类型转换成客户端特有的原始类型。另一个有潜在用途的标量是Url,通常会序列化成字符串,但是会由服务器保证是有效的URL。

服务器可能会在schema中省略内建标量,譬如,服务器并未使用浮点数,那么它可能并不会包含Float类型。 但是一旦schema包含了本规范所述的类型,那么一定会遵守本规范描述的对应行为,譬如,服务器一定不会使用名为Int的类型去表示128‐bit的数字,或者国际化信息。

Result Coercion/结果类型转换

当GraphQL服务器准备一个标量字段的时候,必须遵守此标量的描述协议,或者强制转换原始值,或者抛出错误。

例如:当服务器在准备一个Int型标量时,收到的是一个浮点数,如果服务器直接输出这个值,那势必会打破协定,所以服务器可以剔除小数部分,只保留整数部分,然后返回整数值,如果服务器收到的是布尔型值true,那么就可以返回1,如果服务器收到的是字符串型,那么就尝试以10为底,解析字符串为整数,如果服务器没法将某些值转换成Int,那么就只能抛出错误。

因为这个转换行为对于客户端是不可见的,所以准确的转换规则全由实现指定,规范对其的唯一要求就是输出值需遵守标量协议。

Input Coercion/输入类型转换

如果GraphQL的某个参数要求标量作为输入,其类型转换显而易见地必须良好定义。如果输入值无法满足转换规则,则必须抛出错误、

GraphQL对于整数和浮点数输入值有不同的字面量表示方式,其类型转换也对应输入类型做转换。GraphQL可以使用变量作为参数,这些变量的值像是在HTTP的传输中通常会被序列化,由于有些序列化方法并不区分整数和浮点数(譬如JSON),可能因为一个数没有有效小数值而被当作整数而不是浮点数。

下列所有类型中,除了Non‐Null之外,如果显式提供了null,那么其输入结果就会被转换成null

Built‐in Scalars/内建标量

GraphQL提供一套基本的定义良好的标量类型,每个GraphQL服务器都应该支持这些类型,并且使用这些名字的类型必须遵守下文描述的行为、

3.1.1.1Int/整数型

整数型标量类型表示一个32位有符号的无小数部分的数值。响应格式应该使用一个支持32位的整数型或者数值类型来表示这个标量类型、

Result Coercion/结果类型转换

GraphQL服务器应该转换非整数型原始数据为整数型,如若不能,则必须抛出字段错误。例如,从浮点型1.0转换成1,或者从字符串型"2"转换成2

Input Coercion/输入类型转换

当需要作为输入类型时,只接受整数型输入值。其它类型,包含字符串型数值内容,都要抛出类型不正确的查询错误。如果整数型输入值小于-231或者大于231,也要抛出查询错误。

超过32位的整数建议使用字符串或者自定义的标量类型,因为不是所有平台和传输协议都支持超过32位精度编码额整数型数值。
3.1.1.2Float/浮点型

浮点型标量类型表示一个有符号的双精度小数,见IEEE 754。响应格式应该使用一个合适的双精度数值类型来表示这个标量类型。

Result Coercion/结果类型转换

GraphQL服务器应该转换非浮点型原始数据为型,如若不能,则必须抛出字段错误。例如,从整数型1转换成1.0,或者是字符串型"2"转换成2.0

Input Coercion/输入类型转换

当需要作为输入类型时,接受整数型和浮点型输入值。整数型会被转换成小数部分为空的浮点数,譬如整数型输入值1转换成1.0,其他类型,包含字符串型数值类型,都要抛出类型不正确的查询错误。如果整型输入值无法使用IEEE 754方式表示,也要抛出查询错误。

3.1.1.3String/字符串型

字符串型标量表示UTF‐8字符序列组成的文本数据。GraphQL一般使用字符串型来表示任意格式人类可读的文本。所有响应格式都必须支持字符串型表示,字符串型如下所述。

Result Coercion/结果类型转换

GraphQL服务器应该转换非字符串型原始数据为字符串型,如若不能,则必须抛出字段错误。例如,从布尔型true转换成"true",从整型1转换成"1"

Input Coercion/输入类型转换

当需要作为输入类型时,只接受有效的UTF‐8字符串型输入值。其它类型都要抛出类型不正确的查询错误。

3.1.1.4Boolean/布尔型

布尔型标量表示true或者false两个值。响应格式应该使用内建的布尔型,如不支持,则使用另外的表示法,整数型10

Result Coercion/结果类型转换

GraphQL服务器应该转换非布尔型原始数据为布尔型,如若不能,则必须抛出字段错误。例如,将任意不为零数值转换成true

Input Coercion/输入类型转换

当需要作为输入类型时,只接受布尔型输入值。其它类型都要抛出类型不正确的查询错误。

3.1.1.5ID

ID型标量表示一个唯一标识符,通常用于重取一个对象或者作为缓存的键。ID型使用String相同方式来序列化,但是它并不是为了人类可读,虽然它通常可能是数值型,但也总是序列化成String

Result Coercion/结果类型转换

GraphQL对ID的格式是不干预的,将其序列化成字符串以保证ID的多种格式之间的相容性,可以是自增数值,可以是128位大数,也可是base64编码后的值和GUID等格式的字符串值。

GraphQL服务器应该将给定ID格式转换成字符串,如若不能,则必须抛出字段错误。

Input Coercion/输入类型转换

当需要作为输入类型时,任意字符串(譬如"4")或者整数(譬如4)都应该被转换成给定服务器支持的ID格式。其他的输入类型,包括浮点型(譬如4.0)都必须抛出类型不正确的查询错误。

3.1.2Objects/对象

GraphQL查询是层级式的可组装的,以树的形式描述了信息。其中标量类型描述了层级查询中叶子节点的值,对象则描述了中间层。

GraphQL对象表示一个具名字段列表,每个字段会产出一个特定类型的值。对象的值应该向有序映射集(ordered maps),其中查询字段名(或者别名)作为键,字段的结果作为值,以出现在查询中的顺序来排序。

对象中的所有字段都不应以"__"(双下划线)起头命名,因为这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"
  }
}

Field Ordering/字段排序

当查询一个对象时,字段的结果映射在概念上的排序顺序应该是跟查询执行期间字段被执行的顺序一致,除了片段上并不适用于当前字段的类型和被@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
}

Result Coercion/结果类型转换

对象类型的结果类型转换判定机制是GraphQL执行器的核心,因此将在本规范的执行器那一章节覆盖这个内容。

Input Coercion/输入类型转换

对象类型不可作为有效输入类型。

3.1.2.1Object Field Arguments/对象字段参数

概念上的对象字段是会产生值的函数,有时候对象字段能够接受参数来进一步指定返回值。对象字段参数在定义上是一个所有可能参数和参数输入类型的列表。

一个字段内的所有参数都不能以"__"(双下划线)起头命名,因为这GraphQL内省系统专用的命名方式。

例如,Person拥有一个picture字段,接受一个参数以返回特定大小的图片链接。

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.2Object Field deprecation/对象字段弃用

应用在必要情况下会将对象字段标注为弃用。这样之后,查询弃用字段依然有效(为了保证既有客户端不被这个变更导致异常),但是这种字段应该在文档和工具中正确对待。

3.1.2.3Object type validation/对象类型验证

对象类型可能因为定义的不严谨而导致潜在的无效性。GraphQL Schema中,以下规则必须被所有对象遵守。

  1. 一个对象类型必须定义一个或多个字段。
  2. 一个对象类型内的字段必须拥有这个对象类型内唯一的命名;任何两个字段都不可同名。
  3. 一个对象类型的每个字段都不能以"__"(双下划线)起头命名。
  4. 一个对象类型可以声明实现了一个或多个不同接口。
  5. 一个对象类型必须是所有它所实现的接口的超集:
    1. 对象类型必须包含其接口内所有字段同名的字段。译者案:此句翻译准确性待定
      1. 这个对象字段类型必须是接口字段类型等价的类型或者其子类型(协变性)。
        1. 一个对象字段类型如果是这个接口字段类型等价(相同)的类型,那么它是一个有效子类型。
        2. 一个对象字段类型如果是一个对象类型,且其接口字段类型是一个接口类型或者联合类型,且这个对象字段类型是这个接口字段类型的可能类型,那么它是一个有效子类型。
        3. 一个对象字段类型如果是一个列表类型,且其接口字段类型也是一个列表类型,且这个对象字段类型的列表元素类型是接口字段类型的列表元素类型的子类型,那么它是一个有效子类型。
        4. 一个对象字段类型如果是其接口字段类型的子类型的Non‐Null(非空)变体,那么它是一个有效子类型。
      2. 这个对象字段必须包含其接口字段上定一个所有参数的同名参数。
        1. 这个对象字段的参数必须接受其接口字段上参数同类型的参数(逆变性)。
      3. 这个对象字段可以包含其接口字段上未定义的附加参数,但附加参数并不是必须。

3.1.3Interfaces/接口

GraphQL接口表示一个具名字段列表以及其参数,GraphQL对象可以实现接口,并保证包含接口中的字段。

GraphQL接口上的字段拥有和GraphQL对象上相同的规则;字段类型可以是标量、对象、枚举型、接口或者联合,或者这五个类型作为基本类型的封装类型。

例如,一个接口可以描述某个必要字段的类型,譬如Person或者Business,随后实现这个接口。

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,其中NamedEntity中定义了name,所以这个查询是有效的。所以,下面这个查询是无效的:

{
  entity {
    name
    age
  }
  phoneNumber
}

因为entity指代NamedEntity,这个接口中并没有定义age,所以只有在entity的结果为Person,查询age才有效。查询语句可以通过片段或者内联片段来表述这种情况:

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

Result Coercion/结果类型转换

接口类型应该能够判定给定结果对应哪一个对象类型,一旦确定,接口的结果类型转换和对象的接口转换采用一样的方法。

Input Coercion/输入类型转换

接口类型不可作为有效输入类型。

3.1.3.1Interface type validation/接口类型验证

接口类型可能因为定义的不严谨而导致潜在的无效性。

  1. 一个接口类型必须定义一个或多个字段。
  2. 一个接口类型内的字段必须拥有这个接口类型内唯一的命名;任何两个字段都不可同名。
  3. 一个对象类型的每个字段都不能以"__"(双下划线)起头命名。

3.1.4Unions/联合

GraphQL联合表示一个对象的类型是对象类型列表中之一,但不保证这些类型之间的字段。另一个区别于接口的方面是,对象会声明其实现的接口,而不知道它被包含的联合。

对于接口和对象,只可以直接查询在其中被定义的字段,如果要查询接口的其他字段,必须使用类型片段。对于联合也是一样,但是联合不定义任何字段,所以联合上允许查询任何字段,除非使用类型片段。

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

union SearchResult = Photo | Person

type Person {
  name: String
  age: Int
}

type Photo {
  height: Int
  width: Int
}

type SearchQuery {
  firstSearchResult: SearchResult
}

当查询SearchQuery类型的firstSearchResult字段时,查询可能需要片段的所有字段来判断类型。如果结果是Person,那请求其name,如果是photo,那请求height。下列案例是错的,因为联合上不定义任何字段:

{
  firstSearchResult {
    name
    height
  }
}

而正确的查询应该是:

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

Result Coercion/结果类型转换

联合类型应该能够判定给定结果对应哪一个对象类型,一旦确定,联合的结果类型转换和对象的接口转换采用一样的方法。

Input Coercion/输入类型转换

联合类型不可作为有效输入类型。

3.1.4.1Union type validation/联合类型验证

联合类型可能因为定义的不严谨而导致潜在的无效性。

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

3.1.5Enums/枚举型

GraphQL枚举型是基于标量类型的变体,其表示可能值的一个有限集。

GraphQL枚举型并不指代数值,而是正确的唯一值。他们序列化成字符串,字符串中用名来表示值。

Result Coercion/结果类型转换

GraphQL服务器必须返回定义中可能结果集的一个值。如果无法达成合理的类型转换,则抛出字段错误。

Input Coercion/输入类型转换

GraphQL用常量字面量表示枚举型输入值,GraphQL字符串型字面量不可作为枚举型输入值,否则将抛出查询错误。

查询变量的序列化传输方式中,如果对于非字符串符号值有区别于字符串的表示方法(譬如EDN),那么就应该采用那种方法。否则就像没有这个能力的大多数序列化传输方法一样,字符串型将被转义成同名的枚举型值。

3.1.6Input Objects/输入对象

字段上可能会定义参数,客户端将参数合在查询中传输,从而改变字段的行为。这些输入可能是字符串型或者枚举型,但是有时候需要比这个更复杂的类型结构。

上文中定义的Object并不适合在这儿重用,因为Object可能包含循环引用或者指代了接口类型或联合类型,这俩都不适合作为输入参数。因此,输入对象才成为了这个系统中单独的类型。

Input Object定义了输入字段的一个集合,输入字段并不是标量、枚举型或者其他输入对象,这使参数可以接受任意的复杂结构。

Result Coercion/结果类型转换

输入对象类型不可作为有效结果类型。

Input Coercion/输入类型转换

输入对象类型的值应该是输入对象的字面量或者无序映射集,否则将抛出错误。这个无序映射集不应该包含这个输入对象类型中未定义的字段,否则将抛出错误。

如果输入对象上定一个非空字段没有从原始值收到对应条目(譬如变量未赋值,或者赋予了null(空)值),则抛出错误。

类型转换的结果是环境特定的无序映射集,其定义了由输入对象类型和原始值构成的字段。

对于输入对象类型中的字段,如果原始值中有条目拥有相同的名字,条目的值是一个字面量或者变量(运行时值),这个条目就被添加到结果中同名的字段上。

结果中条目的值是原始值类型转换之后的结果,类型转换的规则由输入字段的类型决定。

下列是输入对象类型转换的案例:

input ExampleInputObject {
  a: String
  b: Int!
}
原始值 变量 转换后的值
{ a: "abc", b: 123 } null { a: "abc", b: 123 }
{ a: 123, b: "123" } null { a: "123", b: 123 }
{ a: "abc" } null Error: Missing required field b
{ a: "abc", b: null } null Error: b must be non‐null.
{ a: null, b: 1 } null { a: null, b: 1 }
{ b: $var } { var: 123 } { b: 123 }
{ b: $var } {} Error: Missing required field b.
{ b: $var } { var: null } Error: b must be non‐null.
{ a: $var, b: 1 } { var: null } { a: null, b: 1 }
{ a: $var, b: 1 } {} { b: 1 }
输入值显式声明一个输入字段额度值为null和连输入字段都没声明两种情况存在语义差异。
3.1.6.1Input Object type validation/输入对象类型验证
  1. 一个输入对象类型必须定义一个或多个字段。
  2. 一个输入对象类型内的字段必须拥有这个接口类型内唯一的命名;任何两个字段都不可同名。
  3. 每个字段的返回类型必须是输入类型。

3.1.7Lists/列表型

GraphQL列表市一个特殊的集合类型,它声明了列表中元素的类型(下文指代为元素类型)。列表值的序列化结果是一个有序列表,列表中的元素根据元素类型序列化。一个字段使用了一个列表类型,其中封装了元素类型,其标注形式为:pets: [Pet]

Result Coercion/结果类型转换

GraphQL服务器必须返回有序列表作为一个列表类型的结果,列表中的每一个元素都是其元素类型的结果类型转换的结果,如果无法达成合理的类型转换,则抛出字段错误。其中,如果返回的是非列表类型,类型转换也会失败,这是要抛出一个类型系统和实现不匹配的异常。

Input Coercion/输入类型转换

当作为输入类型的时候,要求所有列表值都符合列表元素类型。

如果作为输入类型传递给列表类型的值,既是列表型也不是null空值,那么它将作为列表中的唯一元素作类型转换,这使输入能够在即便声明为“var args”类型参数数组,但只有一个参数被传入(常见场景),客户端可以直接传递值而不用封装成列表。

如果通过运行时变量传入给一个列表型的值是null,那么这个值会被解读为未传输任何列表,而不是一个长度为1的值为null的列表。

3.1.8Non-Null/非空型

默认情况下,GraphQL的所有类型都是可空的;null可作为上述所有类型的有效响应。如果要声明一个值不可为空,可以使用GraphQL的非空类型。这个类型封装一个内部类型,并表现为和被封装类型一样的行为,除了null不可作为其有效响应。非空类型使用一个感叹号跟着封装类型的方式来标注:name: String!

Nullable vs. Optional/可空 vs 可选

查询上下文中的字段总是可选的,某个字段可以被省略而查询依然有效。然后,非空类型的字段绝不能对查询返回null

输入(譬如字段参数)默认情况下总是可选的,然而如果一个参数是非空输入类型,那么它将不接受null也不能被省略。为了简明,(nullable)可空类型总是(optional)可选的,(non‐null)非空类型总是(required)必须的。

Result Coercion/结果类型转换

上述所有结果类型转换中,null都被视为有效值,如果转换一个非空类型,那么会使用被封装类型的转换规则。如果非空类型的转换结果不为null空,那么就取这个结果。否则如果结果为null空,那么则抛出字段错误。

Input Coercion/输入类型转换

如果未提供非空类型的输入对象字段或者参数,则被认为是提供了字面量null,或者认为是提供了一个未赋值或者为null空值的运行时变量,然后抛出查询错误。

如果给非空类型提供的值是字面量而非null,或者一个非空变量值,则其会按照被封装类型的输入类型转换规则来转换。

案例:非空参数不可省略。

{
  fieldWithNonNullArg
}

案例:null空值不可作为非空参数的值。

{
  fieldWithNonNullArg(nonNullArg: null)
}

案例:一个可空的变量不能作为非空参数的值。

query withNullableVariable($var: String) {
  fieldWithNonNullArg(nonNullArg: $var)
}
在验证章节的定义中,向可空类型提供非空的输入值是无效。

Non‐Null type validation/非空类型验证

  1. 一个非空类型不可封装另一个非空类型。

3.2Directives/指令

一个GraphQL Schema包含了执行引擎支持的指令表。

GraphQL的实现需要提供@skip@include指令。

3.2.1@skip

@skip指令可用于字段、片段展开以及内联片段,从而能够在执行期间通过if参数完成条件性排除。

下列案例中,experimentalField只有在$someTestfalse的时候才会被查询。

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

3.2.2@include

@include指令可用于字段、片段展开以及内联片段,从而能够在执行期间通过if参数完成条件性包含。

下列案例中,experimentalField只有在$someTesttrue的时候才会被查询。

query myQuery($someTest: Boolean) {
  experimentalField @include(if: $someTest)
}
@skip@include没有优先级差别,当@skip@include同时应用于一个字段时,当且仅当@skipfalse@includetrue的时候它才会被查询。相反的,在仅有@skiptrue或者仅有@includefalse的时候它不会被查询。

3.3Initial types/初始类型

一个GraphQL Schema包含了类型,表示query/查询、mutation/更改和subscription/订阅的起点,提供了进入类型系统的最初入口。其中query类型始终是必须的,并且是一个对象基础类型。mutation类型是可选的,如果其为空,那意味着此系统不支持mutation/更改,如果不为空,它必须是一个对象基础类型。同样的,subscription类型也是可选的,如果其为空,则此系统不支持subscription/订阅,如果不为空,它必须是一个对象基础类型。

query类型的字段表示GraphQL查询中最顶层可用的字段。例如:一个基本的GraphQL查询可能像这样:

query getMe {
  me
}

当query对应的类型含有一个名为“me”的字段时,这个查询就是有效的。类似的:

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

当mutation对应的类型不为空,这个类型有一个名为“setName”参数为“name”的字段时,这个更改就是有效的。

subscription {
  newMessage {
    text
  }
}

当subscription对应的类型不为空,这个类型有一个名为“newMessage”的字段,且只有一个根字段时,这个订阅就是有效的。

4Introspection/内省

一个GraphQL服务器支持基于它的Schema来支持内省,这个Schema可以通过GraphQL自身来查询,创建了一个强大的工具构建平台。

拿一个琐碎的app的样例查询举例。样例中有一个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.1General Principles/基本原则

4.1.1Naming conventions/命名约定

GraphQL内省系统要求的类型和字段与用户定义的类型和系统共享相同的上下文,其字段名以"__"双下划线开头,以避免和用户定义的GraphQL类型命名冲突。相反地,GraphQL类型系统作者不能定义任何以双下划线开头的类型、字段、参数和其他类型系统工件。artifact的翻译不确定

4.1.2Documentation/文档

所有内省系统中的类型必须提供String类型的description字段,以便类型设计者发布文档以增强能力。GraphQL服务可以返回使用Markdown语法(CommonMark中指定)的description字段。因此建议所有展示description字段的工具使用CommonMark兼容的Markdown渲染器。

4.1.3Deprecation/弃用

为了支持向后兼容管理,GraphQL字段和枚举值可以指出其是否弃用(isDeprecated: Boolean)和一个为何弃用的描述(deprecationReason: String)。

基于GraphQL内省系统建造的工具应该通过隐含信息或者面向开发者的警告信息来减少弃用字段的使用。

4.1.4Type Name Introspection/类型命名内省

GraphQL支持类型命名自行,查询任意对象/接口/联合时可以在一个查询语句的任意位置通过元字段__typename: String!,得到当前查询的对象类型。

这在查询接口或者联合类型的真实类型的时候用得最为频繁。

这是个隐式字段,并不会出现在定义的类型的字段列表中。

4.2Schema Introspection/Schema内省

Schema的内省系统可通过查询操作的根级类型上的元字段__schema__type来接入。

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

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

GraphQL Schema内省系统的Schema:

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
}

4.2.1The __Type Type/__Type类型

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

__Type也表示类型修改器,其通常用于表示修改一个类型(ofType: __Type),这正是我们如何表示列表类型和非空类型,以及他们的组合类型。

4.2.2Type Kinds/类型种类

类型系统中存在多个种类的类型,每种类型都有不同的有效字段,这些类型都被列举在__TypeKind枚举值中。

4.2.2.1Scalar/标量

标量中譬如整数型、字符串型、布尔型都不能拥有字段。

GraphQL类型设计者需要描述标量的description字段描述数据格式以及标量的类型转换规则。

字段

  • kind必须返回__TypeKind.SCALAR
  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • 其他字段必须返回null空值。
4.2.2.2Object/对象

对象类型表示一系列字段的具体实例,内省类型(譬如__Type,__Field等)亦是典型的对象类型。

字段

  • kind必须返回__TypeKind.OBJECT
  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • fields:本类型上可被查询的字段的集合。
    • 接受参数includeDeprecated,默认为false。如过为true则弃用的字段也将返回。
  • interfaces:对象实现的接口的集合。
  • 其他字段必须返回null空值。
4.2.2.3Union/联合

联合是一个抽象类型,其不声明任何字段。联合的可能类型要显式的在possibleTypes中列出。类型可不加修改的直接作为联合的一部分。

字段

  • kind必须返回__TypeKind.UNION
  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • possibleTypes必须返回联合内可能的类型的列表,它们都必须是对象类型。
  • 其他字段必须返回null空值。
4.2.2.4Interface/接口

接口是一个抽象类型,其声明了通用字段。所有实现接口的对象必须定义完全匹配的名字和类型的字段。实现了此接口的类型都有在possibleTypes中列出。

字段

  • __TypeKind.INTERFACE
  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • fields: 接口要求的字段的集合。
  • possibleTypes返回实现了此接口的类型,它们都必须是对象类型。
  • 其他字段必须返回null空值。
4.2.2.5Enum/枚举型

枚举型是特殊的标量,他只能拥有有限集合的标量。

字段

  • kind必须返回__TypeKind.ENUM
  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • enumValuesEnumValue的列表。必须至少有一个值,并且必须有不同的名字。
    • 接受参数includeDeprecated,默认为false。如过为true则弃用的字段也将返回。
4.2.2.6Input Object/输入对象

输入对象是复合类型,通常以具名输入值列表的形式作为查询的输入。

例如输入对象Point可以定义成:

input Point {
  x: Int
  y: Int
}

字段

  • kind必须返回__TypeKind.INPUT_OBJECT
  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • inputFieldsInputValue的列表。
4.2.2.7List/列表

列表表示一系列GraphQL值。列表类型是类型修改器:它在ofType中封装了另一个类型的实例,其定义了列表中的每个元素。

字段

  • kind必须返回__TypeKind.LIST
  • ofType:任意类型。
  • 其他字段必须返回null空值。
4.2.2.8Non-Null/非空

GraphQL类型都是可空的,null值是有效的字段类型返回值。

非空类型是类型修改器:它它在ofType中封装了另一个类型的实例。非空类型不允许null作为返回,也用于表示必要输入参数和必要输入对象字段。

  • kind必须返回__TypeKind.NON_NULL
  • ofType:除了Non‐null外的所有类型。
  • 其他字段必须返回null空值。
4.2.2.9Combining List and Non-Null/列表和非空的组合

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

如果列表修改的类型是非空,那么列表不能包含任何null空值元素。

如果非空修改的类型是列表,那么不能接受null空值,但是空数组可接受。

如果列表修改的类型是列表,那么第一个列表的元素是第二个列表类型的列表。

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

4.2.3The __Field Type/__Field类型

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

字段

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

4.2.4The __InputValue Type/__InputValue类型

__InputValue类型表示字段和指令的参数,如同输入对象的inputFields字段。

字段

  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • type必须返回表示这个输入值期待的类型的__Type
  • defaultValue返回一个(使用GraphQL语言)字符串,表示这个输入值在运行时未提供值的情况下的默认值,如果这个输入值没有默认值,返回null空值。

4.2.5The __EnumValue Type/__EnumValue类型

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

字段

  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • isDeprecated返回true如果这个字段不应再被使用,否则false
  • deprecationReason可选,提供字段被弃用的原因。

4.2.6The __Directive Type/__Directive类型

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

字段

  • name必须返回字符串。
  • description必须返回字符串或者null空值。
  • locations返回__DirectiveLocation列表,表示指令可以放置的有效位置。
  • args返回__InputValue的列表,表示这个字段接受的参数。

5Validation/验证

GraphQL不仅会检测一个请求是否句法上正确,还会其在给定GraphQL schema上下文内无歧义无错误。

一个无效请求依然是技术上可执行的,也能通过执行章节的步骤产生稳定的结果,但是相对于这个有验证错误的请求,其结果可能是有歧义的、意外不可预知的,所以对于无效请求,不应该予以执行。

典型的验证会在请求执行前的上下文中执行,但是如果给定请求之前已经通过验证,那么GraphQL服务在执行这个请求前可能不会显式的验证它,譬如一个请求在开发期已经通过验证,并假设他后面不会改变,或者服务器层验证了一个请求,记住了它的验证结果以避免后续再次验证同样的请求。因此任何客户端或者开发期工具,都应该汇报验证错误,并阻止构建或者执行当时已知错误的请求。

Type system evolution/类型系统演变

GraphQL类型系统可能会随着时间添加一些类型和字段,从而发生了演变,之前有效的请求之后可能就变得无效。任何让之前有效的请求变得无效的变化都称之为破坏性变化。GraphQL服务和schema维护者被鼓励要避免破坏性变化,但是为了保证面对破坏性变化的弹性,复杂的GraphQL系统可能依然允许执行在某个点上没有错误也未曾改变的请求。

Examples/案例

至于本章节的schema,我们假定有如下类型系统用于描述案例:

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.1Operations/操作

5.1.1Named Operation Definitions/具名操作定义

5.1.1.1Operation Name Uniqueness/操作名唯一性

Formal Specification/形式规范

  • 对于文档中的每一个操作定义operation
  • 使operationNameoperation的名字。
  • 如果存在operationName
    • 使operations为文档中名为operationName的所有的操作定义。
    • operations必然是只有一个值的集合。

Explanatory Text/解释文本

每一个具名操作定义必须是在其文档中唯一,以便于使用其名字指代。

例如下列文档就是有效的:

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.2Anonymous Operation Definitions/匿名操作定义

5.1.2.1Lone Anonymous Operation/单独匿名操作

Formal Specification/形式规范

  • 使operations为文档中所有的操作定义。
  • 使anonymous为文档中所有的匿名操作定义。
  • 如果operations集合多余1个值:
    • anonymous必须为空.

Explanatory Text/解释文本

GraphQL允许在文档只有一个操作存在时用简写形式定义查询操作。

例如下列文档就是有效的:

{
  dog {
    name
  }
}

然而这个文档是无效的:

{
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}

5.1.3Subscription Operation Definitions/订阅操作定义

5.1.3.1Single root field/单个根级字段

Formal Specification/形式规范

  • 对于文档中的每一个订阅定义subscription
  • 使rootFieldssubscription上的顶级选择集。
    • rootFields必然是只有一个值的集合。

Explanatory Text/解释文本

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

有效案例:

subscription sub {
  newMessage {
    body
    sender
  }
}
fragment newMessageFields on Message {
  body
  sender
}

subscription sub {
  newMessage {
    ... newMessageFields  
  }
}

无效案例:

subscription sub {
  newMessage {
    body
    sender
  }
  disallowedSecondRootField
}

内省字段也是计算在内的,以下案例是无效的:

subscription sub {
  newMessage {
    body
    sender
  }
  __typename
}

5.2Fields/字段

5.2.1Field Selections on Objects, Interfaces, and Unions Types/对象、接口和联合上的字段选择

Formal Specification/形式规范

  • 对于文档中的每一个selection
  • 使fieldNameselection的目标字段。
  • fieldName必须定义在范围内的类型上。

Explanatory Text/解释文本

字段选择的目标字段必须定义在选择集的范围类型上。对别名没有限制。

譬如下列片段无法通过验证:

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.2Field Selection Merging/字段选择合并

Formal Specification/形式规范

  • 使set为GraphQL文档上定义的任意选择集。
  • FieldsInSetCanMerge(set)必然为true。

FieldsInSetCanMerge(set):

  • 使fieldsForName为包含访问片段和内联片段的set中给定响应名的选择集。
  • 假设fieldsForName有一对成员fieldAfieldB
    • SameResponseShape(fieldA, fieldB)必须为true。
    • 如果fieldAfieldB的父类型一样或者都不为对象类型:
      • fieldAfieldB必然有相同的字段名。
      • fieldAfieldB必然有相同的参数集。
      • 使mergedSet为选择集fieldAfieldB相加的结果。
      • FieldsInSetCanMerge(mergedSet)必然为true。

SameResponseShape(fieldA, fieldB):

  • 使typeAfieldA的返回类型。
  • 使typeBfieldB的返回类型。
  • 如果typeA或者typeB是Non‐Null非空类型。
    • typeAtypeB必然两个都是Non‐Null类型。
    • 使typeAtypeA的可空类型
    • 使typeBtypeB的可空类型
  • 如果typeA或者typeB是List列表类型。
    • typeAtypeB必然两个都是List类型
    • 使typeAtypeA的元素类型
    • 使typeBtypeB的元素类型
    • 从第3步重复。
  • 如果typeA或者typeB是Scalar标量或者Enum枚举型。
    • typeAtypeB必然是相同类型。
  • 断言:typeAtypeB都是组合类型。
  • 使mergedSet为选择集fieldAfieldB相加的结果。
  • 使fieldsForName为包含访问片段和内联片段的set中给定响应名的选择集。
  • 假设fieldsForName有一对成员subfieldAsubfieldB
    • SameResponseShape(subfieldA, subfieldB)必然为true。

Explanatory Text/解释文本

如果执行期间遇到了相同响应名的多个字段选择,执行的字段和参数以及结果值都应该避免歧义。然而,任意两个字段选择能在同一个对象里面遇到还是有效的,那只能是它们等价的情况。

对于简单的手写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可能是String或者Int

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

5.2.3Leaf Field Selections/叶子节点选择

Formal Specification/形式规范

  • 对于文档中的每一个selection
  • 使selectionTypeselection的结果类型
  • 如果selectionType是一个标量:
    • 这个选择的下级选择必须为空
  • 如果selectionType是一个接口、联合或者对象
    • 这个选择的下级选择必为空

Explanatory Text/解释文本

标量上的字段选择是不允许的,标量在任何GraphQL查询的都只是叶子节点。

下列是有效的。

fragment scalarSelection on Dog {
  barkVolume
}

下列是无效的。

fragment scalarSelectionsNotAllowedOnBoolean on Dog {
  barkVolume {
    sinceWhen
  }
}

相反的,GraphQL查询的叶子字段选择必须为标量。在接口、联合或者对象上的选择也不允许没有下级选择。

假设schema有下列查询根类型的补充内容:

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

以下案例是无效的

query directQueryOnObjectWithoutSubFields {
  human
}

query directQueryOnInterfaceWithoutSubFields {
  pet
}

query directQueryOnUnionWithoutSubFields {
  catOrDog
}

5.3Arguments/参数

参数在字段和指令上都有使用,下列验证规则可应用于这两种情况。

5.3.1Argument Names/参数名

Formal Specification/形式规范

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

Explanatory Text/解释文本

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

譬如下列是有效的:

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.2Argument Uniqueness/参数唯一性

字段和指令将参数视作参数名到从参数值的映射,一个参数集合内有多于一个参数拥有一个样的参数名时将会产生歧义,也是无效的。

Formal Specification/形式规范

  • 对于文档中的每一个argument
  • 使argumentNameargument的名字。
  • 使arguments为参数集合中所有具有argumentName的名字且包含argument的所有参数。
  • arguments必然是只包含argument的集合。

5.3.3Argument Values Type Correctness/参数值类型正确性

5.3.3.1Compatible Values/兼容值

Formal Specification/形式规范

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

Explanatory Text/解释文本

字面量值必须和使用它的参数的类型相兼容,兼容规则为类型系统章节所定义的转换规则。

譬如,一个Int型可以被转换为Float型。

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

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

而String到Int则是不可转换的,因此下面的案例是无效的:

fragment stringIntoInt on Arguments {
  intArgField(intArg: "3")
}
5.3.3.2Required Non-Null Arguments/必要非空参数
  • 对于文档中的每一个字段或指令。
  • 使arguments为字段或指令提供的参数。
  • 使argumentDefinitions为字段或指令的参数定义集合。
  • 对于argumentDefinitions上的每一个definition
    • 使typedefinition所需要的类型。
    • 如果type是Non‐Null非空:
      • 使argumentNamedefinition的名字。
      • 使argumentarguments中名为argumentName的参数。
      • argument必然存在。
      • 使valueargument的值。
      • value不可为null字面量。

Explanatory Text/解释文本

参数也能是必要的,只要参数的类型是非空,那么这个参数就是必须的,且显式值null也不能用于此。否则参数是可选的。

例如以下是有效的:

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

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

可空参数的字段,参数可以省略。

因此以下是有效的:

fragment goodBooleanArgDefault on Arguments {
  booleanArgField
}

但是这对于一个非空参数就不是有效的了。

fragment missingRequiredArg on Arguments {
  nonNullBooleanArgField
}

使用显式值null也是无效的。

fragment missingRequiredArg on Arguments {
  notNullBooleanArgField(nonNullBooleanArg: null)
}

5.4Fragments/片段

5.4.1Fragment Declarations/片段声明

5.4.1.1Fragment Name Uniqueness/片段名唯一性

Formal Specification/形式规范

  • 对于文档中的每一个fragment
  • 使fragmentNamefragment的名字。
  • 使fragments为文档中名为fragmentName的所有片段定义。
  • fragments必然是只有一个值的集合。

Explanatory Text/解释文本

片段定义在片段解构中使用名字引用。为避免歧义,每一个片段都必须是片段内唯一。

行内片段并不被当作片段定义,也不被这些验证规则影响。

例如以下文档是有效的:

{
  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.2Fragment Spread Type Existence/片段解构类型存在性

Formal Specification/形式规范

  • 对于文档中的每一个文档解构namedSpread
  • 使fragmentnamedSpread的目标
  • fragment的目标类型必须在schema中定义过的。

Explanatory Text/解释文本

片段必须在schema中存在的类型上指定,这同时适用于具名片段和内联片段。如果它们不存在于schema上,那么这个查询则无法通过验证。

譬如下列片段是有效的:

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.3Fragments On Composite Types/组合类型上的片段

Formal Specification/形式规范

  • 对于定义在文档内的每一个fragment
  • 片段的目标类型必须是UNIONINTERFACE或者OBJECT类型。

Explanatory Text/解释文本

片段只能在联合、接口和对象上声明,在标量上是无效的,只能应用与非叶子节点,这个规则同时适用于具名片段和行内片段。

下列片段声明是有效的:

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.4Fragments Must Be Used/必须使用的片段

Formal Specification/形式规范

  • 对于文档中的每一个fragment
  • fragment必然是文档中至少一个解构的目标。

Explanatory Text/解释文本

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

例如下列是一个无效的查询文档:

fragment nameFragment on Dog { # unused
  name
}

{
  dog {
    name
  }
}

5.4.2Fragment Spreads/片段解构

字段选择也被片段解构之间的互相调用决定。譬如目标片段的选择集和同级的引用目标片段相联合。

5.4.2.1Fragment spread target defined/片段解构目标必须预先定义

Formal Specification/形式规范

  • 对于文档中的每一个namedSpread
  • 使fragmentnamedSpread的目标。
  • fragment必须在文档中预先定义。

Explanatory Text/解释文本

具名片段解构必须指定文档中已经定义的片段。如果结构的目标没有定义,则将会报错:

{
  dog {
    ...undefinedFragment
  }
}
5.4.2.2Fragment spreads must not form cycles/片段解构不可造成循环

Formal Specification/形式规范

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

DetectCycles(fragmentDefinition, visited)

  • 使spreads为为fragmentDefinition的所有片段解构后代。
  • 对于spreads的每一个spread
    • visited必不包含spread
    • 使nextVisited为包含spreadvisited成员的集合
    • 使nextFragmentDefinitionspread的目标
    • DetectCycles(nextFragmentDefinition, nextVisited)

Explanatory Text/解释文本

片段结构的图不可造成任何循环,即便是对自身的解构。否则一个操作会下层数据的环上无尽地结构无尽地执行。

这使可能导致无尽解构的片段失效;

{
  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.3Fragment spread is possible/片段结构必须可行

Formal Specification/形式规范

  • 对于文档中定义的每样个(具名的和内联的)spread
  • 使fragmentspread的目标。
  • 使fragmentTypefragment的类型条件
  • 使parentType为包含spread的类型选择集合
  • 使applicableTypesGetPossibleTypes(fragmentType)GetPossibleTypes(parentType)的交集。
  • applicableTypes必不为空。

GetPossibleTypes(type):

  • 如果type是一个object/对象类型非,返回包含type的集合
  • 如果type是一个interface/接口类型非,返回实现type的集合
  • 如果type是一个union/联合类型非,返回type的可能类型的集合

Explanatory Text/解释文本

片段在一个类型上声明,并只在这个运行时对象匹配类型条件的时候应用。它们也能在父类型的上下文中解构。片段解构只有在类型条件能够应用与父类型的时候有效。

5.4.2.3.1Object Spreads In Object Scope/对象范围内的对象解构

在一个对象类型范围内,唯一有效的对象类型片段解构是能够应用与范围内同一类型的(片段)。

例如:

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

而下列是无效的

fragment catInDogFragmentInvalid on Dog {
  ... on Cat {
    meowVolume
  }
}
5.4.2.3.2Abstract Spreads in Object Scope/对象范围内的抽象解构

在对象类型的范围,联合或者接口解构仅在对象类型实现了接口或者是联合的成员的时候可用。

例如

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.3Object Spreads In Abstract Scope/抽象范围内的对象解构

在对象类型片段范围内,联合或者接口解构仅在对象类型是接口或者联合的可能类型之一时可用。

例如,下列片段是有效的:

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.4Abstract Spreads in Abstract Scope/抽象范围内的抽象解构

联合或者接口解构能在互相内部使用。只要存在一个对象在可能集合的交集中,这个对象的解构就被视为有效的。

因此下例

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
}

并不有效,因为不存在同时实现了PetSentient的类型。

5.5Values/值

5.5.1Input Object Field Uniqueness/输入对象字段唯一性

Formal Specification/形式规范

  • 对于文档中的每一个输入对象值inputObject
  • 对于inputObject中的每一个inputField
    • 使nameinputField的名字
    • 使fields be all Input Object Fields named name in inputObject.
    • fields必然是仅包含inputField的集合。

Explanatory Text/解释文本

输入对象不能包含多余一个同名的字段,否则存在让部分句法被忽略的歧义。

例如,下列查询并不会通过验证。

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

5.6Directives/指令

5.6.1Directives Are Defined/指令必须预先定义

Formal Specification/形式规范

  • 对于文档中的每一个directive
  • 使directiveNamedirective的名字。
  • 使directiveDefinition为名为directiveName的指令。
  • directiveDefinition必然存在。

Explanatory Text/解释文本

GraphQL服务器定义他们支持的指令,对于每个指令的使用,必须要指令在服务器上可用。

5.6.2Directives Are In Valid Locations/指令必须在有效位置

Formal Specification/形式规范

  • 对于文档中的每一个directive
  • 使directiveNamedirective的名字。
  • 使directiveDefinition为名为directiveName的指令。
  • 使locationsdirectiveDefinition的有效位置。
  • 使adjacent为收到指令影响的AST(抽象语法树)节点。
  • adjacent必须以locations内的元素来表示。

Explanatory Text/解释文本

GraphQL定义了他们支持的指令,在何处支持。每一指令的使用,都必须在服务器声明支持使用的地方。

譬如,下列查询无法通过验证,因为@skipQUERY上,并不是有效位置。

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

5.6.3Directives Are Unique Per Location/每个位置的指令都必须唯一

Formal Specification/形式规范

  • 对于文档中指令可是应用的location
    • 使directives为应用于location的指令集合。
    • 对于directives中的每一个directive
      • 使directiveNamedirective的名字。
      • 使namedDirectivesdirectives中名为directiveName的指令集合。
      • namedDirectives必然是只有一个值的集合。

Explanatory Text/解释文本

指令用于描述其应用的定义的元数据或者表现的改变。当同名的指令多次使用时,期望的源数据或者表现就会变得有歧义,因此一个位置上只允许使用一个指令。

譬如,下列查询不会通过验证,因为@skip在同一个字段上用了两次:

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

但是下面案例是有效的,因为@skip在每个位置只用了一次,即便在查询中同名的字段上用了两次:

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

5.7Variables/变量

5.7.1Variable Uniqueness/变量唯一性

Formal Specification/形式规范

  • 对于文档中的每一个operation
    • 对于operation中定义的每一个variable
      • 使variableNamevariable的名字
      • 使variablesoperation上名为variableName的变量集合
      • variables必然是只有一个值的集合。

Explanatory Text/解释文本

任意操作定了多于一个同名变量,将会变得有歧义,并是无效的。即便重复变量的值是一样的,他也是无效的。

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.2Variable Default Values Are Correctly Typed/变量默认值必须是正确的类型

Formal Specification/形式规范

  • 对于文档中的每一个operation
  • 对于每一个operation上的每一个variable
    • 使variableTypevariable的类型
    • 如果variableType是non‐null非空,则不能有默认值。
    • 如果variable有默认值,则其必须是同样的类型或者能转换到variableType

Explanatory Text/解释文本

操作中定一个变量都是可以定义默认值的,如果对应类型不是non‐null。

例如,下列查询能通过验证。

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.3Variables Are Input Types/变量必须是输入类型

Formal Specification/形式规范

  • document中的每一个operation
  • 对每一个operation上的每一个variable
    • 使variableTypevariable的类型
    • variableTypeLIST或者NON_NULL
      • 使variableTypevariableType引用的类型
    • variableType必然是SCALARENUM或者INPUT_OBJECT类型

Explanatory Text/解释文本

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

对于这些的案例,假设有如下类型系统补充内容:

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.4All Variable Uses Defined/所有变量的使用必须预先定义

Formal Specification/形式规范

  • 对于文档中的每一个operation
    • 对于范围内的每一个variableUsage,变量必须在operation的变量列表中。
    • 使fragmentsoperation传递引用的每一个片段
    • 对于fragments中的每一个fragment
      • 对于fragment范围内的每一个variableUsage,变量必须在operation的变量列表中。

Explanatory Text/解释文本

变量的有效范围是在每一个操作内的,也就是说任何在操作上下文中要使用的变量,必须在那个操作的顶部预先定义。

例如:

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

因为isHousetrainedFragmentvariableIsDefinedUsedInSingleFragment操作的上下文中使用,其参数也被也在这个操作上定义。

另一面,如果操作内引入的片段所使用的参数并没有在操作上定义,那么这个查询是无效的。

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操作引入使用。

5.7.5All Variables Used/所有变量都必须被使用

Formal Specification/形式规范

  • 对于文档中的每一个operation
  • 使variablesoperation上定义的变量。
  • variables中的每一个variable必须至少被使用一次,无论是操作本身使用,还是操作引入的片段通过传递引用使用。

Explanatory Text/解释文本

操作上定义的所有变量必须至少被使用一次,无论是操作本身使用,还是操作引入的片段通过传递引用使用。未使用的变量会导致验证错误。

例如,下列是无效的:

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

因为$atOtherHomes没有被引用过。

这个规则也适用于片段解构的传递引用:

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

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

上面是有效的,因为$atOtherHomesisHousetrainedFragment中被使用过了,其由variableUsedInFragment引入。

如果那个片段没有引用$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.6All Variable Usages are Allowed/所有变量都允许使用

Formal Specification/形式规范

  • document中的每一个operation
  • 使variableUsagesoperation引入的所有传递使用。
  • 对于variableUsages中的每一个variableUsage
    • 使variableTypeoperation上的变量定义的的类型。
    • 使argumentType为传递进去的参数的类型。
    • 使hasDefault为true,如果参数定义有默认值。
    • AreTypesCompatible(argumentType, variableType, hasDefault)必然为true
  • AreTypesCompatible(argumentType,variableType, hasDefault):
    • 如果hasDefault为true,把variableType当作non‐null非空。
    • 如果argumentTypevariableType的内部类型不相同,返回falae
    • 如果argumentTypevariableType的列表纬度不相同,返回falae
    • 如果variableType的任意列表层级不是non‐null,而argument对应的是non‐null,则类型不兼容。

Explanatory Text/解释文本

变量使用必须和它们传递进去的参数兼容。

验证失败会发生在变量在类型上下文完全不匹配和传递可空类型参数给非空类型变量的时候。

类型必须匹配:

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

$intArgInt类型,不能作为参数传递给Boolean类型的booleanArg

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

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!]

6Execution/执行

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

一个用于执行的请求包含以信息的一部分:

有了这些信息,ExecuteRequest()的结果就能生成响应,然后按照下述响应一节来格式化。

6.1Executing Requests/执行请求

要执行请求,执行器必须有一个解析过的Document/文档(本规范的“Query Language”部分有定义过),如果文档中定义了多个操作还需要有选择的操作名,不然文档将被视为只包含一个操作。请求的结果取决于其操作根据下文“Executing Operations”一节执行的结果。

ExecuteRequest(schema, document, operationName, variableValues, initialValue):

GetOperation(document, operationName):

6.1.1Validating Requests/验证请求

如同验证章节中解释的一样,只有通过了所有验证的请求才应该被执行。如果得到了验证错误,它们将被添加到响应中的“errors”列表中,而这个请求也就不执行直接失败。

典型的情况下,验证将在请求执行之前的上下文内瞬间完成,但是在一个相同请求之前已经验证过的情况下,GraphQL服务可能直接执行而不验证。GraphQL服务只应该执行那些某个时间点上没有验证错误也没更改过的请求。

例如:一个请求在开发期已经通过验证,并假设他后面不会改变,或者服务器层验证了一个请求,记住了它的验证结果以避免后续再次验证同样的请求。

6.1.2Coercing Variable Values/转换变量值

如果操作定义了任何变量,然后这些变量的值需要根据变量声明的类型的输入转换规则而转换。如果变量值的输入转换中发生了查询错误,操作会不执行直接失败。

CoerceVariableValues(schema, operation, variableValues):

  • 使coercedValues为一个空的无序Map/映射集。
  • 使variableDefinitionsoperation定义的变量。
  • 对于variableDefinitions中的每一个variableDefinition
    • 使variableNamevariableDefinition的名字。
    • 使variableTypevariableDefinition的期望类型。
    • 使defaultValuevariableDefinition的默认值。
    • 使valuevariableValues中给名为variableName的变量提供的值。
    • 如果value并不存在(variableValues中并未提供):
      • 如果defaultValue存在(包括null):
        • coercedValues添加一个名为variableName值为defaultValue的条目。
      • 或者如果variableType是一个Non‐Nullable/非空类型,抛出一个查询错误。
      • 否则,继续处理下一个变量定义。
    • 或者, 如果value无法根据variableType的输入转换规则转换,抛出一个查询错误。
    • 使coercedValue为根据variableType的输入转换规则转换的结果。
    • coercedValues添加一个名为variableName值为coercedValue的条目。
  • 返回coercedValues
这个算法和CoerceArgumentValues()的很相似。

6.2Executing Operations/执行操作

如果本规范的“Type System”/类型系统一章节所描述,类型系统必须提供一个查询的根级对象类型。如果支持更改或者订阅,它也必须提供更改或者订阅对应的根级对象类型。

6.2.1Query/查询

如果操作是一个查询,那操作的结果就是用查询根级对象类型执行查询的顶层选择集的结果。

执行一个查询的时候可以提供一个初始值。

ExecuteQuery(query, schema, variableValues, initialValue):

  • 使queryTypeschema中的根级查询类型。
  • 断言:queryType是一个对象类型。
  • 使selectionSetquery中的顶层选择集。
  • 使data正常执行ExecuteSelectionSet(selectionSet, queryType, initialValue, variableValues)的结果(允许并行)。
  • 使errors为执行选择集期间产生的任何字段错误
  • 返回一个包含dataerrors的无序映射集。

6.2.2Mutation/更改

如果操作是一个更改,那操作的结果就是用更改根级对象类型执行更改的顶层选择集的结果。这个选择集应该依次执行。

更改的顶层字段被期望用于在下层数据系统上执行副作用操作。依次执行这些更改,以保证副作用操作期间没有竞态条件。

ExecuteMutation(mutation, schema, variableValues, initialValue):

  • 使mutationTypeschema的根级更改类型。
  • 断言:mutationType是一个对象类型。
  • 使selectionSetmutation中的顶层选择集。
  • 使data依次执行ExecuteSelectionSet(selectionSet, mutationType, initialValue, variableValues)的结果
  • 使errors为执行选择集期间产生的任何字段错误
  • 返回一个包含dataerrors的无序映射集。

6.2.3Subscription/订阅

如果操作是一个订阅,那结果是一个事件流,称作“Response Stream”响应流,事件流中的每一个事件即是针对下层“Source Stream”源流上的新事件执行操作的结果。

执行订阅会在服务端创建一个永久的函数,用于将下层源流映射成返回的响应流。

Subscribe(subscription, schema, variableValues, initialValue):

  • 使sourceStreamCreateSourceEventStream(subscription, schema, variableValues, initialValue)执行的结果。
  • 使responseStreamMapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)执行的结果。
  • 返回responseStream
在大型订阅系统中,Subscribe()ExecuteSubscriptionEvent()算法可能运行在分离的服务器上,以保持可预测的规模属性。可在下文章节中看到关于支持大规模订阅的内容。

考虑一个聊天应用案例。客户端发送一个如下请求来订阅投递到聊天室的新消息:

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

当客户端订阅后,任何时候有ID为“123”的新消息投递到聊天室后,对于“sender”和“text”的选择将会被执行然后发布到客户端。例如:

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

”新消息投递到聊天室”可以使用了“Pub‐Sub”发布订阅系统,其中聊天室ID是“topic”,并且每一个“publish”都包含了“sender”发送者和“text”文本。

Event Streams

事件流表示一序列可观测的离散事件。例如,一个“Pub‐Sub”系统当“subscribing to a topic”订阅了一个话题可能产生一个事件流,每一次“publish”发布到话题都会在事件流上产生一个事件。事件流可能产生一序列无尽的事件,也可能在任何时候终止。响应中的事件流可能因为发生错误而终止,也可能仅仅因为没有后续事件发生。观察者可以在任何时候取消订阅而停止观察事件流,这之后就不会从事件流中收到后续事件了。

Supporting Subscriptions at Scale/支持大规模订阅

支持订阅对GraphQL服务器而言是一个显著的变化。查询和更改操作是无状态的,可以通过克隆GraphQL服务器实例而扩展规模。订阅却相反,它是有状态的,要求在这个订阅的生命周期内维持文档、变量和其他上下文。

考虑下你服务中某个机器宕机状态丢失的时候,你的系统的行为。使用分离的专用服务器来处理订阅状态和客户端连接将能提升系统的持久性和可用性。

6.2.3.1Source Stream/源流

源流表示会触发GraphQL对应事件执行的一序列事件。就像字段值的解析一样,创建源流的逻辑也是应用特定的。

CreateSourceEventStream(subscription, schema, variableValues, initialValue):

  • 使subscriptionTypeschema的根级订阅类型。
  • 断言:subscriptionType是一个对象类型。
  • 使selectionSet
  • 使rootFieldselectionSet中的第一个顶层字段。
  • 使argumentValuesCoerceArgumentValues(subscriptionType, rootField, variableValues)的结果。
  • 使fieldStream为运行ResolveFieldEventStream(subscriptionType, initialValue, rootField, argumentValues)的结果。
  • 返回fieldStream

ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues):

  • 使resolversubscriptionType提供的内部函数,用于解析名为fieldName的订阅字段的事件流。
  • 返回使用rootValueargumentValues调用resolver的结果。
这个ResolveFieldEventStream()算法有意与ResolveFieldValue()相似,以保证给任何操作类型定义解析函数时的一致性。
6.2.3.2Response Stream/响应流

下层源流中的每一个事件都会触发订阅使用这个事件作为根值执行选择集。

MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues):

  • 返回产生如下事件的事件流responseStream
  • 对于sourceStream上的每一个event
    • 使response为运行ExecuteSubscriptionEvent(subscription, schema, variableValues, event)的结果。
    • 产生包含response的事件。
  • responseStream完成的时候:终止这个事件流。

ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue):

  • 使subscriptionType为为schema的根级订阅类型。
  • 断言:subscriptionType是一个对象类型。
  • 使selectionSetsubscription中的顶层选择集。
  • 使data正常执行的结果ExecuteSelectionSet(selectionSet, subscriptionType, initialValue, variableValues)(允许并行)。
  • 使errors为执行选择集期间产生的任何字段错误
  • 返回一个包含dataerrors的无序映射集。
这个ExecuteSubscriptionEvent()算法有意与ExecuteQuery()相似,因为这便是每个事件结果如何产生的。
6.2.3.3Unsubscribe/退订

当客户端不再想要收到订阅的载荷时可以通过退订来取消响应流。这可能也同时取消掉了源流。这个是一个清理被这个订阅占用的其他资源的好机会。

Unsubscribe(responseStream)

  • 取消responseStream

6.3Executing Selection Sets/执行选择集

要执行选择集,对象值必须得到,对象类型必须已知,同样还需要知道其需要依次执行还是并列执行。

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

ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues):

resultMap依照出现在query中的顺序排序。这在下列字段集合一节中有更详细的介绍。

6.3.1Normal and Serial Execution/正常序列执行

正常情况下,不论分组字段集合中的条目顺序为何,执行器都能执行(通常是并行执行)。因为除了顶层更改必然有副作用且具有幂等性,字段解析的执行顺序必然不会影响其结果,因此服务器能够以他认为优化的方式自由地执行字段条目。

例如,有下正常执行的分组字段:

{
  birthday {
    month
  }
  address {
    street
  }
}

一个有效的GraphQL执行器可以以任何他选择的顺序来解析这四个字段(然而,birthday必然在month之前,同理addressstreet之前)。

当执行更改时,最顶层的选择集会依序执行。

当依序执行一个分组字段集的时候,执行器必须以每个条目出现在分组字段集中的顺序来决定在结果映射集对应的条目,使得分组映射集中每一个条目完成之后才继续下一个条目。

例如,有以依序执行的选择集:

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

执行器必须依序执行:

  • 执行changeBirthdayExecuteField(),其中CompleteValue()期间,会正常执行{ month }次级选择集。
  • 执行changeAddressExecuteField(),其中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.3.2Field Collection/字段集合

执行之前,通过调用CollectFields(),选择集会被转换成分组字段集。分组字段集中的每一个条目都是共享同一个响应键的列表。这保证了同一个响应键(别名或者字段名)内所有的字段,包含通过片段引入的,都能同时执行。

如果,手机如下选择集的字段会收集到两个a字段的实体和一个b字段的实体:

{
  a {
    subfield1
  }
  ...ExampleFragment
}

fragment ExampleFragment on Query {
  a {
    subfield2
  }
  b
}

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

CollectFields(objectType, selectionSet, variableValues, visitedFragments):

  • 如果未提供visitedFragments,将其初始化为空集。
  • 初始化groupedFields为列表的空的有序集。
  • 对于selectionSet中的每一个selection
    • 如果selection提供了@skip指令,使skipDirective为此指令。
      • 如果skipDirectiveif参数是true或者是variableValues中的一个为true的变量,则继续selectionSet中的下一个selection
    • 如果selection提供了@include指令,使includeDirective为此指令。
      • 如果includeDirective参数不是true或者不是variableValues中的一个为true的变量,则继续selectionSet中的下一个selection
    • 如果selection是一个Field
      • 使responseKeyselection中的响应键。
      • 使groupForResponseKeygroupedFields中的一个responseKey列表;如果不存在这么一个列表,则创建一个空列表。
      • 附加selectiongroupForResponseKey上。
    • 如果selection是一个FragmentSpread
      • 使fragmentSpreadNameselection的名字。
      • 如果fragmentSpreadNamevisitedFragments中,则继续selectionSet中的下一个selection
      • 添加fragmentSpreadNamevisitedFragments上。
      • 使fragment为当前文档中名为fragmentSpreadName的片段。
      • 如果不存在那样的fragment,则继续selectionSet中的下一个selection
      • 使fragmentTypefragment上的类型条件。
      • 如果DoesFragmentTypeApply(objectType, fragmentType)为false,则继续selectionSet中的下一个selection
      • 使fragmentSelectionSetfragment的顶层选择集。
      • 使fragmentGroupedFieldSet为调用CollectFields(objectType, fragmentSelectionSet, visitedFragments)的结果。
      • 对于fragmentGroupedFieldSet中的每一个fragmentGroup
        • 使responseKeyfragmentGroup中所有字段共享的响应键。
        • 使groupForResponseKeygroupedFields中的responseKey列表;如果不存在这么一个列表,则创建一个空列表。
        • 附加fragmentGroup中所有的元素到groupForResponseKey上。
    • 如果selection是一个InlineFragment
      • 使fragmentTypeselection上的类型条件。
      • 如果fragmentType不为nullDoesFragmentTypeApply(objectType, fragmentType)是false,则继续selectionSet中的下一个selection
      • 使fragmentSelectionSetselection的顶层选择集。
      • 使fragmentGroupedFieldSet为调用CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)的结果。
      • 对于fragmentGroupedFieldSet中的每一个fragmentGroup
        • 使responseKey为为fragmentGroup中所有字段共享的响应键
        • 使groupForResponseKeygroupedFields中的responseKey列表;如果不存在这么一个列表,则创建一个空列表。
          • 附加fragmentGroup中所有的元素到groupForResponseKey上。
  • 返回groupedFields

DoesFragmentTypeApply(objectType, fragmentType):

  • 如果fragmentType是一个对象类型:
    • 如果objectTypefragmentType是同类型,返回true,否则返回false
  • 如果fragmentType是一个接口类型:
    • 如果objectTypefragmentType的一个实现,返回true,否则返回false
  • 如果fragmentType是一个联合:
    • 如果objectTypefragmentType的一个可能类型,返回true,否则返回false

6.4Executing Fields/执行字段

分组字段集上的每一个请求字段(定义在被选择的对象类型上)都会得到一个响应映射集中的一个条目。字段执行首先会转换任何提供的技术值,然后解析这个字段的值,最后通过递归执行另一个选择集或者转换一个标量来完成这个字段的值。

ExecuteField(objectType, objectValue, fieldType, fields, variableValues):

6.4.1Coercing Field Arguments/转换字段参数

字段可能包含下层运行时产生正确结果的参数,这些参数定义在类型系统中的字段上,都有一个特定的输入类型:Scalars标量、Enum庙举行、Input Object输入对象,或者这三个的List列表和Non‐Null非空封装。

对于查询中每个参数的位置,可能是一个字面量值,也可能是运行时提供的变量。

CoerceArgumentValues(objectType, field, variableValues):

  • 使coercedValues为空的无序映射集。
  • 使argumentValuesfield提供的参数值。
  • 使fieldNamefield的名字。
  • 使argumentDefinitionsobjectType上字段名fieldName定义的参数。
  • 对于argumentDefinitions中的每一个argumentDefinition
    • 使argumentNameargumentDefinition的名字。
    • 使argumentTypeargumentDefinition的期待类型。
    • 使defaultValueargumentDefinition的默认值。
    • 使valueargumentValues中针对argumentName提供的值。
    • 如果value是一个变量:
      • 使variableNamevalue变量的名字。
      • 使variableValuevariableValues中针对variableName提供的值。
      • 如果variableValue存在(包含null):
        • 添加名为argName值为variableValue的条目到coercedValues
      • 否则,如果defaultValue存在(包含null):
        • 添加名为argName值为defaultValue的条目到coercedValues
      • 否则,如果argumentType是一个Non‐Nullable非空类型,抛出字段错误。
      • 否则,继续下一个参数定义。
    • 否则,如果value不存在(argumentValues中未提供):
      • 如果defaultValue存在(包含null):
        • 添加名为argName值为defaultValue的条目到coercedValues
      • 否则,如果argumentType是一个Non‐Nullable非空类型,抛出字段错误。
      • 否则,继续下一个参数定义。
    • 否则, 如果value不能根据argType的输入转换规则转换,抛出字段错误。
    • 使coercedValue为根据argType的输入转换规则转换value的结果。
    • 添加名为argName值为variableValue的条目到coercedValues
  • 返回coercedValues
变量的值并没有被转换,因为它们应该在执行CoerceVariableValues()中的操作前前就被转换,一个有效的查询必须仅允许合适类型的变量。

6.4.2Value Resolution/值解析

虽然几乎所有的GraphQL执行都能通用化描述,但最终内部系统暴露给GraphQL接口的时候必须提供值。这通过ResolveFieldValue暴露,其生成真实值的类型上给定字段的值,

在案例中,这个可能接收objectType/对象类型Person,其field/字段为"soulMate",这个objectValue/类型值表示John Lennon。它可能被期待得到值表示Yoko Ono。

ResolveFieldValue(objectType, objectValue, fieldName, argumentValues):

  • 使resolverobjectType提供的内部函数,用以决定名为fieldName的字段的解析值。
  • 返回使用objectValueargumentValues调用resolver的结果。
resolver通常可能是异步的,因为其依靠读取下层数据库或者网络服务来产生值。这要求其他的GraphQL执行器能够处理异步执行流。

6.4.3Value Completion/值完成

在解析一个字段的值后,再确认其符合期望返回类型即完成。如果返回值是另一个对象类型,然后字段执行将继续递归。

CompleteValue(fieldType, fields, result, variableValues):

  • 如果fieldType是一个Non‐Null非空类型:
    • 使innerTypefieldType的内部类型。
    • 使completedResult为调用CompleteValue(innerType, fields, result, variableValues)的结果。
    • 如果completedResultnull,抛出一个字段错误。
    • 返回completedResult
  • 如果resultnull (或者另一种类似于的null内部值,譬如undefinedNaN), 返回null
  • 如果fieldType是一个List列表类型:
    • 如果result并不是一个值的集合,抛出一个字段错误。
    • 使innerTypefieldType的内部类型。
    • 返回一个列表,其中每个列表元素都是调用CompleteValue(innerType, fields, resultItem, variableValues)的结果,其中resultItemresult中的每个元素。。
  • 如果fieldType是Scalar标量或者Enum枚举类型:
    • 返回“转换”result的结果,保证其为fieldType的合法值,否则为null
  • 如果fieldType是Object对象,Interface接口,或者Union类型:
    • 如果fieldType是一个Object对象类型。
      • 使objectTypefieldType
    • Otherwise 如果fieldType是一个Interface接口,或者Union类型。
      • 使objectType为ResolveAbstractType(fieldType, result)。
    • 使subSelectionSet为调用MergeSelectionSets(fields)的结果。
    • 返回ExecuteSelectionSet(subSelectionSet, objectType, result, variableValues)正常求值的结果(允许并行)。

Resolving Abstract Types/解析抽象类型

当完成一个具有抽象返回类型的字段时,譬如Interface接口或者Union联合返回类型,首先,抽象类型必须接地到一个相关的对象类型上去,这个由内部系统决定哪一个是合适的。

在面向对象的环境中,譬如Java或者C#,用以决定一个objectValue的对象类型的通用方法是使用这个objectValue的类名。

ResolveAbstractType(abstractType, objectValue):

  • 返回调用系统提供的内部方法的结果,此方法用于决定给定值objectValueabstractType的对象类型。

Merging Selection Sets/合并选择集

当多余一个同名字段并行执行后,他们的选择集在完成这个值的时候被合并,以继续次级选择集的执行。

案例查询中,描述了带次级选择集的同名的并行字段。

{
  me {
    firstName
  }
  me {
    lastName
  }
}

当解析了 me的值后,选择集将合并,所以firstNamelastName可以被解析到一个值上。

MergeSelectionSets(fields):

  • 使selectionSet为一个空列表。
  • 对于 fields中的每一个field
    • 使fieldSelectionSetfield的选择集。
    • 如果fieldSelectionSet是null或者empty,继续下一个字段。
    • fieldSelectionSet中所有的选择集添加到selectionSet
  • 返回selectionSet

6.4.4Errors and Non-Nullability/错误与非空

当解析一个字段时抛出了错误,它应该被当作这个字段返回了null,且错误必须添加到响应的"errors"列表中。

如果一个字段解析的结果就是null(不论是字段的解析函数返回了null还是发生了错误),且那个字段是Non-Null,那么抛出一个字段错误。这个错误必须添加到响应的"errors"列表中。

如果字段因为错误而返回了null,这个错误也被添加到了响应的"errors"列表中,这个"errors"列表后续就不应被影响,即是,错误列表中一个字段上只允许添加一个错误。

因为Non-Null类型不能为null,字段错误将会冒泡到父级字段并被父级字段处理。如果父级字段可能为null,那么它就解析为null,否则如果父级字段也是Non-Null类型,那么这个字段错误将会继续冒泡到上一级父级字段。

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

7Response/响应

如果一个GraphQL服务器收到一个请求,它必须返回一个良好格式化的响应。如果请求操作执行成功,服务器的响应则描述其执行结果,否则描述执行期间遇到的错误。

一个响应可能会包含部分是响应,部分是另一个字段上发生错误的时候,响应中这个字段的值被替换成了null。

7.1Serialization Format/序列化格式

GraphQL并不要求特定的序列化格式,然而客户端应该使用一种支持GraphQL响应中的主要原始类型的序列化格式。特别需要支持一下原始类型:

在执行一节中的CollectFields()的定义中,序列化格式如果支持有序映射集,那么它应该保持请求字段的顺序。只支持无序映射集的序列化格式(譬如JSON)应该保持其语法上的顺序。

产生跟请求一致的字段顺序响应有助于提过调试过程中面向人类的可读性和属性相关的响应解析效率。

系列化格式应支持下列原始类型,然而String可能用于替换下列原始类型。

7.1.1JSON Serialization/JSON序列化

虽然如上所述,GraphQL并不要求一种特定的序列化格式,但是还是比较偏好于JSON。为了风格统一和易于标注,本规范中响应的案例都以JSOn的格式表示,特别需要注意的是,我们的JSON案例中,原始类型都以一下JSON概念来表示:

GraphQL值 JSON值
Map Object
List Array
Null null
String String
Boolean true or false
Int Number
Float Number
Enum Value String

Object Property Ordering/对象属性排序

JSON被描述为无序键值对集合,而键值对在表示上确实有序的方式。换言之,即便JSON字符串{ "name": "Mark", "age": 30 }{ "age": 30, "name": "Mark" }编码了相同的值,但是他们却有不同的属性表示顺序。

选择集的求值结果是有序的,这个顺序来源于请求,并由查询执行器定义,因此JSON序列化可以保持这个顺序,并以这个顺序写入到对象属性中。

譬如,如果查询是{ name, age },则GraphQL服务器响应的JSON会是{ "name": "Mark", "age": 30 }而不是{ "age": 30, "name": "Mark" }

这并没有破坏JSON规范,因为客户端还是会将响应转换成无序映射集的有效值。

7.2Response Format/响应格式

GraphQL的响应应该是映射集类型。

如果操作包含了执行,那其响应映射集的第一个条目的键必须是data,其值将在“Data/数据”一章节中描述。如果操作在执行之前就失败,譬如语法错误、缺失信息或者验证错误,则此条目不应显示。

如果操作遇到了错误,则响应映射集的下一个条目的键必须是errors,其值将在“Errors/错误”一章节中描述。如果操作并没遇到错误,则此条目不应显示。

响应可以包含键为extensions的条目,此条目必须包含值。此条目是作为协议扩展而为实现者保留的,因此并没有对其内容的附加限制要求。

为了保证本协议今后的变化不会破坏已有的服务器和客户端,顶层的响应映射集不能包含上述三个之外的条目。

7.2.1Data/数据

响应中的data条目是请求的操作执行的结果。如果操作是query/查询,输出则是schema查询的根级类型对象,如果操作是mutation/更改,输出则是schema更改的根级类型对象。

如果在执行前遇到错误,结果中将不应有data条目。

如果在执行中遇到错误,并导致不能返回有效响应,则data条目应该为null

7.2.2Errors/错误

响应中的errors是一个非空错误列表,每个错误是一个映射集。

如果在执行请求的操作中未遇到错误,结果中将不应有errors条目。

如果响应结果中没有data条目,则响应中的errors条目不可为空,其必须包含至少一条错误,这个错误应指出为什么没有数据返回。

如果响应中包含data条目(包含值为null的情况),那么响应中的errors条目可以包含执行期间的任何错误。如果执行期发生了错误,那这个错误就应该被包含。

Error result format/错误结果格式

每个错误都必须包含键为message的条目,其包含了针对开发这错误描述,以便修正错误。

如果一个错误能和请求的GraphQL文档特定点所匹配,它应该包含键为locations的条目,其内容为一个定位列表,每个定位都是键为linecolumn的映射集,两者都是从1开始的正数,用以描述相关的语法元素的起始位置。

如果一个错误能和GraphQL结果中的特定字段关联,它必须包含键为path的条目,其描述了响应中哪一个字段面临了错误。这能让客户端鉴别一个null是正常逻辑还是运行时错误。

这个字段应该是一个路径段的列表,从响应根级开始直到关联的字段结束。用于表示字段的路径段应该是字符串类型,而表示列表索引的路径段则应该是从0开始的整数。如果错误发生在别名字段,那对应的路径应该使用别名,因为其表示的是响应中的路径而非查询中的。

例如,如果下列查询中,获取一个朋友的名字失败了:

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

响应会像这样:

{
  "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字段在schema中声明为Non-Null非空,那么查询结果可能不同,但是错误还是一样。

{
  "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服务器可以给error提供附加的条目,以用来展示更为用的或者机器可读的错误,当然,今后本规范也可能为error引入附加条目。

AAppendix: Notation Conventions/A.附录:符号约定

This specification document contains a number of notation conventions used to describe technical concepts such as language grammar and semantics as well as runtime algorithms.

This appendix seeks to explain these notations in greater detail to avoid ambiguity.

A.1Context-Free Grammar/无上下文的语法

A context‐free grammar consists of a number of productions. Each production has an abstract symbol called a “non‐terminal” as its left‐hand side, and zero or more possible sequences of non‐terminal symbols and or terminal characters as its right‐hand side.

Starting from a single goal non‐terminal symbol, a context‐free grammar describes a language: the set of possible sequences of characters that can be described by repeatedly replacing any non‐terminal in the goal sequence with one of the sequences it is defined by, until all non‐terminal symbols have been replaced by terminal characters.

Terminals are represented in this document in a monospace font in two forms: a specific Unicode character or sequence of Unicode characters (ex. = or terminal), and a pattern of Unicode characters defined by a regular expression (ex /[0-9]+/).

Non‐terminal production rules are represented in this document using the following notation for a non‐terminal with a single definition:

While using the following notation for a production with a list of definitions:

NonTerminalWithManyDefinitions
OtherNonTerminalterminal
terminal

A definition may refer to itself, which describes repetitive sequences, for example:

A.2Lexical and Syntactical Grammar/词句上的语法

The GraphQL language is defined in a syntactic grammar where terminal symbols are tokens. Tokens are defined in a lexical grammar which matches patterns of source characters. The result of parsing a sequence of source Unicode characters produces a GraphQL AST.

A Lexical grammar production describes non‐terminal “tokens” by patterns of terminal Unicode characters. No “whitespace” or other ignored characters may appear between any terminal Unicode characters in the lexical grammar production. A lexical grammar production is distinguished by a two colon :: definition.

Word
/[A-Za-z]+/

A Syntactical grammar production describes non‐terminal “rules” by patterns of terminal Tokens. Whitespace and other ignored characters may appear before or after any terminal Token. A syntactical grammar production is distinguished by a one colon : definition.

Sentence
NounVerb

A.3Grammar Notation/语法符号

This specification uses some additional notation to describe common patterns, such as optional or repeated patterns, or parameterized alterations of the definition of a non‐terminal. This section explains these short‐hand notations and their expanded definitions in the context‐free grammar.

Constraints

A grammar production may specify that certain expansions are not permitted by using the phrase “but not” and then indicating the expansions to be excluded.

For example, the production:

SafeName
NameSevenCarlinWords

means that the nonterminal SafeName may be replaced by any sequence of characters that could replace Name provided that the same sequence of characters could not replace SevenCarlinWords.

A grammar may also list a number of restrictions after “but not” separated by “or”.

For example:

Optionality and Lists

A subscript suffix “Symbolopt” is shorthand for two possible sequences, one including that symbol and one excluding it.

As an example:

Sentence
NounVerbAdverbopt

is shorthand for

Sentence
NounVerb
NounVerbAdverb

A subscript suffix “Symbollist” is shorthand for a list of one or more of that symbol.

As an example:

Book
CoverPagelistCover

is shorthand for

Book
CoverPage_listCover

Parameterized Grammar Productions

A symbol definition subscript suffix parameter in braces “SymbolParam” is shorthand for two symbol definitions, one appended with that parameter name, the other without. The same subscript suffix on a symbol is shorthand for that variant of the definition. If the parameter starts with “?”, that form of the symbol is used if in a symbol definition with the same parameter. Some possible sequences can be included or excluded conditionally when respectively prefixed with “[+Param]” and “[~Param]”.

As an example:

ExampleParam
A
BParam
CParam
ParamD
ParamE

is shorthand for

Example
A
B_param
C
E
Example_param
A
B_param
C_param
D

A.4Grammar Semantics/语法语义

This specification describes the semantic value of many grammar productions in the form of a list of algorithmic steps.

For example, this describes how a parser should interpret a string literal:

StringValue
""
  1. Return an empty Unicode character sequence.
StringValue
  1. Return the Unicode character sequence of all StringCharacter Unicode character values.

A.5Algorithms/算法

This specification describes some algorithms used by the static and runtime semantics, they’re defined in the form of a function‐like syntax along with a list of algorithmic steps to take.

For example, this describes if a fragment should be spread into place given a runtime objectType and the fragment’s fragmentType:

doesFragmentTypeApply(objectType, fragmentType)
  1. If fragmentType is an Object Type:
    1. if objectType and fragmentType are the same type, return true, otherwise return false.
  2. If fragmentType is an Interface Type:
    1. if objectType is an implementation of fragmentType, return true otherwise return false.
  3. If fragmentType is a Union:
    1. if objectType is a possible type of fragmentType, return true otherwise return false.

BAppendix: Grammar Summary/A.附录:语法总结

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

B.1Ignored Tokens/忽略符号

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.2Lexical Tokens/词法符号

Punctuator
!$()...:=@[]{|}
Name
/[_A-Za-z][_0-9A-Za-z]*/
Digit
0123456789
EscapedUnicode
/[0-9A-Fa-f]{4}/

B.3Query Document/查询文档

OperationType
querymutationsubscription
BooleanValue
truefalse
EnumValue
Nametruefalsenull
ListValueConst
[]
[ValueConstlist]
ObjectValueConst
{}
{ObjectFieldConstlist}
  1. 1Overview/概览
  2. 2Language/语言
    1. 2.1Source Text/源文本
      1. 2.1.1Unicode/统一码
      2. 2.1.2White Space/空白符
      3. 2.1.3Line Terminators/行终止符
      4. 2.1.4Comments/注释
      5. 2.1.5Insignificant Commas/无语义逗号
      6. 2.1.6Lexical Tokens/词法记号
      7. 2.1.7Ignored Tokens/无语义记号
      8. 2.1.8Punctuators/标点
      9. 2.1.9Names/命名
    2. 2.2Query Document/查询文档
    3. 2.3Operations/操作
    4. 2.4Selection Sets/选择集合
    5. 2.5Fields/字段
    6. 2.6Arguments/参数
    7. 2.7Field Alias/字段别名
    8. 2.8Fragments/片段
      1. 2.8.1Type Conditions/类型条件
      2. 2.8.2Inline Fragments/内联片段
    9. 2.9Input Values/输入值
      1. 2.9.1Int Value/整数值
      2. 2.9.2Float Value/浮点值
      3. 2.9.3Boolean Value/布尔值
      4. 2.9.4String Value/字符串值
      5. 2.9.5Null Value/空值
      6. 2.9.6Enum Value/枚举值
      7. 2.9.7List Value/列表值
      8. 2.9.8Input Object Values/输入型对象值
    10. 2.10Variables/变量
    11. 2.11Input Types/输入类型
    12. 2.12Directives/指令
  3. 3Type System/类型系统
    1. 3.1Types/类型
      1. 3.1.1Scalars/标量
        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.2Objects/对象
        1. 3.1.2.1Object Field Arguments/对象字段参数
        2. 3.1.2.2Object Field deprecation/对象字段弃用
        3. 3.1.2.3Object type validation/对象类型验证
      3. 3.1.3Interfaces/接口
        1. 3.1.3.1Interface type validation/接口类型验证
      4. 3.1.4Unions/联合
        1. 3.1.4.1Union type validation/联合类型验证
      5. 3.1.5Enums/枚举型
      6. 3.1.6Input Objects/输入对象
        1. 3.1.6.1Input Object type validation/输入对象类型验证
      7. 3.1.7Lists/列表型
      8. 3.1.8Non-Null/非空型
    2. 3.2Directives/指令
      1. 3.2.1@skip
      2. 3.2.2@include
    3. 3.3Initial types/初始类型
  4. 4Introspection/内省
    1. 4.1General Principles/基本原则
      1. 4.1.1Naming conventions/命名约定
      2. 4.1.2Documentation/文档
      3. 4.1.3Deprecation/弃用
      4. 4.1.4Type Name Introspection/类型命名内省
    2. 4.2Schema Introspection/Schema内省
      1. 4.2.1The __Type Type/__Type类型
      2. 4.2.2Type Kinds/类型种类
        1. 4.2.2.1Scalar/标量
        2. 4.2.2.2Object/对象
        3. 4.2.2.3Union/联合
        4. 4.2.2.4Interface/接口
        5. 4.2.2.5Enum/枚举型
        6. 4.2.2.6Input Object/输入对象
        7. 4.2.2.7List/列表
        8. 4.2.2.8Non-Null/非空
        9. 4.2.2.9Combining List and Non-Null/列表和非空的组合
      3. 4.2.3The __Field Type/__Field类型
      4. 4.2.4The __InputValue Type/__InputValue类型
      5. 4.2.5The __EnumValue Type/__EnumValue类型
      6. 4.2.6The __Directive Type/__Directive类型
  5. 5Validation/验证
    1. 5.1Operations/操作
      1. 5.1.1Named Operation Definitions/具名操作定义
        1. 5.1.1.1Operation Name Uniqueness/操作名唯一性
      2. 5.1.2Anonymous Operation Definitions/匿名操作定义
        1. 5.1.2.1Lone Anonymous Operation/单独匿名操作
      3. 5.1.3Subscription Operation Definitions/订阅操作定义
        1. 5.1.3.1Single root field/单个根级字段
    2. 5.2Fields/字段
      1. 5.2.1Field Selections on Objects, Interfaces, and Unions Types/对象、接口和联合上的字段选择
      2. 5.2.2Field Selection Merging/字段选择合并
      3. 5.2.3Leaf Field Selections/叶子节点选择
    3. 5.3Arguments/参数
      1. 5.3.1Argument Names/参数名
      2. 5.3.2Argument Uniqueness/参数唯一性
      3. 5.3.3Argument Values Type Correctness/参数值类型正确性
        1. 5.3.3.1Compatible Values/兼容值
        2. 5.3.3.2Required Non-Null Arguments/必要非空参数
    4. 5.4Fragments/片段
      1. 5.4.1Fragment Declarations/片段声明
        1. 5.4.1.1Fragment Name Uniqueness/片段名唯一性
        2. 5.4.1.2Fragment Spread Type Existence/片段解构类型存在性
        3. 5.4.1.3Fragments On Composite Types/组合类型上的片段
        4. 5.4.1.4Fragments Must Be Used/必须使用的片段
      2. 5.4.2Fragment Spreads/片段解构
        1. 5.4.2.1Fragment spread target defined/片段解构目标必须预先定义
        2. 5.4.2.2Fragment spreads must not form cycles/片段解构不可造成循环
        3. 5.4.2.3Fragment spread is possible/片段结构必须可行
          1. 5.4.2.3.1Object Spreads In Object Scope/对象范围内的对象解构
          2. 5.4.2.3.2Abstract Spreads in Object Scope/对象范围内的抽象解构
          3. 5.4.2.3.3Object Spreads In Abstract Scope/抽象范围内的对象解构
          4. 5.4.2.3.4Abstract Spreads in Abstract Scope/抽象范围内的抽象解构
    5. 5.5Values/值
      1. 5.5.1Input Object Field Uniqueness/输入对象字段唯一性
    6. 5.6Directives/指令
      1. 5.6.1Directives Are Defined/指令必须预先定义
      2. 5.6.2Directives Are In Valid Locations/指令必须在有效位置
      3. 5.6.3Directives Are Unique Per Location/每个位置的指令都必须唯一
    7. 5.7Variables/变量
      1. 5.7.1Variable Uniqueness/变量唯一性
      2. 5.7.2Variable Default Values Are Correctly Typed/变量默认值必须是正确的类型
      3. 5.7.3Variables Are Input Types/变量必须是输入类型
      4. 5.7.4All Variable Uses Defined/所有变量的使用必须预先定义
      5. 5.7.5All Variables Used/所有变量都必须被使用
      6. 5.7.6All Variable Usages are Allowed/所有变量都允许使用
  6. 6Execution/执行
    1. 6.1Executing Requests/执行请求
      1. 6.1.1Validating Requests/验证请求
      2. 6.1.2Coercing Variable Values/转换变量值
    2. 6.2Executing Operations/执行操作
      1. 6.2.1Query/查询
      2. 6.2.2Mutation/更改
      3. 6.2.3Subscription/订阅
        1. 6.2.3.1Source Stream/源流
        2. 6.2.3.2Response Stream/响应流
        3. 6.2.3.3Unsubscribe/退订
    3. 6.3Executing Selection Sets/执行选择集
      1. 6.3.1Normal and Serial Execution/正常序列执行
      2. 6.3.2Field Collection/字段集合
    4. 6.4Executing Fields/执行字段
      1. 6.4.1Coercing Field Arguments/转换字段参数
      2. 6.4.2Value Resolution/值解析
      3. 6.4.3Value Completion/值完成
      4. 6.4.4Errors and Non-Nullability/错误与非空
  7. 7Response/响应
    1. 7.1Serialization Format/序列化格式
      1. 7.1.1JSON Serialization/JSON序列化
    2. 7.2Response Format/响应格式
      1. 7.2.1Data/数据
      2. 7.2.2Errors/错误
  8. AAppendix: Notation Conventions/A.附录:符号约定
    1. A.1Context-Free Grammar/无上下文的语法
    2. A.2Lexical and Syntactical Grammar/词句上的语法
    3. A.3Grammar Notation/语法符号
    4. A.4Grammar Semantics/语法语义
    5. A.5Algorithms/算法
  9. BAppendix: Grammar Summary/A.附录:语法总结
    1. B.1Ignored Tokens/忽略符号
    2. B.2Lexical Tokens/词法符号
    3. B.3Query Document/查询文档