From 24aa36a98241cbab5f0cc9c1ca0302c73625ff63 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Mon, 10 Dec 2018 19:49:11 -0500 Subject: [PATCH] Prevent illegal resolver invocation and use way to access children cache Signed-off-by: Arda TANRIKULU --- .../src/errors/illegal-resolver-invocation.ts | 26 +++++++++++++++++++ packages/core/src/errors/index.ts | 1 + packages/core/src/graphql-module.ts | 20 ++++++++++---- packages/core/tests/graphql-module.spec.ts | 23 +++++++++++++++- 4 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/errors/illegal-resolver-invocation.ts diff --git a/packages/core/src/errors/illegal-resolver-invocation.ts b/packages/core/src/errors/illegal-resolver-invocation.ts new file mode 100644 index 0000000000..f019a25db8 --- /dev/null +++ b/packages/core/src/errors/illegal-resolver-invocation.ts @@ -0,0 +1,26 @@ +export class IllegalResolverInvocationError extends Error { + constructor(private _resolverPath: string, private _moduleName: string, private _detail: string) { + super(` + GraphQL-Modules Error: Illegal Resolver Invocation! + - Resolver #${_resolverPath} is invoked unsafely outside of GraphQL-Modules. + -- Detail: ${_detail} + + Possible solutions: + - You may forget to pass context of the module to your GraphQL Server. + -- Check if it is passed like below; + --- const { schema, context } = YourModule; + --- new ApolloServer({ schema, context });' + `); + Object.setPrototypeOf(this, IllegalResolverInvocationError.prototype); + Error.captureStackTrace(this, IllegalResolverInvocationError); + } + get resolverPath() { + return this._resolverPath; + } + get moduleName() { + return this._moduleName; + } + get detail() { + return this._detail; + } +} diff --git a/packages/core/src/errors/index.ts b/packages/core/src/errors/index.ts index d4e785decc..991270f8f0 100644 --- a/packages/core/src/errors/index.ts +++ b/packages/core/src/errors/index.ts @@ -4,3 +4,4 @@ export { DependencyModuleUndefinedError } from './dependency-module-undefined'; export { TypeDefNotFoundError } from './typedef-not-found'; export { ProviderClassNotDecoratedError } from './provider-class-not-decorated'; export { ModuleConfigRequiredError } from './module-config-required'; +export { IllegalResolverInvocationError } from './illegal-resolver-invocation'; diff --git a/packages/core/src/graphql-module.ts b/packages/core/src/graphql-module.ts index 9bdd5361cd..c18cf610d6 100644 --- a/packages/core/src/graphql-module.ts +++ b/packages/core/src/graphql-module.ts @@ -4,7 +4,7 @@ import { Provider, Injector, ProviderScope } from '@graphql-modules/di'; import { DocumentNode, GraphQLSchema, parse } from 'graphql'; import { IResolversComposerMapping, composeResolvers } from './resolvers-composition'; import { DepGraph } from 'dependency-graph'; -import { DependencyModuleNotFoundError, SchemaNotValidError, DependencyModuleUndefinedError, TypeDefNotFoundError, ModuleConfigRequiredError } from './errors'; +import { DependencyModuleNotFoundError, SchemaNotValidError, DependencyModuleUndefinedError, TypeDefNotFoundError, ModuleConfigRequiredError, IllegalResolverInvocationError } from './errors'; import * as deepmerge from 'deepmerge'; import { ModuleSessionInfo } from './module-session-info'; import { asArray } from './utils'; @@ -385,6 +385,15 @@ export class GraphQLModule { return directiveResolvers; } + private checkIfResolverCalledSafely(resolverPath: string, appContext: any, info: any) { + if (!('networkRequest' in appContext)) { + throw new IllegalResolverInvocationError(resolverPath, this.name, `Network Request hasn't been passed!`); + } + if (typeof info === 'undefined') { + throw new IllegalResolverInvocationError(resolverPath, this.name, `GraphQL Resolve Information hasn't been passed!`); + } + } + private addSessionInjectorToSelfResolversContext() { const resolvers = this.selfResolvers; // tslint:disable-next-line:forin @@ -396,6 +405,7 @@ export class GraphQLModule { if (typeof resolver === 'function') { if (prop !== '__resolveType') { typeResolvers[prop] = async (root: any, args: any, appContext: any, info: any) => { + this.checkIfResolverCalledSafely(`${type}.${prop}`, appContext, info); const { networkRequest } = appContext; const moduleContext = await this.context(networkRequest); info.schema = this._cache.schema; @@ -403,6 +413,7 @@ export class GraphQLModule { }; } else { typeResolvers[prop] = async (root: any, appContext: any, info: any) => { + this.checkIfResolverCalledSafely(`${type}.${prop}`, appContext, info); const { networkRequest } = appContext; const moduleContext = await this.context(networkRequest); info.schema = this._cache.schema; @@ -422,6 +433,7 @@ export class GraphQLModule { const compositionArr = asArray(resolversComposition[path]); resolversComposition[path] = [ (next: any) => async (root: any, args: any, appContext: any, info: any) => { + this.checkIfResolverCalledSafely(path, appContext, info); const { networkRequest } = appContext; const moduleContext = await this.context(networkRequest); info.schema = this._cache.schema; @@ -474,13 +486,11 @@ export class GraphQLModule { if (module._cache.modulesMap !== modulesMap) { module._cache.modulesMap = modulesMap; - module._cache.injector = undefined; - module._cache.schema = undefined; - module._cache.contextBuilder = undefined; module.buildSchemaAndInjector(); } - const { injector, resolvers, typeDefs, contextBuilder, schemaDirectives, extraSchemas, directiveResolvers } = module._cache; + const { injector, resolvers, typeDefs, schemaDirectives } = module; + const { contextBuilder, extraSchemas, directiveResolvers } = module._cache; importsInjectors.add(injector); importsResolvers.add(resolvers); diff --git a/packages/core/tests/graphql-module.spec.ts b/packages/core/tests/graphql-module.spec.ts index 6f2b837e02..5b94a223b6 100644 --- a/packages/core/tests/graphql-module.spec.ts +++ b/packages/core/tests/graphql-module.spec.ts @@ -8,7 +8,7 @@ import { OnRequest, ModuleConfigRequiredError, } from '../src'; -import { execute, GraphQLSchema, printSchema, GraphQLString, defaultFieldResolver } from 'graphql'; +import { execute, GraphQLSchema, printSchema, GraphQLString, defaultFieldResolver, print } from 'graphql'; import { stripWhitespaces } from './utils'; import gql from 'graphql-tag'; import { SchemaDirectiveVisitor, makeExecutableSchema } from 'graphql-tools'; @@ -987,4 +987,25 @@ describe('GraphQLModule', () => { expect(result.errors).toBeFalsy(); expect(result.data['foo']).toBe('FOO'); }); + it('should export correct typeDefs and resolvers', async () => { + const gqlModule = new GraphQLModule({ + imports: [ + new GraphQLModule({ + name: 'test', + typeDefs: 'type Query { test: Int }', + resolvers: { + Query: { + test: () => 1, + }, + }, + }), + ], + }); + + const typeDefs = gqlModule.typeDefs; + expect(stripWhitespaces(print(typeDefs))).toBe(stripWhitespaces('type Query { test: Int }')); + const context = await gqlModule.context({}); + const resolvers = gqlModule.resolvers; + expect(await resolvers['Query']['test'](null, {}, context, {})).toBe(1); + }); });