验证
了解 GraphQL 如何使用 Schema 校验操作
在本页中,我们将探讨 GraphQL 请求生命周期中一个重要的阶段,称为校验 (validation)。一个请求不仅要语法正确才能运行,还必须通过针对 API Schema 的校验。
在实践中,当 GraphQL 操作到达服务器时,文档首先被解析,然后使用类型系统进行校验。这使得服务器和客户端能够在创建无效查询时有效地通知开发者,而无需依赖运行时检查。一旦操作通过校验,它就可以在服务器上执行,并将响应交付给客户端。
校验示例
GraphQL 规范详细描述了请求被视为有效必须满足的条件。在接下来的章节中,我们将查看 GraphQL 操作中可能出现的一些常见校验问题的示例。
请求不存在的字段
当我们查询一个字段时,该字段必须在相关类型上定义。由于 hero 返回 Character 类型,其选择集只能请求 Character 类型的字段;由于 Character 没有 favoriteSpaceship 字段,因此该查询无效。
选择集和叶子字段
每当我们查询一个返回标量 (Scalar) 或枚举 (Enum) 类型以外的字段时,我们需要指定想要从该字段获取哪些数据(即“选择集”)。hero 查询字段返回一个 Character,我们已经见过请求其 name 和 appearsIn 等字段的示例。如果我们省略这些叶子字段的选择,查询将无效。
同样,查询标量或枚举的字段是没有意义的,因此为叶子字段添加选择集也会导致查询无效。
输出抽象类型的字段缺少片段
前面提到过,查询只能请求目标类型上存在的字段。因此,当我们查询返回 Character 的 hero 时,我们只能请求 Character 接口类型上存在的字段。但是,如果我们想查询 R2-D2 的主要功能 (primary function) 该怎么办呢?
该查询无效,因为 primaryFunction 不是 Character 接口类型定义的共享字段之一。我们需要某种方式来表明:如果 Character 是 Droid 类型,我们就获取 primaryFunction,否则忽略该字段。我们可以使用片段 (fragments) 来实现这一点。通过设置一个定义在 Droid 上的片段并将其包含在选择集中,我们可以确保仅在定义了该字段的地方查询 primaryFunction。
这个查询是有效的,但有点冗长;当我们需要多次复用片段时,具名片段非常有价值,但这里我们只使用了一次。我们可以使用内联片段 (inline fragment) 来代替具名片段;这仍然允许我们指定查询的类型,但无需为片段单独命名。
循环片段传播
首先,让我们看一个复杂的有效查询。这是一个嵌套查询,但重复的字段已被提取到片段中。
以下是上述查询的一种替代方案,尝试使用递归而不是显式的三层嵌套。这个新查询是无效的,因为片段不能(直接或间接)引用自身,因为由此产生的循环可能会创建一个无界的查询结果!
这仅仅触及了校验系统的皮毛;为了确保 GraphQL 操作在语义上有意义,系统中设有许多校验规则。规范在校验章节中更详细地介绍了这一主题,而参考实现中的 validation 目录包含了实现符合规范的 GraphQL 校验器的代码。
校验错误
正如我们在上面的示例中所看到的,当 GraphQL 服务器在请求中遇到校验错误时,它会在响应的 errors 键中返回相关信息。具体而言,当 GraphQL 在文档中检测到校验问题时,它会在执行开始前抛出请求错误 (request error),这意味着响应中不会包含任何部分数据。
由于 GraphQL 规范要求所有实现都必须根据 Schema 校验传入的请求,开发者无需编写特定的运行时逻辑来手动处理这些校验问题。
后续步骤
回顾一下我们学到的关于校验的知识:
- 要执行请求,必须包含一个语法正确的文档,且该文档在根据 Schema 检查时被视为有效
- 规范要求实现必须检查传入请求是否包含有效的字段选择、正确的片段用法等
- 当发生校验问题时,服务器将抛出请求错误并向客户端返回相关信息;字段执行将不会开始
前往执行 (Execution) 页面,了解在校验步骤成功完成后,GraphQL 如何为请求中的每个字段提供数据。
执行
了解解析器 (resolvers) 如何驱动 GraphQL 执行,以及服务器如何处理并返回每个查询的数据。