Skip to content

Commit

Permalink
feat(server): Use validate option for custom GraphQL validation
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo committed Mar 25, 2021
1 parent 6a0bf94 commit b68d56c
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 3 deletions.
27 changes: 26 additions & 1 deletion README.md
Expand Up @@ -1042,10 +1042,35 @@ useServer(
);
```

<details id="custom-validation">
<summary><a href="#custom-validation">🔗</a> <a href="https://github.com/websockets/ws">ws</a> server usage with custom validation</summary>

```typescript
import { execute, subscribe, validate } from 'graphql';
import ws from 'ws'; // yarn add ws
import { useServer } from 'graphql-ws/lib/use/ws';
import { schema, myValidationRules } from './my-graphql';

const wsServer = new ws.Server({
port: 443,
path: '/graphql',
});

useServer(
{
execute,
subscribe,
validate: (schema, document) =>
validate(schema, document, myValidationRules),
},
wsServer,
);
```

</details>

<details id="custom-exec">
<summary><a href="#custom-exec">🔗</a> <a href="https://github.com/websockets/ws">ws</a> server usage with custom execution arguments and validation</summary>
<summary><a href="#custom-exec">🔗</a> <a href="https://github.com/websockets/ws">ws</a> server usage with custom execution arguments</summary>

```typescript
import { parse, validate, execute, subscribe } from 'graphql';
Expand Down
36 changes: 36 additions & 0 deletions docs/interfaces/server.serveroptions.md
Expand Up @@ -28,6 +28,7 @@ Name | Default |
- [roots](server.serveroptions.md#roots)
- [schema](server.serveroptions.md#schema)
- [subscribe](server.serveroptions.md#subscribe)
- [validate](server.serveroptions.md#validate)

## Properties

Expand Down Expand Up @@ -451,3 +452,38 @@ Name | Type |
`args` | ExecutionArgs |

**Returns:** [*OperationResult*](../modules/server.md#operationresult)

___

### validate

`Optional` **validate**: (`schema`: *GraphQLSchema*, `documentAST`: DocumentNode, `rules?`: readonly ValidationRule[], `typeInfo?`: *TypeInfo*, `options?`: { `maxErrors?`: *number* }) => readonly *GraphQLError*[]

A custom GraphQL validate function allowing you to apply your
own validation rules.

Returned, non-empty, array of `GraphQLError`s will be communicated
to the client through the `ErrorMessage`. Use an empty array if the
document is valid and no errors have been encountered.

Will not be used when implementing a custom `onSubscribe`.

Throwing an error from within this function will close the socket
with the `Error` message in the close event reason.

#### Type declaration:

▸ (`schema`: *GraphQLSchema*, `documentAST`: DocumentNode, `rules?`: readonly ValidationRule[], `typeInfo?`: *TypeInfo*, `options?`: { `maxErrors?`: *number* }): readonly *GraphQLError*[]

#### Parameters:

Name | Type |
:------ | :------ |
`schema` | *GraphQLSchema* |
`documentAST` | DocumentNode |
`rules?` | readonly ValidationRule[] |
`typeInfo?` | *TypeInfo* |
`options?` | *object* |
`options.maxErrors?` | *number* |

**Returns:** readonly *GraphQLError*[]
28 changes: 26 additions & 2 deletions src/server.ts
Expand Up @@ -9,11 +9,14 @@ import {
GraphQLSchema,
ExecutionArgs,
parse,
validate,
validate as graphqlValidate,
getOperationAST,
GraphQLError,
SubscriptionArgs,
ExecutionResult,
DocumentNode,
ValidationRule,
TypeInfo,
} from 'graphql';
import { GRAPHQL_TRANSPORT_WS_PROTOCOL } from './protocol';
import {
Expand Down Expand Up @@ -114,6 +117,26 @@ export interface ServerOptions<E = unknown> {
NonNullable<SubscriptionArgs['rootValue']>
>;
};
/**
* A custom GraphQL validate function allowing you to apply your
* own validation rules.
*
* Returned, non-empty, array of `GraphQLError`s will be communicated
* to the client through the `ErrorMessage`. Use an empty array if the
* document is valid and no errors have been encountered.
*
* Will not be used when implementing a custom `onSubscribe`.
*
* Throwing an error from within this function will close the socket
* with the `Error` message in the close event reason.
*/
validate?: (
schema: GraphQLSchema,
documentAST: DocumentNode,
rules?: ReadonlyArray<ValidationRule>,
typeInfo?: TypeInfo,
options?: { maxErrors?: number },
) => ReadonlyArray<GraphQLError>;
/**
* Is the `execute` function from GraphQL which is
* used to execute the query and mutation operations.
Expand Down Expand Up @@ -447,6 +470,7 @@ export function makeServer<E = unknown>(options: ServerOptions<E>): Server<E> {
schema,
context,
roots,
validate,
execute,
subscribe,
connectionInitWaitTimeout = 3 * 1000, // 3 seconds
Expand Down Expand Up @@ -619,7 +643,7 @@ export function makeServer<E = unknown>(options: ServerOptions<E>): Server<E> {
? await schema(ctx, message, args)
: schema,
};
const validationErrors = validate(
const validationErrors = (validate ?? graphqlValidate)(
execArgs.schema,
execArgs.document,
);
Expand Down
31 changes: 31 additions & 0 deletions src/tests/server.ts
Expand Up @@ -93,6 +93,37 @@ it('should use the schema resolved from a promise on subscribe', async (done) =>
);
});

it('should use the provided validate function', async () => {
const { url } = await startTServer({
schema,
validate: () => [new GraphQLError('Nothing is valid')],
});
const client = await createTClient(url, GRAPHQL_TRANSPORT_WS_PROTOCOL);
client.ws.send(
stringifyMessage<MessageType.ConnectionInit>({
type: MessageType.ConnectionInit,
}),
);
await client.waitForMessage(); // ack

client.ws.send(
stringifyMessage<MessageType.Subscribe>({
id: '1',
type: MessageType.Subscribe,
payload: {
query: '{ getValue }',
},
}),
);
await client.waitForMessage(({ data }) => {
expect(parseMessage(data)).toEqual({
id: '1',
type: MessageType.Error,
payload: [{ message: 'Nothing is valid' }],
});
});
});

it('should use the provided roots as resolvers', async () => {
const schema = buildSchema(`
type Query {
Expand Down

0 comments on commit b68d56c

Please sign in to comment.