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