Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Add missing validation step (#241)
Browse files Browse the repository at this point in the history
* chore(validation): add missing validation step

* chore(package): update CHANGELOG.md
  • Loading branch information
DxCx authored and Urigo committed Jul 31, 2017
1 parent 0ff6e4e commit e16cf37
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Changelog

### vNEXT
- Add Validation step to server [PR #241](https://github.com/apollographql/subscriptions-transport-ws/pull/241)
- Call operation handler before delete the operation on operation complete [PR #239](https://github.com/apollographql/subscriptions-transport-ws/pull/239)

### 0.8.1
- Send first keep alive message right after the ack [PR #223](https://github.com/apollographql/subscriptions-transport-ws/pull/223)
- Return after first post-install when it should install dev dependencies [PR #218](https://github.com/apollographql/subscriptions-transport-ws/pull/218)
- On installing from branch install dev dependencies only if dist folder isnt found [PR #219](https://github.com/apollographql/subscriptions-transport-ws/pull/219)
- Call operation handler before delete the operation on operation complete [PR #239](https://github.com/apollographql/subscriptions-transport-ws/pull/239)

### 0.8.0
- Expose opId `onOperationComplete` method [PR #211](https://github.com/apollographql/subscriptions-transport-ws/pull/211)
Expand Down
27 changes: 25 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import MessageTypes from './message-types';
import { GRAPHQL_WS, GRAPHQL_SUBSCRIPTIONS } from './protocol';
import { SubscriptionManager } from 'graphql-subscriptions';
import isObject = require('lodash.isobject');
import { parse, ExecutionResult, GraphQLSchema, DocumentNode } from 'graphql';
import {
parse,
ExecutionResult,
GraphQLSchema,
DocumentNode,
validate,
ValidationContext,
specifiedRules,
} from 'graphql';
import { executeFromSubscriptionManager } from './adapters/subscription-manager';
import { createEmptyIterable } from './utils/empty-iterable';
import { createAsyncIterator, forAwaitEach, isAsyncIterable } from 'iterall';
Expand Down Expand Up @@ -57,6 +65,7 @@ export interface ServerOptions {
schema?: GraphQLSchema;
execute?: ExecuteFunction;
subscribe?: SubscribeFunction;
validationRules?: Array<(context: ValidationContext) => any>;

onOperation?: Function;
onOperationComplete?: Function;
Expand Down Expand Up @@ -90,6 +99,7 @@ export class SubscriptionServer {
private schema: GraphQLSchema;
private rootValue: any;
private keepAlive: number;
private specifiedRules: Array<(context: ValidationContext) => any>;

/**
* @deprecated onSubscribe is deprecated, use onOperation instead
Expand All @@ -110,6 +120,7 @@ export class SubscriptionServer {
onOperationComplete, onConnect, onDisconnect, keepAlive,
} = options;

this.specifiedRules = options.validationRules || specifiedRules;
this.loadExecutor(options);

this.onOperation = onSubscribe ? onSubscribe : onOperation;
Expand Down Expand Up @@ -360,8 +371,20 @@ export class SubscriptionServer {

const document = typeof baseParams.query !== 'string' ? baseParams.query : parse(baseParams.query);
let executionIterable: AsyncIterator<ExecutionResult>;
let validationErrors: Error[] = [];

if (this.subscribe && isASubscriptionOperation(document, params.operationName)) {
if ( this.schema ) {
// NOTE: This is a temporary condition to support the legacy subscription manager.
// As soon as subscriptionManager support is removed, we can remove the if
// and keep only the validation part.
validationErrors = validate(this.schema, document, this.specifiedRules);
}

if ( validationErrors.length > 0 ) {
executionIterable = createIterableFromPromise<ExecutionResult>(
Promise.resolve({ errors: validationErrors }),
);
} else if (this.subscribe && isASubscriptionOperation(document, params.operationName)) {
executionIterable = this.subscribe(this.schema,
document,
this.rootValue,
Expand Down
44 changes: 43 additions & 1 deletion src/test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from 'chai';
import * as sinon from 'sinon';
import * as WebSocket from 'ws';
import { execute, subscribe } from 'graphql';
import { specifiedRules, execute, subscribe } from 'graphql';

Object.assign(global, {
WebSocket: WebSocket,
Expand Down Expand Up @@ -2033,6 +2033,48 @@ describe('Client<->Server Flow', () => {
});
});

it('validate requests against schema', (done) => {
const testServer = createServer(notFoundRequestListener);
testServer.listen(SERVER_EXECUTOR_TESTS_PORT);

SubscriptionServer.create({
schema: subscriptionsSchema,
execute,
subscribe,
validationRules: specifiedRules,
}, {
server: testServer,
path: '/',
});

const client = new SubscriptionClient(`ws://localhost:${SERVER_EXECUTOR_TESTS_PORT}/`);
let isFirstTime = true;

client.onConnected(async () => {
// Manually close the connection only in the first time, to avoid infinite loop
if (isFirstTime) {
isFirstTime = false;

setTimeout(() => {
// Disconnect the client
client.close(false);

// Subscribe to data, without manually reconnect before
const opId = client.subscribe({
query: `query { invalid }`,
variables: {},
}, (err, res) => {
expect(opId).not.to.eq(null);
expect(res).to.eq(null);
expect(err[0].message).to.eq('Cannot query field "invalid" on type "Query".');
testServer.close();
done();
});
}, 300);
}
});
});

it('should close iteration over AsyncIterator when client unsubscribes', async () => {
subscriptionAsyncIteratorSpy.reset();
resolveAsyncIteratorSpy.reset();
Expand Down

0 comments on commit e16cf37

Please sign in to comment.