GraphQL Learn (3) - Validation

Web

通过使用类型系统,可以预先确定GraphQL查询是否有效。 这样可以让服务器和客户端有效地通知开发人员在创建无效查询时,无需在运行时检查。

对于我们的星球大战示例,文件starWarsValidation-test.js包含许多无效的查询,可以用来测试当前实现的验证器。

首先,我们来看一个复杂的有效查询。 这是一个嵌套查询,类似于上一节的一个示例,但将重复的字段分解成一个片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
hero {
...NameAndAppearances
friends {
...NameAndAppearances
friends {
...NameAndAppearances
}
}
}
}
fragment NameAndAppearances on Character {
name
appearsIn
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{
"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"
]
}
]
}
]
}
}
}

这个查询是合法的,让我们看一些非法的查询:

片段不能引用自身或创建一个循环,因为这可能导致无限循环! 以上是上述相同的查询,但没有弄明白的三层的嵌套关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# { "graphiql": true }
{
hero {
...NameAndAppearancesAndFriends
}
}
fragment NameAndAppearancesAndFriends on Character {
name
appearsIn
friends {
...NameAndAppearancesAndFriends
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"errors": [
{
"message": "Cannot spread fragment \"NameAndAppearancesAndFriends\" within itself.",
"locations": [
{
"line": 11,
"column": 5
}
]
}
]
}

当我们查询字段时,我们必须查询给定类型上存在的字段。 所以当hereo返回一个Character时,我们必须在一个Character字段上查询,然而该类型没有favoriteSpaceship字段,因此此查询无效:

1
2
3
4
5
6
7
# { "graphiql": true }
# INVALID: favoriteSpaceship does not exist on Character
{
hero {
favoriteSpaceship
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"errors": [
{
"message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}

每当我们查询一个字段并返回除标量或枚举之外的东西时,我们需要指定我们想从字段中获取的数据。 hero返回一个Character,而我们一直在请求其中的字段,如nameappearIn; 如果我们忽略,查询将不合法:

1
2
3
4
5
# { "graphiql": true }
# INVALID: hero is not a scalar, so fields are needed
{
hero
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"errors": [
{
"message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}

类似地,如果一个字段是一个标量,查询其中的其他字段就没有意义,这样做会使查询不合法:

1
2
3
4
5
6
7
8
9
# { "graphiql": true }
# INVALID: name is a scalar, so fields are not permitted
{
hero {
name {
firstCharacterOfName
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"errors": [
{
"message": "Field \"name\" must not have a selection since type \"String!\" has no subfields.",
"locations": [
{
"line": 4,
"column": 10
}
]
}
]
}

之前,有人指出,查询只能查询有关类型的字段; 当我们查询返回一个Characterhero时,我们只能查询Character上存在的字段。 如果要直接查询R2-D2s的主要功能,会发生什么?

1
2
3
4
5
6
7
8
# { "graphiql": true }
# INVALID: primaryFunction does not exist on Character
{
hero {
name
primaryFunction
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"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上字段。 我们想要一些判断,如果CharacterDroid,则获取primaryFunction,否则忽略该字段。 我们可以使用前面介绍的片段(Fragment)来做到这一点。 通过设置一个在Droid上定义的片段并包含它,我们确保我们只查询已定义的primaryFunction

1
2
3
4
5
6
7
8
9
10
11
# { "graphiql": true }
{
hero {
name
...DroidFields
}
}
fragment DroidFields on Droid {
primaryFunction
}
1
2
3
4
5
6
7
8
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}

这个查询是合法的,但它有点冗长; 当我们需要多次使用它时,命名片段是有价值的,但是如果我们只使用一次,我们可以直接使用内联片段。

1
2
3
4
5
6
7
8
9
# { "graphiql": true }
{
hero {
name
... on Droid {
primaryFunction
}
}
}
1
2
3
4
5
6
7
8
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}

以上只是验证系统的表面一层,事实上存在这很多验证规则来确保GraphQL查询具有良好的语义和意义。规范中的”Validation”章节有更详细的介绍。validation 中包含了GraphQL符合规范的代码实现。