来自客户端的错误数据通过了 GraphQL 验证
Wrong data from client passes GraphQL validation
我已经使用 GraphQL 在 NestJS 服务器上使用 React 和 Apollo 客户端制作了简单的 CRUD 应用 API。
我有这个简单的突变:
schema.gql:
type Mutation {
createUser(input: CreateUserInput!): User! // CreateUserInput type you can see in user.input.ts below
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): User!
}
user.input.ts:
import { InputType, Field } from "@nestjs/graphql";
import { EmailScalar } from "../email.scalar-type";
@InputType()
export class CreateUserInput {
// EmailScalar is a custom Scalar GraphQL Type that i took from the internet and it worked well
@Field(() => EmailScalar)
readonly email: string;
@Field()
readonly name: string;
}
"EmailScalar" 类型检查 "email" 输入是否基本上具有 *@*.*
格式
当我像这样对 GraphQL API 进行 createUser 查询时:
It cannot pass validation
(因为电子邮件类型工作正常)
但是当从客户端发送查询时 - 它通过了验证:
NestJS server log(来自下面的代码)
users.resolver.ts:
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput) { // Type from user.input.ts
Logger.log(input); // log from screenshot, so if it's here it passed validation
return this.usersService.create(input); // usersService makes requests to MongoDB
}
And it gets into MongoDB
这是客户端部分:
App.tsx:
...
// CreateUserInput class is not imported to App.tsx (it is at server part) but it seems to be fine with it
const ADD_USER = gql`
mutation AddMutation($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`
function App(props: any) {
const { loading, error, data } = useQuery(GET_USERS);
const [addUser] = useMutation(
ADD_USER,
{
update: (cache: any, { data: { createUser } }: any) => {
const { users } = cache.readQuery({ query: GET_USERS });
cache.writeQuery({
query: GET_USERS,
data: {
users: [createUser, ...users],
},
})
}
}
);
...
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return <UserTable users={data.users} addUser={addUser} updateUser={updateUser} deleteUser={deleteUser} />;
}
谁能给我解释一下,客户端查询是如何通过验证的,我做错了什么?
即使是两个空字符串也能通过
之前从未使用过 NestJS、Apollo、React 或 GraphQL,所以我有点迷茫。
For full code:
https://github.com/N238635/nest-react-crud-test
自定义标量的方法是这样定义的:
parseValue(value: string): string {
return value;
}
serialize(value: string): string {
return value;
}
parseLiteral(ast: ValueNode): string {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
}
// Regex taken from:
var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!re.test(ast.value)) {
throw new GraphQLError('Query error: Not a valid Email', [ast]);
}
return ast.value;
}
parseLiteral
在解析查询中的文字值时调用(即用双引号引起来的文字字符串)。 parseValue
在解析变量值时被调用。当您的客户端发送查询时,它会将值作为变量而不是文字值发送。所以使用 parseValue
而不是 parseLiteral
。但是您的 parseValue
不进行任何类型的验证——您只是 return 原样的值。您需要在 both 方法中实现验证逻辑。
实施 serialize
方法也是一个好主意,这样您的标量就可以用于输入和响应验证。
我已经使用 GraphQL 在 NestJS 服务器上使用 React 和 Apollo 客户端制作了简单的 CRUD 应用 API。
我有这个简单的突变:
schema.gql:
type Mutation {
createUser(input: CreateUserInput!): User! // CreateUserInput type you can see in user.input.ts below
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): User!
}
user.input.ts:
import { InputType, Field } from "@nestjs/graphql";
import { EmailScalar } from "../email.scalar-type";
@InputType()
export class CreateUserInput {
// EmailScalar is a custom Scalar GraphQL Type that i took from the internet and it worked well
@Field(() => EmailScalar)
readonly email: string;
@Field()
readonly name: string;
}
"EmailScalar" 类型检查 "email" 输入是否基本上具有 *@*.*
格式
当我像这样对 GraphQL API 进行 createUser 查询时:
It cannot pass validation (因为电子邮件类型工作正常)
但是当从客户端发送查询时 - 它通过了验证:
NestJS server log(来自下面的代码)
users.resolver.ts:
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput) { // Type from user.input.ts
Logger.log(input); // log from screenshot, so if it's here it passed validation
return this.usersService.create(input); // usersService makes requests to MongoDB
}
And it gets into MongoDB
这是客户端部分:
App.tsx:
...
// CreateUserInput class is not imported to App.tsx (it is at server part) but it seems to be fine with it
const ADD_USER = gql`
mutation AddMutation($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`
function App(props: any) {
const { loading, error, data } = useQuery(GET_USERS);
const [addUser] = useMutation(
ADD_USER,
{
update: (cache: any, { data: { createUser } }: any) => {
const { users } = cache.readQuery({ query: GET_USERS });
cache.writeQuery({
query: GET_USERS,
data: {
users: [createUser, ...users],
},
})
}
}
);
...
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return <UserTable users={data.users} addUser={addUser} updateUser={updateUser} deleteUser={deleteUser} />;
}
谁能给我解释一下,客户端查询是如何通过验证的,我做错了什么?
即使是两个空字符串也能通过
之前从未使用过 NestJS、Apollo、React 或 GraphQL,所以我有点迷茫。
For full code: https://github.com/N238635/nest-react-crud-test
自定义标量的方法是这样定义的:
parseValue(value: string): string {
return value;
}
serialize(value: string): string {
return value;
}
parseLiteral(ast: ValueNode): string {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
}
// Regex taken from:
var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!re.test(ast.value)) {
throw new GraphQLError('Query error: Not a valid Email', [ast]);
}
return ast.value;
}
parseLiteral
在解析查询中的文字值时调用(即用双引号引起来的文字字符串)。 parseValue
在解析变量值时被调用。当您的客户端发送查询时,它会将值作为变量而不是文字值发送。所以使用 parseValue
而不是 parseLiteral
。但是您的 parseValue
不进行任何类型的验证——您只是 return 原样的值。您需要在 both 方法中实现验证逻辑。
实施 serialize
方法也是一个好主意,这样您的标量就可以用于输入和响应验证。