GraphQL 不只验证请求在语法上是否正确,还验证在当前服务器的 GraphQL 模式上下文中是否正确。预判查询是否有效,从而可以让服务器和客户端在无效查询创建时就有效地通知开发者,而不用等运行时才知道。
GraphQL 类型系统模式随着时间的推移而增加新的类型和新的字段,以前有效的请求有可能在以后变得无效。任何可能导致先前有效的请求变为无效的更改都被视为重大更改。
下面的类型系统是后面所有示例所参考的:
enum DogCommand { SIT, DOWN, HEEL }
type Dog implements Pet {
name: String!
nickname: String
barkVolume: Int
doesKnowCommand(dogCommand: DogCommand!): Boolean!
isHousetrained(atOtherHomes: Boolean): Boolean!
owner: Human
}
interface Sentient {
name: String!
}
interface Pet {
name: String!
}
type Alien implements Sentient {
name: String!
homePlanet: String
}
type Human implements Sentient {
name: String!
}
enum CatCommand { JUMP }
type Cat implements Pet {
name: String!
nickname: String
doesKnowCommand(catCommand: CatCommand!): Boolean!
meowVolume: Int
}
union CatOrDog = Cat | Dog
union DogOrHuman = Dog | Human
union HumanOrAlien = Human | Alien
type QueryRoot {
dog: Dog
}
// 无效操作
query dogOperation {
dog {
name
}
}
mutation dogOperation {
mutateDog {
id
}
}
// 无效操作
query dogOperation {
dog {
name
}
}
{
mutateDog {
id
}
}
由于联合不定义字段,字段不能直接从联合类型选择集中选择,除了元字段__typename。只能通过片段间接查询来自联合类型选择集的字段。
// 有效的:
fragment inDirectFieldSelectionOnUnion on CatOrDog {
__typename
... on Pet {
name
}
... on Dog {
barkVolume
}
}
// 无效的:
fragment directFieldSelectionOnUnion on CatOrDog {
name
barkVolume
}
如果在执行期间客户端发送具有相同名称的多个字段,则要执行的字段和参数以及结果值应该是明确的。因此,对于同一对象可能遇到的任何两个字段选择只有在等同的情况下才是有效的。
// 正确合并:
fragment mergeIdenticalFields on Dog {
name
name
}
fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: SIT)
}
// 无法合并:
fragment conflictingBecauseAlias on Dog {
name: nickname
name
}
叶子字段作为标量,其上不允许进行字段的选择。相反,GraphQL 查询的叶子字段必须是标量。
// 无效的:
fragment scalarSelectionsNotAllowedOnBoolean on Dog {
barkVolume {
sinceWhen
}
}
参数可以用在字段和指令中。
// 有效的:
fragment argOnOptional on Dog {
isHousetrained(atOtherHomes: true) @include(if: true)
}
// 无效的,因为字段 doesKnowCommand 没有定义 command 类型的参数。
fragment invalidArgName on Dog {
doesKnowCommand(command: CLEAN_UP_HOUSE)
}
// 也是无效的,因为 @include 没有定义 unless。
fragment invalidArgName on Dog {
isHousetrained(atOtherHomes: true) @include(unless: false)
}
// 有效的:
fragment goodBooleanArg on Arguments {
booleanArgField(booleanArg: true)
}
fragment coercedIntIntoFloatArg on Arguments {
floatArgField(floatArg: 1)
}
// 无效的,Float 类型无法转换为 Int 类型:
fragment stringIntoInt on Arguments {
intArgField(intArg: "3")
}
// 无效的:
fragment missingRequiredArg on Arguments {
notNullBooleanArgField(nonNullBooleanArg: null)
}
片段不可重名,且定义后就必须使用,否则查询无效。用 ...片段名
来使用片段,比如 ...dogFragment
。
片段声明不可重名,且必须基于 Schema 中已有的类型 on SomeType
。
// 有效的:
fragment inlineFragment on Dog {
... on Dog {
name
}
}
fragment inlineFragment2 on Dog {
... @include(if: true) {
name
}
}
// 无效的:
fragment notOnExistingType on NotInSchema {
name
}
fragment inlineNotExistingType on Dog {
... on NotInSchema {
name
}
}
不管是内联片段还是普通片段,都必须必须基于以下类型: UNION,INTERFACE 或 OBJECT。在标量上或叶子上是无效的。
// 无效的:
fragment fragOnScalar on Int {
something
}
fragment inlineFragOnScalar on Dog {
... on Boolean {
somethingElse
}
}
// 构成循环的片段,无效:
{
dog {
...nameFragment
}
}
fragment nameFragment on Dog {
name
...barkVolumeFragment
}
fragment barkVolumeFragment on Dog {
barkVolume
...nameFragment
}
下面的抽象范围指的是接口 Interface 或联合 Union 的作用域,对象范围指的是对象 Object 的作用域。
在对象类型的范围内,唯一有效的对象类型片段就是这个片段自身。
// 有效的查询:
query you{
viewer {
...myFragment
}
}
fragment myFragment on User{
name
... on User { // 这里只能基于 User,因为在 User 对象的作用域中。
login
}
}
// 有效的查询的返回数据:
{
"data": {
"viewer": {
"name": null,
"login": "kikajack"
}
}
}
// 无效的:
fragment myFragment on User{
name
... on Repositories{ // 这里只能基于 User,因为在 User 对象的作用域中。
id
}
}
在对象类型的范围内,如果对象类型实现接口或者是联合的成员,则可以使用联合或接口传播。
// 有效,因为 Dog 实现了 Pet。
fragment petNameFragment on Pet {
name
}
fragment interfaceWithinObjectFragment on Dog {
...petNameFragment
}
// 有效,因为 Dog 是 CatOrDog Union 类型的成员
fragment catOrDogNameFragment on CatOrDog {
... on Cat {
meowVolume
}
}
fragment unionWithObjectFragment on Dog {
...catOrDogNameFragment
}
只有当对象类型是该接口或联合的可能类型之一时,才可以在对象类型片段的上下文中使用联合或接口扩展。
//有效的:
fragment petFragment on Pet {
name
... on Dog {
barkVolume
}
}
fragment catOrDogFragment on CatOrDog {
... on Cat {
meowVolume
}
}
Union 和 Interface 片段可以相互嵌套。只要在作用域和扩展的可能类型的交集中存在至少一个对象类型,就是有效的。
// 有效的,因为狗实现接口宠物,是DogOrHuman的成员。
fragment unionWithInterface on Pet {
...dogOrHumanFragment
}
fragment dogOrHumanFragment on DogOrHuman {
... on Dog {
barkVolume
}
}
// 无效的,因为没有实现Pet和Sentient的类型。
fragment nonIntersectingInterfaces on Pet {
...sentientFragment
}
fragment sentientFragment on Sentient {
name
}
Values 具有唯一性,下面的查询将不会通过验证:
{
field(arg: { field: true, field: false })
}
下面的查询不会通过验证,因为 QUERY 不认可 @skip 指令,这不是 @skip 的有效的位置:
query @skip(if: $foo) {
field
}
每个位置不允许重复使用指令。下面的查询不会通过验证:
query ($foo: Boolean = true, $bar: Boolean = false) {
field @skip(if: $foo) @skip(if: $bar)
}
// 成功
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)
}
}
变量是输入类型时,只能是标量、枚举、输入对象、列表和这些类型的非空变体。不能是对象、联合和接口。
对于这些示例,添加以下类型:
input ComplexInput { name: String, owner: String }
extend type QueryRoot {
findDog(complex: ComplexInput): Dog
booleanList(booleanListArg: [Boolean!]): Boolean
}
// 有效的:
query takesBoolean($atOtherHomes: Boolean) {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
query takesComplexInput($complexInput: ComplexInput) {
findDog(complex: $complexInput) {
name
}
}
query TakesListOfBooleanBang($booleans: [Boolean!]) {
booleanList(booleanListArg: $booleans)
}
// 无效:
query takesCat($cat: Cat) {
# ...
}
query takesDogBang($dog: Dog!) {
# ...
}
query takesListOfPet($pets: [Pet]) {
# ...
}
query takesCatOrDog($catOrDog: CatOrDog) {
# ...
}
// 有效的:
query variableIsDefined($atOtherHomes: Boolean) {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
// 无效的:
query variableIsNotDefined {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
// 有效的:
query variableIsDefinedUsedInSingleFragment($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)
}
所有由操作定义的变量必须在该操作或在该操作中传递的片段中使用,或者在该操作中传递的片段中使用。未使用的变量会导致验证错误。
// 有效的,因为 $atOtherHomes 在 isHousetrainedFragment 中使用,其中包含 variableUsedInFragment。
query variableUsedInFragment($atOtherHomes: Boolean) {
dog {
...isHousetrainedFragment
}
}
fragment isHousetrainedFragment on Dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
// 无效的,这个片段没有引用$ atOtherHomes:
query variableNotUsedWithinFragment($atOtherHomes: Boolean) {
...isHousetrainedWithoutVariableFragment
}
fragment isHousetrainedWithoutVariableFragment on Dog {
isHousetrained
}
query intCannotGoIntoBoolean($intArg: Int) {
arguments {
booleanArgField(booleanArg: $intArg)
}
}
query booleanArgQuery($booleanArg: Boolean) {
arguments {
nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)
}
}
// 有效的:
query nonNullListToList($nonNullBooleanList: [Boolean]!) {
arguments {
booleanListArgField(booleanListArg: $nonNullBooleanList)
}
}
// 无效的,可以清空的列表不能传递给非空列表:
query listToNonNullList($booleanList: [Boolean]) {
arguments {
nonNullBooleanListField(nonNullBooleanListArg: $booleanList)
}
}