原文地址: Schemas and Types
通过使用类型系统,可以预先确定一个 GraphQL 查询是否有效。这允许服务器和客户机在创建无效查询时有效地通知开发人员,而不必依赖于运行时检查。
对于我们的星球大战示例,文件 starwarsvalidtest.js 包含大量的查询,这些查询演示了各种各样的失效,并且是一个可以运行来执行引用实现验证器的测试文件。
首先,让我们使用一个复杂的有效查询。这是一个嵌套的查询,类似于上一节的示例,但是将复制的字段分解为一个片段:
{
hero {
...NameAndAppearances
friends {
...NameAndAppearances
friends {
...NameAndAppearances
}
}
}
}
fragment NameAndAppearances on Character {
name
appearsIn
}
{
"data": {
"hero": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
{
"name": "Leia Organa",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
...
]
},
{
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
...
]
},
{
"name": "Leia Organa",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
...
{
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
}
]
}
]
}
}
}
这个查询是有效的。让我们看看一些无效的查询。
片段不能引用自身或创建一个循环,因为这可能导致一个无限循环!下面是相同的查询,但是没有明确的三层嵌套:
{
hero {
...NameAndAppearancesAndFriends
}
}
fragment NameAndAppearancesAndFriends on Character {
name
appearsIn
friends {
...NameAndAppearancesAndFriends
}
}
结果
{
"errors": [
{
"message": "Cannot spread fragment \"NameAndAppearancesAndFriends\" within itself.",
"locations": [
{
"line": 11,
"column": 5
}
]
}
]
}
当我们查询字段时,我们必须查询给定类型中存在的字段。因此,当 hero
返回一个 Character
时,我们必须查询 Character
的字段。该类型没有一个 favoriteSpaceship
,因此这个查询是无效的:
# INVALID: favoriteSpaceship does not exist on Character
{
hero {
favoriteSpaceship
}
}
{
"errors": [
{
"message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
每当我们查询一个字段,它返回的不是标量或枚举时,我们需要指定要从该字段返回的数据。英雄返回一个 Character
,我们一直在请求 name
和 appearsIn
之类的字段;如果我们省略了这个,这个查询就不会有效:
# INVALID: hero is not a scalar, so fields are needed
{
hero
}
{
"errors": [
{
"message": "Field \"hero\" of type \"Character\" must have a selection of subfields. Did you mean \"hero { ... }\"?",
"locations": [
{
"line": 3,
"column": 3
}
]
}
]
}
类似地,如果字段是标量,在它上查询额外的字段是没有意义的,这样做会使查询无效:
# INVALID: name is a scalar, so fields are not permitted
{
hero {
name {
firstCharacterOfName
}
}
}
{
"errors": [
{
"message": "Field \"name\" must not have a selection since type \"String!\" has no subfields.",
"locations": [
{
"line": 4,
"column": 10
}
]
}
]
}
之前,有人注意到,查询只能查询所涉及类型的字段;当我们查询返回一个 Character
的 hero
时,我们只能查询存在于字符的字段。但是,如果我们想要查询 R2-D2s 的主函数,会发生什么呢?
# INVALID: primaryFunction does not exist on Character
{
hero {
name
primaryFunction
}
}
{
"errors": [
{
"message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
"locations": [
{
"line": 5,
"column": 5
}
]
}
]
}
这个查询是无效的,因为 primaryFunction
不是一个 Character
的字段。我们希望通过某种方式表明,如果 Character
是 Droid
,我们希望获取 primaryFunction
,否则将忽略该字段。我们可以使用前面介绍的片段来实现这一点。通过在 Droid
上设置一个片段并包含它,我们可以确保只查询定义的 primaryFunction
{
hero {
name
...DroidFields
}
}
fragment DroidFields on Droid {
primaryFunction
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
这个查询是有效的,但是有点冗长;当我们多次使用它们时,命名的片段是很有价值的,但是我们只使用一次。我们可以使用一个内联片段,而不是使用一个命名的片段;这仍然允许我们指出我们正在查询的类型,但是没有指定一个单独的片段:
{
hero {
name
... on Droid {
primaryFunction
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
这仅仅触及了验证系统的表面;这里有许多验证规则,以确保 GraphQL 查询具有语义上的意义。规范在 验证
部分中详细讨论了这个主题,以及 GraphQL 中的验证目录。js 包含实现符合规范的 GraphQL 验证器的代码。