本文预期读者阅读过本专栏以前的两篇文章javascript
《【第十期】基于 Apollo、Koa 搭建 GraphQL 服务端》前端
和java
《【第十一期】实现 Javascript 版本的 Laravel 风格参数验证器》node
或对
GraphQL
与Laravel
的验证器有所了解。git
前面两篇文章分别讲解了:github
GraphQL
服务器Laravel
风格的验证器今天咱们来尝试将两者结合,在 GraphQL
工程中实现一个 Laravel
风格的高级验证器。npm
一个 GraphQL
请求,会经历三个阶段:json
Parse phase
)Validation phase
)Execution phase
)其中,在验证阶段(Validation phase
),会根据 GraphQL SDL
的类型系统,对参数进行基本校验:bash
可是,对于一些稍复杂场景,类型系统的功能没法覆盖到:服务器
所以,咱们针对更加复杂一些的校验规则,须要一个更高级的验证器。
肯定了需求,咱们来看如何实现这个高级验证器。
咱们知道自定义标量(custom scalar
)能够限制一个字段值的类型,所以在标量上作高级验证器是个不错的开始。
例如:对于年龄字段,咱们新设计一个名为 age
的标量,限制它的取值范围为 0 到 150 之间。对于出生日期字段同理:birthDay
。
可是,这么作有一个问题:咱们的字段类型各类各样,没个尽头,若是为每个类型的字段都设计一个标量,那么咱们将被迫维护数量庞大的标量库。
若是标量能支持参数,咱们只须要将各类高级验证规则抽象为一组 rules
库就行了,这样在不一样字段类型之间,能够复用一些 rules
,避免了标量库随着字段类型的增长而增加的问题。例如: age(max:150,min:0)
或 birthDay(Date,lt:2020-01-01,gt:1900-01-01)
惋惜的是,目前为止,GraphQL
的实现对于标量并不支持设置参数,所以,咱们只能寻求其余的方式。
除了自定义标量外,还有自定义指令(custom directive
)。
Apollo GraphQL
提供了一种方式,有兴趣的读者能够去参考:经过自定义指令动态生成自定义标量
考虑到动态自定义标量对于研发人员并不友好(自定义标量定义在自定义指令的代码中,这增长了阅读和理解工程的成本)
咱们选择使用:经过自定义指令调整解析器的方式来实现高级校验。
@validation
,此指令做用于字段定义上,并支持一个参数 rules
,值的类型为字符串。GraphQL
服务启动时,在自定义指令 @validation
内部,针对定义了 rules
的字段,会调整其解析器,在其原有解析器外围包裹一层验证器逻辑。在解析器执行期间,验证逻辑会执行并对字段值进行校验。rule
的解析和校验工做,由 validator-simple
库提供支持(validator-simple
库是咱们在以前的文章《【第十一期】实现 Javascript 版本的 Laravel 风格参数验证器》中实现的)单个字段的多个 rules
之间,使用 |
分割
字段名称与 rules
之间,使用 =>
分割
多个字段校验描述,使用英文分号 ;
来分割。例如:
gql`
extend type Mutation {
createBook(
book: inputBook
): Book @validation(
rules: "book.name => max:5|min:3;book.price => max:999|min:10"
)
}
`
复制代码
虽然 GraphQL 标准中不容许字符串换行,但为了可读性,咱们能够在外部定义可读性更好的描述:
const createBookValidationRules = `"` +
`book.name => max:5|min:3;` +
`book.price => max:999|min:10` +
`"`
gql`
extend type Mutation {
createBook(
book: inputBook
): Book @validation(
rules: ${createBookValidationRules}
)
}
`
复制代码
rules
的列表,请查看 validator-simple开始前,准备好:
NodeJS
实现的 GraphQl
工程。本文咱们使用文章《【第十期】基于 Apollo、Koa 搭建 GraphQL 服务端》中建立的 graphql-server-demo
工程JavaScript
实现的 Laravel
风格验证器。本文咱们使用文章《【第十一期】实现 Javascript 版本的 Laravel 风格参数验证器》中建立的 npm
库 validator-simple
开始以前,graphql-server-demo
工程的目录结构以下:
.
├── index.js
├── package.json
├── src
│ ├── components
│ │ ├── book
│ │ │ ├── resolver.js
│ │ │ └── schema.js
│ │ └── cat
│ │ ├── resolver.js
│ │ └── schema.js
│ ├── graphql
│ │ ├── directives
│ │ │ ├── auth.js
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── scalars
│ │ ├── date.js
│ │ └── index.js
│ └── middlewares
│ └── auth.js
└── yarn.lock
复制代码
安装 validator-simple
:
yarn add validator-simple@1.0.1
复制代码
注意:在文章《【第十一期】实现 Javascript 版本的 Laravel 风格参数验证器》中建立的 v1.0.0 版本的
validator-simple
并不支持在rules
中使用.
符号指定深层字段名。在 v1.0.1 版本支持此功能。
validator-simple
由于咱们设计好了在 GraphQL schema
中表达验证规则的语法,它和 validator-simple
的语法有些许差别。
所以,咱们建立一个文件 src/libs/validation.js
来作适配的工做。
代码以下:
const V = require('validator-simple')
const findFirstInvalidParam = (params, rules) => {
const serializationRules = {}
rules.split(';').forEach(item => {
const [itemName, itemRules] = item.split('=>')
serializationRules[itemName.trim()] = itemRules.trim()
})
const invalidMsg = V(params, serializationRules)
if (invalidMsg && invalidMsg.length) return invalidMsg[0]
}
module.exports = {
findFirstInvalidParam
}
复制代码
@validation
接下来,在文件夹 src/graphql/directives
中新建文件 validation.js
内容以下:
const { SchemaDirectiveVisitor, UserInputError } = require('apollo-server-koa')
const { defaultFieldResolver } = require('graphql')
const { findFirstInvalidParam } = require('../../libs/validation.js')
class VallidationDirective extends SchemaDirectiveVisitor {
visitFieldDefinition (field) {
this.modifyResolver(field)
}
modifyResolver (field) {
const { resolve = defaultFieldResolver } = field
const { rules } = this.args
if (!rules) return
field.resolve = async function (...args) {
const invalidInfo = findFirstInvalidParam(args[1], rules)
if (invalidInfo) throw new UserInputError(invalidInfo.invalidMessage)
return resolve.apply(this, args)
}
}
}
module.exports = {
validation: VallidationDirective
}
复制代码
在 src/graphql/directives/index.js
中导出指令:
module.exports = {
...require('./validation.js'),
...require('./auth.js')
}
复制代码
而后在 src/graphql/index.js
中注册新的自定义指令:
...
directive @auth on FIELD_DEFINITION
# 注册验证器指令
directive @validation(rules: String) on FIELD_DEFINITION
type Query {
_: Boolean
}
...
复制代码
@validation
打开文件 src/components/book/schema.js
,并增长一个建立 book
的 mutation
,并对 book
字段使用咱们刚刚注册好的验证器指令 @validation
代码以下:
...
extend type Mutation {
createBook ( book: inputBook ): Book! @validation(
rules: "book.name => max:5|min:3;book.price => max:999|min:10"
)
}
...
复制代码
保存文件,启动服务,而后发出一个建立 book
的请求,并有意填写一个过长的名称,来验证一下咱们刚才设置的规则:
curl 'http://localhost:4000/graphql' \
-H 'Content-Type: application/json' \
--data-binary '{"query":"mutation createBook($newBook: inputBook) {\n createBook(book: $newBook) {\n name\n price\n created\n }\n}\n","variables":{"newBook":{"name":"this is new book name","price":100,"created":"2019-01-01"}}}' \
--compressed
复制代码
上面的请求发出后,咱们会收到下面的响应内容:
{
"errors":[
{
"code":"BAD_USER_INPUT",
"message":"book.name 的长度或大小不能大于 5. 实际值为:this is new book name"
}
],
"data":null
}
复制代码
经过响应结果,咱们看到验证器已经生效了。
最终,graphql-server-demo
的目录结构以下:
.
├── index.js
├── package.json
├── src
│ ├── components
│ │ ├── book
│ │ │ ├── resolver.js
│ │ │ └── schema.js
│ │ └── cat
│ │ ├── resolver.js
│ │ └── schema.js
│ ├── graphql
│ │ ├── directives
│ │ │ ├── auth.js
│ │ │ ├── index.js
│ │ │ └── validation.js
│ │ ├── index.js
│ │ └── scalars
│ │ ├── date.js
│ │ └── index.js
│ ├── libs
│ │ └── validation.js
│ └── middlewares
│ └── auth.js
└── yarn.lock
复制代码
至此,咱们的高级验证器就开发完毕了。
从此只须要根据实际需求在 validator-simple
中增长新的验证规则,就能很容易得在 @validation
指令中使用它们。
validator-simple
只是一个为了方便表达文章内容而建立的库。 这里推荐一个更成熟的库node-input-validator
水滴前端团队招募伙伴,欢迎投递简历到邮箱:fed@shuidihuzhu.com