工作草案 – 2015年7月
简介
这是 GraphQL 的 RFC 草案规范,GraphQL 是一种查询语言,由 Facebook 于 2012 年创建,用于描述客户端-服务器应用程序的数据模型的功能和需求。 此标准的制定始于 2015 年。 GraphQL 是一种新兴且不断发展的语言,尚未完成。 本规范的未来版本将继续进行重大改进。
版权声明
版权所有 (c) 2015, Facebook, Inc.。 保留所有权利。
只要满足以下条件,允许以源代码和二进制形式进行再分发和使用,无论是否修改
本软件由版权所有者和贡献者“按原样”提供,并且不承担任何明示或暗示的保证,包括但不限于对适销性和特定用途适用性的暗示保证。 在任何情况下,版权所有者或贡献者均不对任何直接、间接、附带、特殊、惩戒性或后果性损害(包括但不限于采购替代商品或服务; 使用损失、数据或利润; 或业务中断),无论因何种原因以及基于何种责任理论,无论是合同、严格责任还是侵权行为(包括疏忽或其他),以任何方式因使用本软件而引起,即使已被告知发生此类损害的可能性。
GraphQL 是一种查询语言,旨在通过提供直观且灵活的语法和系统来描述客户端应用程序的数据需求和交互,从而构建客户端应用程序。
例如,此 GraphQL 请求将从 Facebook 的 GraphQL 实现中接收 id 为 4 的用户的名称。
{
user(id: 4) {
name
}
}
这将产生结果数据(以 JSON 格式)
{
"user": {
"name": "Mark Zuckerberg"
}
}
GraphQL 不是一种能够进行任意计算的编程语言,而是一种用于查询应用程序服务器的语言,这些服务器具有本规范中定义的功能。 GraphQL 不强制要求实现它的应用程序服务器使用特定的编程语言或存储系统。 相反,应用程序服务器采用其功能并将其映射到 GraphQL 编码的统一语言、类型系统和理念。 这为产品开发提供了友好的统一接口,并为工具构建提供了强大的平台。
GraphQL 有许多设计原则
由于这些原则,GraphQL 是一个强大而高效的环境,用于构建客户端应用程序。 产品开发人员和设计师针对正在运行的 GraphQL 服务器构建应用程序(在高质量工具的支持下),无需阅读大量文档,也无需接受任何或很少的正式培训,即可快速变得高效。 为了实现这种体验,必须有人来构建这些服务器和工具。
以下正式规范可作为这些构建者的参考。 它描述了语言及其语法; 类型系统和用于查询它的内省系统; 以及执行和验证引擎及其驱动算法。 本规范的目标是为 GraphQL 工具、客户端库和服务器实现的生态系统提供基础和框架(跨越组织和平台),而该生态系统尚待构建。 我们期待与社区合作来实现这一目标。
客户端使用 GraphQL 语言从 GraphQL 服务器发出请求。 我们将这些请求称为文档。 文档可能包含操作(查询和变更都是操作)和片段,片段是允许查询重用的通用组合单元。
只有当 GraphQL 文档包含操作时,服务器才能执行它们。 但是,不包含操作的文档仍然可以被解析和验证,以允许客户端跨多个文档表示单个请求。
GraphQL 文档可能包含多个操作,只要它们被命名即可。 当向 GraphQL 服务器提交包含多个操作的文档时,还必须提供所需操作的名称。
如果文档仅包含一个查询操作,则该操作可以用简写形式表示,该形式省略了 query 关键字和查询名称。
GraphQL 模型有两种类型的操作
每个操作都由自定义名称和字段选择表示。
查询简写
如果查询没有变量、指令或名称,则可以省略 query
关键字。 这意味着它必须是文档中唯一的查询。
顶级选择集中的字段通常表示某种可全局访问的信息,供您的应用程序和当前查看者使用。 一些典型的全局字段示例
# `me` could represent the currently logged in user.
query getMe {
me { /* ... */ }
}
# `user` represents one of many users in a graph of data.
query getZuck {
user(id: 4) { /* ... */ }
}
每个字段都有特定的类型,除非它是标量,否则子字段必须始终通过字段选择显式声明。 例如,当从某个用户对象获取数据时
query getZuck {
user(id: 4) {
id,
firstName,
lastName
}
}
字段选择可以进一步组合,以显式声明嵌套类型的所有子字段。 所有查询都必须指定到标量字段。
query getZuck {
user(id: 4) {
id,
firstName,
lastName,
birthday {
month,
day
}
}
}
字段可以接受参数。 这些参数通常直接映射到 GraphQL 服务器实现中的函数参数。 我们已经在上面的全局字段中看到了参数的使用。
在此示例中,我们要查询特定大小的用户个人资料图片
{
user(id: 4) {
id,
name,
profilePic(size: 100)
}
}
给定字段可以存在许多参数
{
user(id: 4) {
id,
name,
profilePic(width: 100, height: 50)
}
}
参数是无序的
字段参数可以以任何语法顺序提供,并保持相同的语义含义。
这两个查询在语义上是相同的
{
picture(width: 200, height: 100)
}
{
picture(height: 100, width: 200)
}
默认情况下,响应对象中的键将使用查询的字段名称。 但是,您可以通过指定别名来定义不同的名称。
在此示例中,我们可以获取两个不同大小的个人资料图片,并确保结果对象不会有重复的键
{
user(id: 4) {
id,
name,
smallPic: profilePic(size: 64),
bigPic: profilePic(size: 1024)
}
}
这将返回结果
{
"user": {
"id": 4,
"name": "Mark",
"smallPic": "https://cdn.site.io/pic-4-64.jpg",
"bigPic": "https://cdn.site.io/pic-4-1024.jpg"
}
}
由于查询的顶层是一个字段,因此也可以为其指定别名
{
zuck: user(id: 4) {
id
name
}
}
返回结果
{
"zuck": {
"id": 4,
"name": "Mark Zuckerberg"
}
}
如果提供了别名,则字段的响应键是其别名,否则是字段的名称。
字段参数和指令都接受输入值。 输入值可以指定为变量或以内联方式表示为字面量。 输入值可以是标量、枚举或输入对象。 列表和输入对象也可能包含变量。
Int
Int 是一个不带小数点的数字(例如 1
)。
Float
Float 数字始终包含小数点(例如 1.0
),并且可以选择性地包含指数(例如 6.0221413e23
)。
Boolean
关键字 true
和 false
表示两个布尔值。
String
字符串是用双引号 "
包裹的字符列表。 (例如 "Hello World"
)。 空格在字符串中很重要。
枚举值
枚举值表示为不带引号的名称(例如 MOBILE_WEB
)。 建议枚举值使用“全大写”。 枚举值仅在已知精确枚举类型的上下文中使用。 因此,不必在字面量中使用枚举类型名称。
列表
列表是用方括号 [ ]
包裹的有序值序列。 数组字面量的值可以是任何值字面量或变量(例如 [1, 2, 3]
)。
逗号在整个 GraphQL 中是可选的,因此允许使用尾随逗号,并且重复的逗号不代表缺失值。
输入对象
输入对象字面量是用花括号 { }
包裹的键值输入值的无序列表。 对象字面量的值可以是任何输入值字面量或变量(例如 { name: "Hello world", score: 1.0 }
)。 我们将输入对象的字面量表示形式称为“对象字面量”。
GraphQL 查询可以使用变量进行参数化,从而最大限度地提高查询重用率,并避免客户端在运行时进行昂贵的字符串构建。
变量必须在操作的顶部定义,并具有全局作用域。
在此示例中,我们要根据特定设备的大小获取个人资料图片大小
query getZuckProfile($devicePicSize: Int) {
user(id: 4) {
id,
name,
profilePic(size: $devicePicSize)
}
}
这些变量的值与 GraphQL 查询一起提供,因此它们可以在执行期间被替换。 如果为变量值提供 JSON,我们可以运行此查询并请求大小为 60 的 profilePic,通过
{
"devicePicSize": 60
}
在某些情况下,您需要提供选项来更改 GraphQL 的执行行为,而字段参数不足以满足需求,例如有条件地跳过字段。 指令通过 @name
提供此功能,并且可以指定为不带参数或带有值参数使用。
指令可用于根据提供的布尔值有条件地在查询中包含字段。 在这个人为设计的示例中,将查询 experimentalField,而不会查询 controlField。
query myQuery($someTest: Boolean) {
experimentalField @if: $someTest
controlField @unless: $someTest
}
随着未来版本的 GraphQL 采用新的可配置执行功能,它们可能会通过指令公开。
片段允许重用查询的重复部分。 它是 GraphQL 中的组合单元。
例如,如果我们想获取有关共同好友以及某些用户好友的一些常用信息
query noFragments
{
user(id: 4) {
friends(first: 10) {
id
name
profilePic(size: 50)
}
mutualFriends(first: 10) {
id
name
profilePic(size: 50)
}
}
}
重复的字段可以提取到片段中,并由父片段或查询组合。
query withFragments {
user(id: 4) {
friends(first: 10) { ...friendFields }
mutualFriends(first: 10) { ...friendFields }
}
}
fragment friendFields on User {
id
name
profilePic(size: 50)
}
片段通过使用展开运算符 (...
) 来使用。 片段选择的所有字段都将添加到与片段调用位于同一级别的查询字段选择中。 这通过多个级别的片段展开发生。
例如
query withNestedFragments
{
user(id: 4) {
friends(first: 10) { ...friendFields }
mutualFriends(first: 10) { ...friendFields }
}
}
fragment friendFields on User {
id
name
..standardProfilePic
}
fragment standardProfilePic on User {
profilePic(size: 50)
}
查询 noFragments
、withFragments
和 withNestedFragments
都产生相同的响应对象。
片段必须指定它们适用的类型。 在此示例中,friendFields
可以在查询 User
的上下文中使用。
片段不能在任何输入值(标量、枚举或输入对象)上指定。
片段可以在对象类型、接口和联合上指定。
仅当片段正在操作的对象的具体类型与片段的类型匹配时,片段内的选择才会返回值。
例如,在 Facebook 数据模型上的此查询中
query FragmentTyping
{
profiles(handles: ["zuck", "cocacola"]) {
handle
...userFragment
...pageFragment
}
}
fragment userFragment on User {
friends { count }
}
fragment pageFragment on Page {
likers { count }
}
profiles
根字段返回一个列表,其中每个元素可以是 Page
或 User
。 当 profiles
结果中的对象是 User
时,将存在 friends
,而不会存在 likers
。 相反,当结果是 Page
时,将存在 likers
,而不会存在 friends
。
{
"profiles" : [
{
"handle" : "zuck",
"friends" : { "count" : 1234 }
},
{
"handle" : "cocacola",
"likers" : { "count" : 90234512 }
}
]
}
查询变量可以在片段中使用。 查询变量在给定操作中具有全局作用域,因此在片段中使用的变量必须在任何传递使用该片段的顶级操作中声明。 如果变量在片段中被引用,并且被不定义该变量的操作包含,则该操作无法执行。
片段可以内联定义以进行查询。 这样做是为了根据字段的运行时类型有条件地执行字段。 标准片段包含的此功能已在 query FragmentTyping
示例中演示。 我们可以使用内联片段完成相同的事情。
query InlineFragmentTyping {
profiles(handles: ["zuck", "cocacola"]) {
handle
... on User {
friends { count }
}
... on Page {
likers { count }
}
}
}
GraphQL 类型系统描述了 GraphQL 服务器的功能,并用于确定查询是否有效。 类型系统还描述了查询变量的输入类型,以确定运行时提供的值是否有效。
GraphQL 服务器的功能被称为该服务器的“模式”。 模式根据其支持的类型和指令来定义。
给定的 GraphQL 模式本身必须在内部有效。 本节描述了此验证过程的相关规则。
GraphQL 模式由每种操作类型的根类型表示:查询和变更; 这决定了这些操作在类型系统中开始的位置。
GraphQL 模式中的所有类型都必须具有唯一的名称。 提供的任何两个类型都不得具有相同的名称。 提供的任何类型都不得具有与任何内置类型(包括标量和内省类型)冲突的名称。
GraphQL 模式中的所有指令都必须具有唯一的名称。 指令和类型可以共享相同的名称,因为它们之间没有歧义。
任何 GraphQL 模式的基本单元都是类型。 GraphQL 中有八种类型。
最基本的类型是 Scalar
。 标量表示原始值,例如字符串或整数。 通常,标量字段的可能响应是可枚举的。 在这些情况下,GraphQL 提供了 Enum
类型,其中类型指定了有效响应的空间。
标量和枚举构成响应树中的叶子; 中间级别是 Object
类型,它定义了一组字段,其中每个字段是系统中的另一种类型,从而允许定义任意类型层次结构。
GraphQL 支持两种抽象类型:接口和联合。
Interface
定义字段列表; 实现该接口的 Object
类型保证实现这些字段。 每当类型系统声明它将返回接口时,它将返回有效的实现类型。
Union
定义了可能的类型列表; 与接口类似,每当类型系统声明将返回联合时,将返回可能的类型之一。
到目前为止,所有类型都假定为既可为空又为单数:例如,标量字符串返回 null 或单数字符串。 类型系统可能需要定义它返回其他类型的列表; 为此提供了 List
类型,并包装了另一种类型。 同样,Non-Null
类型包装了另一种类型,并表示结果永远不会为 null。 这两种类型被称为“包装类型”; 非包装类型被称为“基本类型”。 包装类型具有一个底层的“基本类型”,通过不断解包类型直到找到基本类型为止。
最后,通常将复杂结构作为 GraphQL 查询的输入很有用; Input Object
类型允许模式精确定义客户端在这些查询中期望的数据。
顾名思义,标量表示 GraphQL 中的原始值。 GraphQL 响应采用分层树的形式; 这些树上的叶子是 GraphQL 标量。
所有 GraphQL 标量都可以表示为字符串,尽管根据所使用的响应格式,给定标量类型可能存在更合适的原始类型,并且服务器应在适当时使用这些类型。
GraphQL 提供了许多内置标量,但类型系统可以添加具有语义含义的其他标量。 例如,GraphQL 系统可以定义一个名为 Time
的标量,它在序列化为字符串时,承诺符合 ISO-8601。 当查询 Time
类型的字段时,您可以依靠使用 ISO-8601 解析器解析结果并为时间使用特定于客户端的原始类型的功能。 另一个潜在有用的自定义标量的示例是 Url
,它序列化为字符串,但服务器保证它是有效的 URL。
结果强制转换
GraphQL 服务器在准备给定标量类型的字段时,必须维护标量类型描述的约定,方法是强制转换值或产生错误。
例如,GraphQL 服务器可能正在准备一个标量类型为 Int
的字段,并遇到浮点数。 由于服务器不得通过产生非整数来破坏约定,因此服务器应截断小数值并仅产生整数值。 如果服务器遇到布尔值 true
,则应返回 1
。 如果服务器遇到字符串,则可以尝试解析字符串以获取十进制整数值。 如果服务器遇到某些无法合理强制转换为 Int
的值,则必须引发字段错误。
由于客户端无法观察到此强制转换行为,因此强制转换的精确规则留给实现。 唯一的要求是服务器必须产生符合预期标量类型的值。
输入强制转换
如果 GraphQL 服务器期望标量类型作为字段参数的输入,则强制转换是可观察的,并且规则必须明确定义。 如果输入值与强制转换规则不匹配,则必须引发查询错误。
GraphQL 具有不同的常量字面量来表示整数和浮点输入值,并且强制转换规则的应用可能因遇到的输入值类型而异。 GraphQL 可以通过查询变量进行参数化,查询变量的值通常在通过 HTTP 等传输发送时进行序列化。 由于某些常见的序列化(例如 JSON)不区分整数值和浮点值,如果它们具有空的分数部分(例如 1.0
),则它们被解释为整数输入值,否则被解释为浮点输入值。
GraphQL 提供了一组基本的、明确定义的标量类型。 GraphQL 服务器应支持所有这些类型,并且提供具有这些名称的类型的 GraphQL 服务器必须遵守下面描述的行为。
Int 标量类型表示有符号 32 位数字非小数值。 支持 32 位整数或数字类型的响应格式应使用该类型来表示此标量。
结果强制转换
GraphQL 服务器应在可能的情况下将非 int 原始值强制转换为 Int,否则必须引发字段错误。 这方面的示例可能包括为浮点数 1.0
返回 1
,或为字符串 "2"
返回 2
。
输入强制转换
当预期作为输入类型时,仅接受整数输入值。 所有其他输入值,包括具有数字内容的字符串,都必须引发查询错误,指示类型不正确。 如果整数输入值表示的值小于 -231 或大于或等于 231,则应引发查询错误。
Float 标量类型表示由 IEEE 754 指定的有符号双精度小数值。 支持适当的双精度数字类型的响应格式应使用该类型来表示此标量。
结果强制转换
GraphQL 服务器应在可能的情况下将非浮点原始值强制转换为 Float,否则必须引发字段错误。 这方面的示例可能包括为整数 1
返回 1.0
,或为字符串 "2"
返回 2.0
。
输入强制转换
当预期作为输入类型时,整数和浮点输入值均被接受。整数输入值会被强制转换为浮点数,方法是添加一个空的小数部分,例如,对于整数输入值 1
,转换为 1.0
。所有其他输入值,包括包含数字内容的字符串,都必须引发查询错误,指示类型不正确。如果整数输入值表示的值无法用 IEEE 754 表示,则应引发查询错误。
String 标量类型表示文本数据,以 UTF‐8 字符序列表示。String 类型最常被 GraphQL 用于表示自由格式的人类可读文本。所有响应格式都必须支持字符串表示形式,并且此处必须使用该表示形式。
结果强制转换
GraphQL 服务器应尽可能将非字符串原始值强制转换为 String,否则它们必须引发字段错误。例如,这可能包括为布尔值 true 返回字符串 "true"
,或为整数 1
返回字符串 "1"
。
输入强制转换
当预期作为输入类型时,仅接受有效的 UTF‐8 字符串输入值。所有其他输入值都必须引发查询错误,指示类型不正确。
Boolean 标量类型表示 true
或 false
。响应格式应使用内置的布尔类型(如果支持);否则,它们应使用整数 1
和 0
的表示形式。
结果强制转换
GraphQL 服务器应尽可能将非布尔原始值强制转换为 Boolean,否则它们必须引发字段错误。例如,这可能包括为任何非零数字返回 true
。
输入强制转换
当预期作为输入类型时,仅接受布尔输入值。所有其他输入值都必须引发查询错误,指示类型不正确。
ID 标量类型表示唯一标识符,通常用于重新获取对象或作为缓存的键。ID 类型以与 String
相同的方式序列化;但是,它不旨在人类可读。虽然它通常是数字,但它应始终序列化为 String
。
结果强制转换
GraphQL 与 ID 格式无关,并序列化为字符串以确保各种 ID 可能代表的格式之间的一致性,从小的自增数字到大的 128 位随机数,到 base64 编码的值,或格式如 GUID 的字符串值。
GraphQL 服务器应根据他们期望的 ID 格式进行适当的强制转换,当强制转换不可能时,他们必须引发字段错误。
输入强制转换
当预期作为输入类型时,任何字符串(例如 "4"
)或整数(例如 4
)输入值都应根据给定 GraphQL 服务器期望的 ID 格式强制转换为 ID。任何其他输入值,包括浮点输入值(例如 4.0
),都必须引发查询错误,指示类型不正确。
GraphQL 查询是分层的和组合的,描述了一个信息树。虽然标量类型描述了这些分层查询的叶子值,但对象描述了中间层。
GraphQL 对象表示命名字段的列表,每个字段产生特定类型的值。对象值序列化为无序映射,其中查询的字段名称(或别名)是键,而评估字段的结果是值。
例如,类型 Person
可以描述为
type Person {
name: String
age: Int
picture: Url
}
其中 name
是将产生 String
值的字段,age
是将产生 Int
值的字段,而 picture
是将产生 Url
值的字段。
对象值的查询必须至少选择一个字段。字段的这种选择将产生一个无序映射,其中仅包含查询对象的子集。只有在对象类型上声明的字段才能在该对象上进行有效查询。
例如,选择 Person
的所有字段
{
name,
age,
picture
}
将产生对象
{
"name": "Mark Zuckerberg",
"age": 30,
"picture": "http://some.cdn/picture.jpg"
}
而选择字段的子集
{
name,
age
}
必须仅产生该子集
{
"name": "Mark Zuckerberg",
"age": 30
}
对象类型的字段可以是标量、枚举、另一个对象类型、接口或联合。此外,它可以是任何包装类型,其底层基本类型是这五种类型之一。
例如,Person
类型可能包含 relationship
type Person {
name: String
age: Int
picture: Url
relationship: Person
}
有效查询必须为返回对象的字段提供嵌套字段集,因此此查询无效
{
name,
relationship
}
但是,此示例有效
{
name,
relationship {
name
}
}
并将产生查询的每个对象类型的子集
{
"name": "Mark Zuckerberg",
"relationship": {
"name": "Priscilla Chan"
}
}
结果强制转换
确定强制转换对象的结果是 GraphQL 执行器的核心,因此这将在规范的该部分中介绍。
输入强制转换
对象永远不是有效的输入。
对象字段在概念上是产生值的函数。有时,对象字段可以接受参数以进一步指定返回值。字段参数定义为所有可能的参数名称及其预期输入类型的列表。
例如,具有 picture
字段的 Person
类型可以接受参数来确定要返回的图像大小。
type Person {
name: String
picture(size: Int): Url
}
GraphQL 查询可以选择性地为其字段指定参数以提供这些参数。
此示例查询
{
name,
picture(size: 600)
}
可能产生结果
{
"name": "Mark Zuckerberg",
"picture": "http://some.cdn/picture_600.jpg"
}
字段参数的类型可以是标量(如本示例中所示)或枚举。它也可以是输入对象(在本文档后面介绍),或者可以是任何包装类型,其底层基本类型是这三种类型之一。
对象中的字段可以根据应用程序的需要标记为已弃用。查询这些字段仍然是合法的(以确保现有客户端不会因更改而中断),但应在文档和工具中适当处理这些字段。
如果对象类型定义不正确,则可能无效。GraphQL 模式中的每个对象类型都必须遵守这组规则。
GraphQL 接口表示命名字段及其参数的列表。然后,GraphQL 对象可以实现一个接口,这保证了它们将包含指定的字段。
GraphQL 接口上的字段与 GraphQL 对象上的字段具有相同的规则;它们的类型可以是标量、对象、枚举、接口或联合,或者任何包装类型,其基本类型是这五种类型之一。
例如,接口可以描述必需的字段,而诸如 Person
或 Business
之类的类型可以实现此接口。
interface NamedEntity {
name: String
}
type Person : NamedEntity {
name: String
age: Int
}
type Business : NamedEntity {
name: String
employeeCount: Int
}
当预期使用多个对象类型之一,但应保证某些字段时,产生接口的字段很有用。
继续该示例,Contact
可能会引用 NamedEntity
。
type Contact {
entity: NamedEntity
phoneNumber: String
address: String
}
这允许我们为 Contact
编写查询,该查询可以选择公共字段。
{
entity {
name
},
phoneNumber
}
当查询接口类型上的字段时,只能查询在接口上声明的那些字段。在上面的示例中,entity
返回 NamedEntity
,并且 name
在 NamedEntity
上定义,因此它是有效的。但是,以下查询将无效
{
entity {
name,
age
},
phoneNumber
}
因为 entity
引用了 NamedEntity
,并且 age
未在该接口上定义。仅当 entity
的结果是 Person
时,查询 age
才有效;查询可以使用片段或内联片段来表达这一点
{
entity {
name,
... on User {
age
}
},
phoneNumber
}
结果强制转换
接口类型应具有某种方法来确定给定结果对应于哪个对象。一旦完成,接口的结果强制转换与对象的结果强制转换相同。
输入强制转换
接口永远不是有效的输入。
如果接口类型定义不正确,则可能无效。
GraphQL 联合表示一个对象,它可以是 GraphQL 对象类型列表之一,但不提供这些类型之间的任何保证字段。它们也与接口不同,因为对象类型声明它们实现的接口,但不知道哪些联合包含它们。
对于接口和对象,只能直接查询在类型上定义的那些字段;要查询接口上的其他字段,必须使用类型化的片段。这与联合相同,但是联合不定义任何字段,因此在不使用类型化的片段的情况下,任何字段都不能在此类型上查询。
例如,我们可能有以下类型系统
Union SearchResult = Photo | Person
type Person {
name: String
age: Int
}
type Photo {
height: Int
width: Int
}
type SearchQuery {
firstSearchResult: SearchResult
}
当查询类型为 SearchQuery
的 firstSearchResult
字段时,查询将要求片段内的所有字段指示适当的类型。如果查询想要在结果是 Person 时获取名称,而在结果是照片时获取高度,则以下查询无效,因为联合本身未定义字段
{
firstSearchResult {
name
height
}
}
相反,查询将是
{
firstSearchResult {
... on Person {
name
}
... on Photo {
height
}
}
}
结果强制转换
联合类型应具有某种方法来确定给定结果对应于哪个对象。一旦完成,联合的结果强制转换与对象的结果强制转换相同。
输入强制转换
联合永远不是有效的输入。
如果联合类型定义不正确,则可能无效。
当预期作为输入类型时,仅当输入数组中的每个项目都可以被项目类型接受时,才接受数组输入值。如果输入值不是数组,但可以被项目类型接受,则通过创建一个以输入值作为唯一项的数组来强制转换它。其他输入值必须引发查询错误,指示类型不正确。
GraphQL 枚举是标量类型的一种变体,它表示一组有限的可能值之一。
GraphQL 枚举不是数值的引用,而是其自身独特的价值。它们序列化为字符串:表示值的名称。
结果强制转换
GraphQL 服务器必须返回一组定义的可能值之一,如果无法进行合理的强制转换,则必须引发字段错误。
输入强制转换
GraphQL 具有常量字面量来表示枚举输入值。GraphQL 字符串字面量不得被接受为枚举输入,而应引发查询错误。
对于非字符串符号值具有不同表示形式的查询变量传输序列化(例如,EDN)应仅允许此类值作为枚举输入值。否则,对于大多数没有这样做的传输序列化,字符串可以被解释为具有相同名称的枚举输入值。
字段可以定义客户端在查询中传递的参数,以配置其行为。这些输入可以是字符串或枚举,但有时它们需要比这更复杂。
上面定义的 Object
类型不适合在此处重用,因为 Object
可以包含表达循环引用或对接口和联合的引用的字段,这两种都不适合用作输入参数。因此,输入对象在系统中具有单独的类型。
Input Object
定义了一组输入字段;输入字段可以是标量、枚举或其他输入对象。这允许参数接受任意复杂的结构。
结果强制转换
输入对象永远不是有效的结果。
输入强制转换
输入对象的输入应为无序映射,否则应抛出错误。强制转换的结果是一个无序映射,其中每个输入字段都有一个条目,其键是输入字段的名称。强制转换映射中条目的值是对输入中具有相同键的条目的值进行输入强制转换的结果;如果输入没有相应的条目,则该值是对 null 进行强制转换的结果。上面的输入强制转换应根据输入字段声明的类型的输入强制转换规则执行。
GraphQL 列表是一种特殊的集合类型,它声明了列表中每个项目的类型(称为列表的项目类型)。列表值序列化为有序列表,其中数组中的每个项目都根据项目类型进行序列化。
结果强制转换
GraphQL 服务器必须返回有序列表作为列表类型的结果。列表中的每个项目都必须是项目类型的结果强制转换的结果。如果无法进行合理的强制转换,则必须引发字段错误。特别是,如果返回非列表,则强制转换应失败,因为这表明类型系统与实现之间的期望不匹配。
输入强制转换
当作为输入接受时,列表中的每个项目都应根据项目类型的输入强制转换进行强制转换。
如果作为输入传递给列表类型的值不是列表,则应将其强制转换为好像输入是大小为一的列表,其中传递的值是列表中的唯一项。这是为了允许接受“var args”的输入将其输入类型声明为列表;如果仅传递一个参数(常见情况),则客户端可以仅传递该值,而不是构造列表。
默认情况下,GraphQL 中的所有类型都是可为空的;null
值对于上述所有类型都是有效的响应。要声明不允许为空的类型,可以使用 GraphQL 非空类型。此类型声明一个底层类型,并且此类型的行为与该底层类型完全相同,但 null
对于包装类型而言不是有效响应。
结果强制转换
在上述所有结果强制转换中,null
被认为是有效值。要强制转换非空类型的结果,应执行底层类型的结果强制转换。如果该结果不是 null
,则强制转换非空类型的结果就是该结果。如果该结果是 null
,则应引发错误。
输入强制转换
当作为输入接受时,列表中的每个项目都应根据项目类型的输入强制转换进行强制转换。
如果作为输入传递给列表类型的值不是列表,则应将其强制转换为好像输入是大小为一的列表,其中传递的值是列表中的唯一项。这是为了允许接受“var args”的输入将其输入类型声明为列表;如果仅传递一个参数(常见情况),则客户端可以仅传递该值,而不是构造列表。
GraphQL 模式包括受支持指令的列表,每个指令都有一个名称。指令可以应用于操作、片段或字段;每个指令都指示它适用于其中的哪些。
指令可以选择性地接受参数。指令参数的类型与字段参数的类型具有相同的限制。它可以是标量、枚举、输入对象或任何包装类型,其底层基本类型是这三种类型之一。
GraphQL 模式包括类型,指示查询和 mutation 操作的起始位置。这提供了类型系统的初始入口点。必须始终提供查询类型,并且它是对象基本类型。mutation 类型是可选的;如果它为 null,则意味着系统不支持 mutation。如果提供了 mutation 类型,则它必须是对象基本类型。
查询类型上的字段指示在 GraphQL 查询的顶层可用的字段。例如,像这样的基本 GraphQL 查询
query getMe {
me
}
当为查询起始类型提供的类型具有名为“me”的字段时,它是有效的。类似地
mutation setName {
setName(name: "Zuck") {
newName
}
}
当为 mutation 起始类型提供的类型不为 null,并且具有名为“setName”且带有名为“name”的字符串参数的字段时,它是有效的。
GraphQL 服务器支持对其模式进行内省。此模式使用 GraphQL 本身进行查询,从而为工具构建创建了一个强大的平台。
以一个简单应用程序的示例查询为例。在这种情况下,有一个 User 类型,其中包含三个字段:id、user 和 birthday。
例如,给定一个具有以下类型定义的服务器
type User {
id: String
name: String
birthday: Date
}
查询
{
__type(name: "User") {
name
fields {
name
type { name }
}
}
}
将返回
{
"__type": {
"name" : "User",
"fields": [
{ "name": "id", "type": { "name": "String" } },
{ "name": "name", "type": { "name": "String" } },
{ "name": "birthday", "type": { "name": "Date" } },
]
}
}
GraphQL 内省系统所需的类型和字段与用户定义的类型和字段在同一上下文中使用时,以两个下划线作为前缀。这是为了避免与用户定义的 GraphQL 类型发生命名冲突。相反,GraphQL 类型系统作者不得定义任何带有两个前导下划线的类型、字段、参数或任何其他类型系统工件。
内省系统中的所有类型都提供 description
字段(类型为 String
),以允许类型设计者发布文档以及功能。GraphQL 服务器可以使用 Markdown 语法返回 description
字段。因此,建议任何显示描述的工具都使用 Markdown 渲染器。
为了支持向后兼容性的管理,GraphQL 字段和枚举值可以指示它们是否已弃用(isDeprecated: Boolean
)以及弃用的原因描述(deprecationReason: String
)。
使用 GraphQL 内省构建的工具应通过隐藏信息或面向开发人员的警告来劝阻弃用使用,从而尊重弃用。
GraphQL 通过元字段 __typename: String!
在查询中的任何点支持类型名称内省,当针对任何对象、接口或联合进行查询时。它返回当前正在查询的对象类型的名称。
当针对接口或联合类型进行查询以识别已返回的可能类型的实际类型时,这最常用。
此字段是隐式的,并且不显示在任何已定义类型中的字段列表中。
模式内省系统可以从元字段 __schema
和 __type
访问,这些元字段可以从查询操作根类型的类型访问。
__schema : __Schema!
__type(name: String) : __Type
这些字段是隐式的,并且不显示在查询操作根类型的字段列表中。
GraphQL 模式内省系统的模式
type __Schema {
types: [__Type!]!
queryType: __Type!
mutationType: __Type
directives: [__Directive!]!
}
type __Type {
kind: __TypeKind!
name: String
description: String
# OBJECT and INTERFACE only
fields(includeDeprecated: Boolean = false): [__Field!]
# OBJECT only
interfaces: [__Type!]
# INTERFACE and UNION only
possibleTypes: [__Type!]
# ENUM only
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
# INPUT_OBJECT only
inputFields: [__InputValue!]
# NON_NULL and LIST only
ofType: __Type
}
type __Field {
name: String!
description: String
args: [__InputValue!]!
type: __Type!
isDeprecated: Boolean!
deprecationReason: String
}
type __InputValue {
name: String!
description: String
type: __Type!
defaultValue: String
}
type __EnumValue {
name: String!
description: String
isDeprecated: Boolean!
deprecationReason: String
}
enum __TypeKind {
SCALAR
OBJECT
INTERFACE
UNION
ENUM
INPUT_OBJECT
LIST
NON_NULL
}
type __Directive {
name: String!
description: String
type: __Type
onOperation: Boolean!
onFragment: Boolean!
onField: Boolean!
}
__Type
是类型内省系统的核心。它表示系统中的标量、接口、对象类型、联合、枚举。
__Type
还表示类型修饰符,用于修改它引用的类型 (ofType: __Type
)。这就是我们表示列表、非空类型及其组合的方式。
有几种不同的类型种类。在每种种类中,不同的字段实际上是有效的。这些种类在 __TypeKind
枚举中列出。
表示标量类型,例如 Int、String 和 Boolean。标量不能有字段。
GraphQL 类型设计者应在任何标量的描述字段中描述数据格式和标量强制转换规则。
字段
kind
必须返回 __TypeKind.SCALAR
。name
必须返回 String。description
可以返回 String 或 null
。null
。对象类型表示字段集的具体实例化。内省类型(例如 __Type
、__Field
等)是对象的示例。
字段
kind
必须返回 __TypeKind.OBJECT
。name
必须返回 String。description
可以返回 String 或 null
。fields
:此类型上可查询的字段集。includeDeprecated
,默认为 false
。如果 true
,也会返回已弃用的字段。interfaces
:对象实现的接口集。null
。联合是抽象类型,其中未声明任何公共字段。联合的可能类型在 possibleTypes
中显式列出。类型可以成为联合的一部分,而无需修改该类型。
字段
kind
必须返回 __TypeKind.UNION
。name
必须返回 String。description
可以返回 String 或 null
。possibleTypes
返回可以在此联合中表示的类型列表。它们必须是对象类型。null
。接口是一种抽象类型,其中声明了公共字段。实现接口的任何类型都必须定义所有名称和类型完全匹配的字段。此接口的实现都在 possibleTypes
中显式列出。
字段
kind
必须返回 __TypeKind.INTERFACE
。name
必须返回 String。description
可以返回 String 或 null
。fields
:此接口所需的字段集。includeDeprecated
,默认为 false
。如果 true
,也会返回已弃用的字段。possibleTypes
返回可以在此联合中表示的类型列表。它们必须是对象类型。null
。枚举是特殊的标量,只能具有一组定义的值。
字段
kind
必须返回 __TypeKind.ENUM
。name
必须返回 String。description
可以返回 String 或 null
。enumValues
:EnumValue
的列表。必须至少有一个,并且它们必须具有唯一的名称。includeDeprecated
,默认为 false
。如果 true
,也会返回已弃用的枚举值。null
。输入对象是复合类型,用作查询的输入,定义为命名输入值的列表。
例如,输入对象 Point
可以定义为
type Point {
x: Int
y: Int
}
字段
kind
必须返回 __TypeKind.INPUT_OBJECT
。name
必须返回 String。description
可以返回 String 或 null
。inputFields
:InputValue
的列表。null
。列表表示 GraphQL 中的值序列。列表类型是一种类型修饰符:它将另一个类型实例包装在 ofType
字段中,该字段定义列表中每个项目的类型。
字段
kind
必须返回 __TypeKind.LIST
。ofType
:任何类型。null
。GraphQL 类型是可为空的。值 null
对于字段类型是有效的响应。
非空类型是一种类型修饰符:它将另一个类型实例包装在 ofType
字段中。非空类型不允许将 null
作为响应,并指示参数和输入对象字段的必需输入。
kind
必须返回 __TypeKind.NON_NULL
。ofType
:任何类型,但非空类型除外。null
。列表和非空可以组合,表示更复杂的类型。
如果列表的修改类型是非空,则该列表可能不包含任何 null
项。
如果非空的修改类型是列表,则不接受 null
,但接受空列表。
如果列表的修改类型是列表,则第一个列表中的每个项目都是第二个列表类型的另一个列表。
非空类型不能修改另一个非空类型。
GraphQL 不仅验证请求在语法上是否正确。
在执行之前,它还可以验证请求在给定 GraphQL 模式的上下文中是否有效。验证主要针对开发时工具。任何客户端工具都应返回错误,并且不允许制定已知在给定时间点违反类型系统的查询。
服务器端执行期间的完全请求验证是可选的。随着模式和系统随时间推移而变化,现有客户端最终可能会发出不再对当前类型系统有效的查询。服务器(如本规范的“执行”部分所述)尝试尽可能满足请求,并在存在类型系统错误的情况下继续执行,而不是完全停止执行。
对于本节,我们将假设以下类型系统,以便演示示例
enum DogCommand { SIT, DOWN, HEEL }
type Dog : Pet {
name: String!,
nickname: String,
barkVolume : Int,
doesKnowCommand(dogCommand: DogCommand!) : Boolean!
isHousetrained(atOtherHomes: Boolean): Boolean!
}
interface Sentient { name: String! }
interface Pet { name: String!, nickname: String }
type Alien : Sentient { name: String!, homePlanet: String }
type Human : Sentient { name: String! }
type Cat : Pet {
name: String!,
nickname: String,
meowVolume: Int
}
union CatOrDog = Cat | Dog
union DogOrHuman = Dog | Human
union HumanOrAlien = Human | Alien
形式规范
selection
。fieldName
为 selection
的目标字段fieldName
必须在作用域内的类型上定义解释性文本
字段选择的目标字段必须在选择集的作用域类型上定义。别名名称没有限制。
例如,以下片段将无法通过验证
fragment fieldNotDefined on Dog {
meowVolume
}
fragment aliasedLyingFieldTargetNotDefined on Dog {
barkVolume : kawVolume
}
对于接口,直接字段选择只能在字段上完成。具体实现者的字段与给定接口类型选择集的有效性无关。
例如,以下是有效的
fragment interfaceFieldSelection on Pet {
name
}
以下是无效的
fragment definedOnImplementorsButNotInterface : Pet {
nickname
}
因为字段未在联合上声明,所以在联合类型选择集上直接进行字段选择。即使联合的具体实现者定义了 fieldName,也是如此。
例如,以下是无效的
fragment directFieldSelectionOnUnion : CatOrDog {
directField
}
fragment definedOnImplementorsQueriedOnUnion : CatOrDog {
name
}
形式规范
set
为 GraphQL 文档中定义的任何选择集setForKey
为 set
中具有给定响应键的选择集setForKey
的所有成员都必须解释性文本
选择名称已去重并合并以进行验证,但目标字段、参数和指令必须完全相同。
对于人工管理的 GraphQL,此规则似乎有点违反直觉,因为它看起来是明显的开发人员错误。但是在存在嵌套片段或机器生成的 GraphQL 的情况下,要求唯一选择对工具作者来说是一个繁重的限制。
以下选择正确合并
fragment mergeIdenticalFields on Dog {
name
name
}
fragment mergeIdenticalAliasesAndFields on Dog {
otherName : name
otherName : name
}
以下无法合并
fragment conflictingBecauseAlias on Dog {
name : nickname
name
}
如果字段参数具有相同的参数,则它们也会被合并。值和变量都可以正确合并。
例如,以下正确合并
fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: SIT)
}
fragment mergeIdenticalFieldsWithIdenticalValues on Dog {
doesKnowCommand(dogCommand: $dogCommand)
doesKnowCommand(dogCommand: $dogCommand)
}
以下不正确合并
fragment conflictingArgsOnValues on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: HEEL)
}
fragment conflictingArgsValueAndVar on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: $dogCommand)
}
fragment conflictingArgsWithVars on Dog {
doesKnowCommand(dogCommand: $varOne)
doesKnowCommand(dogCommand: $varTwo)
}
相同的逻辑适用于指令。给定作用域中具有相同响应键的每个选择上的指令集必须相同。
以下是有效的
fragment mergeSameFieldsWithSameDirectives on Dog {
name @if:true
name @if:true
}
以下是无效的
fragment conflictingDirectiveArgs on Dog {
name @if: true
name @unless: false
}
形式规范
selection
selectionType
为 selection
的结果类型selectionType
是标量selectionType
是接口、联合或对象解释性文本
标量上永远不允许字段选择:标量是任何 GraphQL 查询的叶子节点。
以下是有效的。
fragment scalarSelection: Dog {
barkVolume
}
以下是无效的。
fragment scalarSelectionsNotAllowedOnBoolean : Dog {
barkVolume { sinceWhen }
}
相反,GraphQL 查询的叶子字段选择必须是标量。不允许在没有子字段的对象、接口和联合上进行叶子选择。
让我们假设模式的以下查询根类型
type QueryRoot {
human: Human
pet: Pet
catOrDog: CatOrDog
}
以下示例无效
query directQueryOnObjectWithoutSubFields
{
human
}
query directQueryOnInterfaceWithoutSubFields
{
pet
}
query directQueryOnUnionWithoutSubFields
{
catOrDog
}
形式规范
selection
arguments
为提供给 selection
的参数集targetField
为给定 selection
的目标字段argumentDefinitions
为 targetField
的参数定义集arguments
中的每个 argumentName
都必须在 targetField
中具有同名的相应参数定义解释性文本
字段选择可以接受参数。每个字段选择对应于封闭类型上的字段定义,该定义指定了一组可能的参数。提供给选择的每个参数都必须在可能的参数集中定义。
例如,以下是有效的
fragment argOnRequiredArg on Dog {
doesKnowCommand(dogCommand: SIT)
}
fragment argOnOptional on Dog {
isHousetrained(atOtherHomes: true)
}
以下是无效的,因为 command 未在 DogCommand 上定义
fragment invalidArgName on Dog {
doesKnowCommand(command: CLEAN_UP_HOUSE)
}
为了探索更复杂的参数示例,让我们将以下内容添加到我们的类型系统中
type Arguments {
multipleReqs(x: Int!, y: Int!)
booleanArgField(booleanArg: Boolean)
floatArgField(floatArg: Float)
intArgField(intArg: Int)
nonNullBooleanArgField(nonNullBooleanArg: Boolean!)
}
参数的顺序无关紧要。因此,以下两个示例均有效。
fragment multipleArgs on Arguments {
multipleReqs(x: 1, y: 2)
}
fragment multipleArgsReverseOrder on Arguments {
multipleReqs(y: 1, x: 2)
}
形式规范
selection
arguments
为提供给 selection
的参数集targetField
为给定 selection
的目标字段argumentDefinitions
为 targetField
的参数定义集arguments
的每个 literalArgument
,其中文字用于值。literalArgument
的类型必须等于参数定义的类型 或者literalArgument
的类型必须可强制转换为参数定义的类型解释性文本
参数字面值必须与字面值传递到的类型上定义的类型兼容。
这意味着以下之一
例如,Int 可以被强制转换为 Float。
fragment goodBooleanArg on Arguments {
booleanArgField(booleanArg: true)
}
fragment coercedIntIntoFloatArg on Arguments {
floatArgField(floatArg: 1)
}
不可强制转换的转换是从字符串到整数。因此,以下示例无效。
fragment stringIntoInt on Arguments {
intArgField(intArg: "3")
}
selection
arguments
为提供给 selection
的参数集targetField
为给定 selection
的目标字段argumentDefinitions
为 targetField
的参数定义集解释性文本
字段参数可以是必需的。如果参数的类型为非空,则字段参数是必需的。如果不是非空,则参数是可选的。可选参数必须具有默认值。
例如,以下是有效的
fragment goodBooleanArg on Arguments {
booleanArgField(booleanArg: true)
}
fragment goodNonNullArg on Arguments {
nonNullBooleanArgField(nonNullBooleanArg: true)
}
在具有可空参数的字段上,该参数可以省略。
因此,以下查询是有效的
fragment goodBooleanArgDefault on Arguments {
booleanArgField
}
但在非空参数上,这是无效的。
fragment missingRequiredArg on Arguments {
notNullBooleanArgField
}
形式规范
解释性文本
片段必须在模式中存在的类型上指定。这适用于命名片段和内联片段。如果它们未在模式中定义,则查询将不会通过验证。
例如,以下片段是有效的
fragment CorrectType on Dog {
name
}
fragment InlineFragment on Dog {
... on Dog {
name
}
}
以下片段不会通过验证
fragment NotOnExistingType on NotInSchema {
name
}
fragment InlineNotExistingType on Dog {
... on NotInSchema { name }
}
形式规范
解释性文本
片段只能在联合、接口和对象上声明。它们在标量上是无效的。它们只能应用于非叶子字段。此规则适用于内联片段和命名片段。
以下片段声明是有效的
fragment fragOnObject on Dog { name }
fragment fragOnInterface on Pet { name }
fragment fragOnUnion on CatOrDog { ... on Dog { name } }
以下片段声明是无效的
fragment fragOnScalar on Int { something }
fragment inlineFragOnScalar on Dog { ... on Boolean { somethingElse } }
形式规范
解释性文本
定义的片段必须在查询文档中使用。
例如,以下是一个无效的查询文档
fragment nameFragment on Dog { // unused
name
}
{ dog { name } }
字段选择也由将片段相互展开来确定。目标片段的选择集与引用目标片段的级别的选择集联合。
形式规范
解释性文本
命名片段展开必须引用文档中定义的片段。如果展开的目标未定义,则会发生错误
fragment nameFragment on Dog { name }
{ dog { ...undefinedFragment} }
形式规范
DetectCycles(fragmentDefinition, visited) :
解释性文本
片段展开图不得形成任何循环,包括自身展开。否则,操作可能会无限展开或在底层数据中的循环上无限执行。
这将使导致无限展开的片段无效
{
dog {
...nameFragment
}
}
fragment nameFragment on Dog {
name
...barkVolumeFragment
}
fragment barkVolumeFragment on Dog {
barkVolume
...nameFragment
}
如果上述片段是内联的,这将导致无限大的
{
dog {
name, barkVolume, name, barkVolume, name, barkVolume, name, # etc...
}
}
这也将使在循环数据上执行时导致无限递归的片段无效
{
dog {
...dogFragment
}
}
fragment dogFragment on Dog {
name
owner {
...ownerFragment
}
}
fragment ownerFragment on Dog {
name
pets {
...dogFragment
}
}
形式规范
解释性文本
片段在类型上声明,并且仅在运行时对象类型与类型条件匹配时才适用。它们也在父类型的上下文中展开。仅当片段的类型条件可能在父类型中应用时,片段展开才有效。
以及以下有效片段
在对象类型的范围内,唯一有效的对象类型片段展开是应用于与作用域中类型相同的类型的片段展开。
例如
fragment dogFragment on Dog { ... on Dog { barkVolume } }
以下是无效的
fragment catInDogFragmentInvalid on Dog { ... on Cat { meowVolume } }
在对象类型的范围内,如果对象类型实现了接口或是联合的成员,则可以使用联合或接口展开。
例如
fragment petNameFragment on Pet { name }
fragment interfaceWithinObjectFragment on Dog { ...petNameFragment }
有效,因为 Dog 实现了 Pet 接口。
同样地
fragment CatOrDogNameFragment on CatOrDog { ... on Cat { meowVolume } }
fragment unionWithObjectFragment on Dog { ...CatOrDogFragment }
有效,因为 Dog 是 CatOrDog 联合的成员。值得注意的是,如果检查 CatOrDogNameFragment 的内容,您可能会注意到永远不会返回有效的结果。但是,我们不将其指定为无效,因为我们只考虑片段声明,而不是其主体。
联合或接口展开可以在对象类型片段的上下文中使用,但前提是对象类型是该接口或联合的可能类型之一。
例如,以下片段是有效的
fragment petFragment on Pet { name, ... on Dog { barkVolume } }
fragment catOrDogFragment on CatOrDog { ... on Cat { meowVolume } }
petFragment 有效,因为 Dog 实现了接口 Pet。catOrDogFragment 有效,因为 Cat 是 CatOrDog 联合的成员。
相比之下,以下片段是无效的
fragment sentientFragment on Sentient { ... on Dog { barkVolume } }
fragment humanOrAlienFragment on HumanOrAlien { ... on Cat { meowVolume } }
Dog 未实现接口 Sentient,因此 sentientFragment 永远无法返回有意义的结果。因此,该片段无效。同样,Cat 不是联合 HumanOrAlien 的成员,它也永远无法返回有意义的结果,使其无效。
联合或接口片段可以在彼此之间使用。只要在作用域和展开的可能类型的交集中存在至少一个对象类型,则展开被认为是有效的。
例如
fragment unionWithInterface Pet { ...dogOrHumanFragment }
fragment dogOrHumanFragment on DogOrHuman { ... on Dog { barkVolume } }
被认为是有效的,因为 Dog 实现了接口 Pet 并且是 DogOrHuman 的成员。
然而
fragment nonIntersectingInterfaces on Pet { ...sentientFragment }
fragment sentientFragment on Sentient { name }
是无效的,因为不存在同时实现 Pet 和 Sentient 的类型。
形式规范
解释性文本
GraphQL 服务器定义它们支持哪些指令。对于指令的每次使用,该指令必须在服务器上可用。
形式规范
解释性文本
指令参数遵循与字段参数类似的规则。与字段参数非常相似,指令的参数必须与指令类型的输入类型相同或可强制转换为指令类型的输入类型。
指令参数与字段参数的不同之处在于,它们可以在不提供参数的情况下使用。如果指令的类型不是非空,则指令可以可选地在不带参数的情况下使用。如果指令的类型未定义,则它是标志指令:它不能有参数。如果为标志指令提供了值,则会发生验证错误。
形式规范
解释性文本
操作定义的变量如果该变量的类型不是非空,则允许定义默认值。
例如,以下查询将通过验证。
query HouseTrainedQuery($atOtherHomes: Boolean = true) {
dog { isHousetrained(atOtherHomes: $atOtherHomes) }
}
但是,如果变量定义为非空,则默认值是无法访问的。因此,诸如以下的查询将无法通过验证
query HouseTrainedQuery($atOtherHomes: Boolean! = true) {
dog { isHousetrained(atOtherHomes: $atOtherHomes) }
}
默认值必须与变量的类型兼容。类型必须匹配,或者它们必须可以强制转换为该类型。
不匹配的类型会失败,例如在以下示例中
query HouseTrainedQuery($atOtherHomes: Boolean = "true") {
dog { isHousetrained(atOtherHomes: $atOtherHomes) }
}
但是,如果类型是可强制转换的,则查询将通过验证。
例如
query IntToFloatQuery($floatVar: Float = 1) {
arguments {
floatArgField(floatArg: $floatVar)
}
}
形式规范
解释性文本
变量只能是标量、枚举、输入对象或这些类型的列表和非空变体。这些被称为输入类型。对象、联合和接口不能用作输入。
以下查询是有效的
query TakesBoolean($atOtherHomes: Boolean) { /* ... */ }
query TakesComplexInput($complexInput: ComplexInput) { /* ... */ }
query TakesListOfBooleanBang($booleans: [Boolean!]) { /* ... */ }
以下查询是无效的
query TakesCat($cat: Cat) { /* ... */ }
query TakesDogBang($dog: Dog!) { /* ... */ }
query TakesListOfPet($pets: [Pet]) { /* ... */ }
query TakesCatOrDog($catOrDog: CatOrDog) { /* ... */ }
形式规范
解释性文本
变量的作用域是基于每个操作的。这意味着在操作上下文中使用的任何变量都必须在该操作的顶层定义
例如
query VariableIsDefined($atOtherHomes: Boolean) {
dog { isHousetrained(atOtherHomes: $booleanArg)
}
是有效的。$atOtherHomes 由操作定义。
相比之下,以下查询是无效的
query VariableIsNotDefined {
dog { isHousetrained(atOtherHomes: $atOtherHomes)
}
$atOtherHomes 未由操作定义。
片段使此规则复杂化。操作传递包含的任何片段都可以访问该操作定义的变量。片段可以出现在多个操作中,因此变量用法必须与所有这些操作中的变量定义相对应。
例如,以下是有效的
query VariableIsDefinedUsedInSingleFragment($atOtherHomes: Boolean) {
dog { ...isHousetrainedFragment }
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes}
}
因为 isHousetrainedFragment 在操作 VariableIsDefinedUsedInSingleFragment 的上下文中被使用,并且该变量由该操作定义。
相反,如果在不定义引用变量的操作中包含片段,则会发生验证错误。
query VariableIsNotDefinedUsedInSingleFragment {
dog { ...isHousetrainedFragment }
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes}
}
这也适用于传递性,因此以下操作也会失败
query VariableIsNotDefinedUsedInNestedFragment {
dog { ...outerHousetrainedFragment }
}
fragment outerHousetrainedFragment on Dog {
...isHousetrainedFragment
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes}
}
变量必须在片段使用的所有操作中定义。
query HousetrainedQueryOne($atOtherHomes: Boolean) {
dog { ...isHousetrainedFragment }
}
query HousetrainedQueryTwo($atOtherHomes: Boolean) {
dog { ...isHousetrainedFragment }
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes}
}
但是,以下操作无法通过验证
query HousetrainedQueryOne($atOtherHomes: Boolean) {
dog { ...isHousetrainedFragment }
}
query HousetrainedQueryTwoNotDefined {
dog { ...isHousetrainedFragment }
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes}
}
这是因为 HousetrainedQueryTwoNotDefined 未定义变量 $atOtherHomes,但该变量由 isHousetrainedFragment 使用,而 isHousetrainedFragment 包含在该操作中。
形式规范
解释性文本
操作定义的所有变量都必须在该操作或该操作传递包含的片段中使用。未使用的变量会导致验证错误。
例如,以下是无效的
query VariableUnused($atOtherHomes: Boolean) {
dog { isHousetrained }
}
因为 $atOtherHomes 未被引用。
这些规则也适用于传递片段展开
query VariableUsedInFragment($atOtherHomes: Boolean) {
dog { ...isHousetrainedFragment }
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
以上是有效的,因为 $atOtherHomes 在 isHousetrainedFragment 中使用,而 isHousetrainedFragment 由 VariableUsedInFragment 包含。
如果该片段没有对 $atOtherHomes 的引用,则它将无效
query VariableNotUsedWithinFragment($atOtherHomes: Boolean) {
...isHousetrainedWithoutVariableFragment
}
fragment isHousetrainedWithoutVariableFragment on Dog {
isHousetrained
}
文档中的所有操作都必须使用其所有变量。
因此,以下文档无法通过验证。
query QueryWithUsedVar($atOtherHomes: Boolean) {
dog { ...isHousetrainedFragment }
}
query QueryWithExtraVar($atOtherHomes: Boolean, $extra: Int) {
dog { ...isHousetrainedFragment }
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
此文档无效,因为 QueryWithExtraVar 定义了一个多余的变量。
形式规范
解释性文本
变量用法必须与它们传递到的参数兼容。
当变量在类型完全不匹配的上下文中,或者当可空类型中的变量传递给非空参数类型时,会发生验证失败。
类型必须匹配
Query IntCannotGoIntoBoolean($intArg: Int) {
arguments { booleanArgField(booleanArg: $intArg) }
}
类型为 Int 的 $intArg 不能用作类型为 Boolean 的 booleanArg 的参数。
列表基数也必须相同。例如,列表不能传递到单数值中。
Query BooleanListCannotGoIntoBoolean($booleanListArg: [Boolean]) {
arguments { booleanArgField(booleanArg: $booleanListArg) }
}
空值性也必须受到尊重。通常,可空变量不能传递给非空参数。
Query BooleanArgQuery($booleanArg: Boolean) {
arguments { nonNullBooleanArgField(nonNullBooleanArg: $booleanArg) }
}
一个值得注意的例外是当提供默认参数时。实际上,它们被视为非空。
Query BooleanArgQueryWithDefault($booleanArg: Boolean = true) {
arguments { nonNullBooleanArgField(nonNullBooleanArg: $booleanArg) }
}
对于列表类型,围绕空值性的相同规则适用于外部类型和内部类型。可空列表不能传递给非空列表,并且可空值列表不能传递给非空值列表。
Query NonNullListToList($nonNullBooleanList: ![Boolean]) {
arguments { booleanListArgField(booleanListArg: $nonNullBooleanList) }
}
但是,可空列表不能传递给非空列表。
Query ListToNonNullList($booleanList: [Boolean]) {
arguments { nonNullBooleanListField(nonNullBooleanListArg: $booleanList) }
}
这将验证失败,因为 [T]
不能传递给 [T]!
。
类似地,[T]
不能传递给 [T!]
。
本节描述 GraphQL 如何从请求生成响应
为了评估请求,执行器将具有已解析的 Document
(如本规范的“查询语言”部分中所定义)和要运行的选定操作名称。
执行器应在 Document
中找到具有给定操作名称的 Operation
。如果不存在这样的操作,执行器应抛出错误。如果找到了操作,则评估请求的结果应是根据“评估操作”部分评估操作的结果。
类型系统,如本规范的“类型系统”部分中所述,必须提供“查询根”和“突变根”对象。
如果操作是突变,则操作的结果是在“突变根”对象上评估突变的顶层选择集的结果。此选择集应串行评估。
如果操作是查询,则操作的结果是在“查询根”对象上评估查询的顶层选择集的结果。
为了评估选择集,执行器需要知道在其上评估集合的对象以及是否正在串行评估它。
如果选择集正在 null
对象上评估,则评估选择集的结果为 null
。
否则,选择集将转换为分组字段集;分组字段集中的每个条目都是共享 responseKey 的字段列表。
通过调用 CollectFields
,初始化 visitedFragments
为空列表,将选择集转换为分组字段集。
评估选择集的结果是评估相应的分组字段集的结果。如果选择集正在串行评估,则相应的分组字段集应串行评估,否则应正常评估。
评估分组字段集的结果将是一个无序映射。此映射中将为分组字段集中的每个项创建一个条目。
分组字段集中的每个项都可能在结果映射中创建一个条目。结果映射中的该条目是调用 GetFieldEntry
在分组字段集中的相应项上的结果。GetFieldEntry
可以返回 null
,这表示此项的结果映射中不应存在条目。请注意,这与返回带有字符串键和空值的条目不同,后者表示应为此键添加一个结果条目,并且其值应为空。
GetFieldEntry
假定存在两个在本规范的此部分中未定义的函数。类型系统应提供这些方法
ResolveFieldOnObject
,它接受对象类型、字段和对象,并返回在该对象上解析该字段的结果。GetFieldTypeFromObjectType
,它接受对象类型和字段,并返回该字段在对象类型上的类型,如果该字段在对象类型上无效,则返回 null
。null
。当在没有串行执行顺序要求的情况下评估分组字段集时,执行器可以按其选择的任何顺序确定结果映射中的条目。由于除顶层突变字段之外的字段的解析始终是无副作用和幂等的,因此执行顺序不得影响结果,因此服务器可以自由地按其认为最佳的任何顺序评估字段条目。
例如,给定以下要正常评估的分组字段集
{
birthday {
month
},
address {
street
}
}
有效的 GraphQL 执行器可以按其选择的任何顺序解析这四个字段。
请注意,根据以上部分,执行器将以串行执行顺序运行的唯一时间是在突变操作的顶层选择集及其相应的分组字段集上。
当串行评估分组字段集时,执行器必须按照分组字段集中提供的顺序考虑分组字段集中的每个条目。它必须在继续处理分组字段集中的下一个条目之前,确定结果映射中每个条目对应的完成状态。
例如,给定以下要串行评估的选择集
{
changeBirthday(birthday: $newBirthday) {
month
},
changeAddress(address: $newAddress) {
street
}
}
执行器必须串行地:
changeBirthday
运行 getFieldEntry
,在 CompleteValue
期间,它将正常评估 { month }
子选择集。changeAddress
运行 getFieldEntry
,在 CompleteValue
期间,它将正常评估 { street }
子选择集。作为一个说明性示例,假设我们有一个 mutation 字段 changeTheNumber
,它返回一个包含一个字段 theNumber
的对象。如果我们串行执行以下选择集
{
first: changeTheNumber(newNumber: 1) {
theNumber
},
second: changeTheNumber(newNumber: 3) {
theNumber
},
third: changeTheNumber(newNumber: 2) {
theNumber
},
}
执行器将串行地评估以下内容:
changeTheNumber(newNumber: 1)
字段first
的 { theNumber }
子选择集changeTheNumber(newNumber: 3)
字段second
的 { theNumber }
子选择集changeTheNumber(newNumber: 2)
字段third
的 { theNumber }
子选择集正确的执行器必须为该选择集生成以下结果
{
"first": {
"theNumber": 1
},
"second": {
"theNumber": 3
},
"third": {
"theNumber": 2
},
}
如果在解析字段时发生错误,则应将其视为该字段返回 null,并且必须将错误添加到响应中的“errors”列表中。
如果解析字段的结果为 null(无论是由于解析字段的函数返回 null 还是由于发生错误),并且该字段在类型系统中被标记为非空,则包含此字段的整个字段集的评估结果现在为 null。
如果字段由于错误而为 null,则错误已被记录,并且响应中的“errors”列表不得受到影响。
如果字段解析函数返回 null,并且该字段为非空,则尚未记录任何错误,因此必须将适当的错误添加到“errors”列表中。
当 GraphQL 服务器收到请求时,它必须返回格式良好的响应。服务器的响应描述了成功执行请求操作的结果,并描述了请求期间遇到的任何错误。
在字段被替换为 null 的情况下发生错误时,响应可能同时包含部分响应和遇到的错误。
GraphQL 不要求特定的序列化格式。但是,客户端应使用支持 GraphQL 响应中主要原语的序列化格式。特别是,序列化格式必须支持以下四个原语的表示:
序列化格式可以支持以下原语,但是,字符串可以用作这些原语的替代。
JSON 是 GraphQL 的首选序列化格式,但如上所述,GraphQL 不要求特定的序列化格式。为了保持一致性和易于表示,规范全文中的响应示例均以 JSON 形式给出。特别是,在我们的 JSON 示例中,我们将使用以下 JSON 概念来表示原语:
GraphQL 值 | JSON 值 |
---|---|
映射 | 对象 |
列表 | 数组 |
Null | null |
String | String |
Boolean | true 或 false |
Int | 数字 |
Float | 数字 |
枚举值 | String |
GraphQL 操作的响应必须是映射。
如果操作包含执行,则响应映射必须包含一个键为“data”的条目。此条目的值在“Data”部分中描述。如果操作在执行之前由于语法错误、缺少信息或验证错误而失败,则不得存在此条目。
如果操作遇到任何错误,则响应映射必须包含一个键为“errors”的条目。此条目的值在“Errors”部分中描述。如果操作完成时未遇到任何错误,则不得存在此条目。
响应映射还可以包含一个键为 extensions
的条目。此条目(如果设置)必须具有映射作为其值。此条目保留供实现者随意扩展协议,因此对其内容没有其他限制。
为了确保协议的未来更改不会破坏现有的服务器和客户端,顶级响应映射不得包含除上述三个条目之外的任何条目。
响应中的 data
条目将是请求操作的执行结果。如果操作是查询,则此输出将是模式的查询根类型的对象;如果操作是 mutation,则此输出将是模式的 mutation 根类型的对象。
如果在执行开始之前遇到错误,则结果中不应存在 data
条目。
如果在执行期间遇到阻止有效响应的错误,则响应中的 data
条目应为 null
。
响应中的 errors
条目是一个非空错误列表,其中每个错误都是一个映射。
如果在请求的操作期间未遇到任何错误,则结果中不应存在 errors
条目。
每个错误都必须包含一个键为 message
的条目,其中包含错误的字符串描述,旨在为开发人员提供指导,以理解和纠正错误。
如果错误可以与请求的 GraphQL 文档中的特定点相关联,则它应包含一个键为 locations
的条目,其中包含位置列表,其中每个位置都是一个包含键 line
和 column
的映射,这两个键都是从 1
开始的正数,用于描述关联语法元素的开头。
GraphQL 服务器可以根据需要为错误提供其他条目,以生成更有帮助或机器可读的错误,但是未来版本的规范可能会描述错误的附加条目。
如果响应中的 data
条目为 null
或不存在,则响应中的 errors
条目不得为空。它必须至少包含一个错误。它包含的错误应指示为什么无法返回数据。
如果响应中的 data
条目不为 null
,则响应中的 errors
条目可能包含执行期间发生的任何错误。如果执行期间发生错误,则应包含这些错误。
GraphQL 文档在语法语法中定义,其中终结符是标记。标记在词法语法中定义,词法语法匹配源字符的模式。解析源 UTF-8 字符序列的结果生成 GraphQL AST。
符号定义(例如,Symbol :) 为符号的单个序列或符号的可能序列列表,可以是项目符号列表或使用“one of”简写。
下标后缀 “Symbolopt ” 是两个可能序列的简写,一个包括该符号,另一个排除该符号。
例如
是以下内容的简写
下标后缀 “Symbollist ” 是一个或多个该符号的列表的简写。
例如
是以下内容的简写
花括号中的符号定义下标后缀参数 “SymbolParam ” 是两个符号定义的简写,一个附加了该参数名称,另一个没有附加。符号上的相同下标后缀是该定义的变体的简写。如果参数以 “?” 开头,则在具有相同参数的符号定义中使用该形式的符号。一些可能的序列可以分别在 “[+Param]” 和 “[~Param]” 前缀时有条件地包含或排除。
例如
是以下内容的简写
GraphQL 文档由几种源标记组成,这些标记在此处的词法语法中定义,并在 GraphQL 的语法语法中用作终结符。此词法语法通过在 monospace 中或作为 /regular_expressions/ 指定字符模式来定义源字符的模式。非终结模式定义为 斜体。
GraphQL 文档语法语法是根据这些词法标记定义的
! | $ | ( | ) | ... | : | = | @ | [ | ] | { | | | } |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
" | \ | / | b | f | n | r | t |
在每个词法标记之前,可以有任意数量的忽略的源字符,例如空格和注释。空格,字符串内除外,并不重要。
GraphQL 将逗号 ,
视为类似于空格。这确保了逗号的存在与否不会有意义地改变文档的解释语法,这在其他语言中是一个常见的用户错误。它还允许样式化地使用尾随逗号或换行符作为分隔符,这通常是期望的,以提高源代码的可读性和可维护性。当逗号和其他空格可以提高可读性时,建议使用它们。
GraphQL 忽略以下字符序列
GraphQL 文档在语法语法中定义,其中终结符表示为斜体标记(例如 Document)或作为 标点符号(例如 :)或 名称(例如 query)的等宽字体简写。
GraphQL 文档描述了一个完整的文件或请求字符串。文档包含多个定义,包括操作。
操作描述了对 GraphQL 的某种类型的请求。最常见的操作是 query
,即从 GraphQL 读取数据的只读请求。查询操作存在简写语法。
query | mutation |
片段允许重用字段的常见选择,减少文档中的重复文本。内联片段可以直接在选择中内联使用,以在针对接口或联合进行查询时应用类型条件。
字段可以接受参数值。值可以是任何 JSON 风格的值、变量或枚举值。
语义
语义
指令提供了一种描述 GraphQL 文档中运行时执行和类型验证行为的方法。
GraphQL 使用类型系统描述其提供的数据的模式。在定义查询变量时,文档中会引用这些类型。
语义