GraphQL Learn (3) - Validation
通过使用类型系统,可以预先确定GraphQL查询是否有效。 这样可以让服务器和客户端有效地通知开发人员在创建无效查询时,无需在运行时检查。
对于我们的星球大战示例,文件starWarsValidation-test.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": "C-3PO",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
{
"name": "R2-D2",
"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"
]
},
{
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
}
]
},
{
"name": "Leia Organa",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
{
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
},
{
"name": "C-3PO",
"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
}
]
}
]
}
当我们查询字段时,我们必须查询给定类型上存在的字段。 所以当hereo
返回一个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
}
]
}
]
}
每当我们查询一个字段并返回除标量或枚举之外的东西时,我们需要指定我们想从字段中获取的数据。 hero
返回一个Character
,而我们一直在请求其中的字段,如name
和appearIn
; 如果我们忽略,查询将不合法:
# INVALID: hero is not a scalar, so fields are needed
{
hero
}
{
"errors": [
{
"message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
类似地,如果一个字段是一个标量,查询其中的其他字段就没有意义,这样做会使查询不合法:
# 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
时,我们只能查询Character
上存在的字段。 如果要直接查询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,否则忽略该字段。 我们可以使用前面介绍的片段(Fragment)来做到这一点。 通过设置一个在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查询具有良好的语义和意义。规范中的"Validation"章节有更详细的介绍。validation 中包含了GraphQL符合规范的代码实现。