Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mercurius): add plugins option #2538

Merged
merged 2 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions packages/mercurius/lib/drivers/mercurius-federation.driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import { IncomingMessage, Server, ServerResponse } from 'http';
import mercurius from 'mercurius';
import { MercuriusDriverConfig } from '../interfaces/mercurius-driver-config.interface';
import { buildMercuriusFederatedSchema } from '../utils/build-mercurius-federated-schema.util';
import { registerMercuriusPlugin } from '../utils/register-mercurius-plugin.util';

@Injectable()
export class MercuriusFederationDriver extends AbstractGraphQLDriver<MercuriusDriverConfig> {
constructor(
private readonly graphqlFederationFactory: GraphQLFederationFactory,
) {
super();
}

get instance(): FastifyInstance<
Server,
IncomingMessage,
Expand All @@ -21,17 +28,12 @@ export class MercuriusFederationDriver extends AbstractGraphQLDriver<MercuriusDr
return this.httpAdapterHost?.httpAdapter?.getInstance?.();
}

constructor(
private readonly graphqlFederationFactory: GraphQLFederationFactory,
) {
super();
}

public async start(options: MercuriusDriverConfig) {
const adapterOptions = await this.graphqlFederationFactory.mergeWithSchema(
options,
buildMercuriusFederatedSchema,
);
const { plugins, ...adapterOptions } =
await this.graphqlFederationFactory.mergeWithSchema(
options,
buildMercuriusFederatedSchema,
);

if (adapterOptions.definitions && adapterOptions.definitions.path) {
await this.graphQlFactory.generateDefinitions(
Expand All @@ -50,6 +52,7 @@ export class MercuriusFederationDriver extends AbstractGraphQLDriver<MercuriusDr
await app.register(mercurius, {
...adapterOptions,
});
await registerMercuriusPlugin(app, plugins);
}

/* eslit-disable-next-line @typescript-eslint/no-empty-function */
Expand Down
5 changes: 4 additions & 1 deletion packages/mercurius/lib/drivers/mercurius-gateway.driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FastifyInstance, FastifyLoggerInstance } from 'fastify';
import { IncomingMessage, Server, ServerResponse } from 'http';
import mercurius from 'mercurius';
import { MercuriusDriverConfig } from '../interfaces/mercurius-driver-config.interface';
import { registerMercuriusPlugin } from '../utils/register-mercurius-plugin.util';

export class MercuriusGatewayDriver extends AbstractGraphQLDriver<MercuriusDriverConfig> {
get instance(): FastifyInstance<
Expand All @@ -22,10 +23,12 @@ export class MercuriusGatewayDriver extends AbstractGraphQLDriver<MercuriusDrive
throw new Error(`No support for current HttpAdapter: ${platformName}`);
}

const { plugins, ...mercuriusOptions } = options;
const app = httpAdapter.getInstance<FastifyInstance>();
await app.register(mercurius, {
...options,
...mercuriusOptions,
});
await registerMercuriusPlugin(app, plugins);
}

public async stop(): Promise<void> {}
Expand Down
4 changes: 3 additions & 1 deletion packages/mercurius/lib/drivers/mercurius.driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { printSchema } from 'graphql';
import { IncomingMessage, Server, ServerResponse } from 'http';
import mercurius from 'mercurius';
import { MercuriusDriverConfig } from '../interfaces/mercurius-driver-config.interface';
import { registerMercuriusPlugin } from '../utils/register-mercurius-plugin.util';

export class MercuriusDriver extends AbstractGraphQLDriver<MercuriusDriverConfig> {
get instance(): FastifyInstance<
Expand All @@ -17,7 +18,7 @@ export class MercuriusDriver extends AbstractGraphQLDriver<MercuriusDriverConfig
}

public async start(mercuriusOptions: MercuriusDriverConfig) {
const options =
const { plugins, ...options } =
await this.graphQlFactory.mergeWithSchema<MercuriusDriverConfig>(
mercuriusOptions,
);
Expand All @@ -39,6 +40,7 @@ export class MercuriusDriver extends AbstractGraphQLDriver<MercuriusDriverConfig
await app.register(mercurius, {
...options,
});
await registerMercuriusPlugin(app, plugins);
}

public async stop(): Promise<void> {}
Expand Down
1 change: 1 addition & 0 deletions packages/mercurius/lib/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './mercurius-driver-config.interface';
export * from './mercurius-federation-driver-config.interface';
export * from './mercurius-gateway-driver-config.interface';
export * from './mercurius-plugin.interface';
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import {
GqlOptionsFactory,
} from '@nestjs/graphql';
import { MercuriusOptions } from 'mercurius';
import { MercuriusPlugins } from './mercurius-plugin.interface';

export type MercuriusDriverConfig = GqlModuleOptions & MercuriusOptions;
export type MercuriusDriverConfig = GqlModuleOptions &
MercuriusOptions &
MercuriusPlugins;

export type MercuriusDriverConfigFactory =
GqlOptionsFactory<MercuriusDriverConfig>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
GqlOptionsFactory,
} from '@nestjs/graphql';
import { MercuriusCommonOptions, MercuriusGatewayOptions } from 'mercurius';
import { MercuriusPlugin } from './mercurius-plugin.interface';

export type MercuriusGatewayDriverConfig = GqlModuleOptions &
MercuriusCommonOptions &
MercuriusGatewayOptions;
MercuriusGatewayOptions &
MercuriusPlugin;

export type MercuriusGatewayDriverConfigFactory =
GqlOptionsFactory<MercuriusGatewayDriverConfig>;
Expand Down
24 changes: 24 additions & 0 deletions packages/mercurius/lib/interfaces/mercurius-plugin.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
FastifyPluginAsync,
FastifyPluginCallback,
FastifyPluginOptions,
FastifyRegisterOptions,
} from 'fastify';

export interface MercuriusPlugin<
Options extends FastifyPluginOptions = unknown,
> {
plugin:
| FastifyPluginCallback<Options>
| FastifyPluginAsync<Options>
| Promise<{
default: FastifyPluginCallback<Options>;
}>;
options?: FastifyRegisterOptions<Options>;
}

export interface MercuriusPlugins<
Options extends FastifyPluginOptions = unknown,
> {
plugins?: MercuriusPlugin<Options>[];
}
21 changes: 21 additions & 0 deletions packages/mercurius/lib/utils/register-mercurius-plugin.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FastifyInstance } from 'fastify';
import { MercuriusPlugin } from '../interfaces/mercurius-plugin.interface';
import { isArray, isNull, isUndefined } from './validation.util';

export async function registerMercuriusPlugin(
app: FastifyInstance,
plugins?: MercuriusPlugin[],
): Promise<void> {
if (
isUndefined(plugins) ||
isNull(plugins) ||
!isArray(plugins) ||
plugins.length === 0
) {
return;
}

for (const plugin of plugins) {
await app.register(plugin.plugin, plugin.options);
}
}
9 changes: 9 additions & 0 deletions packages/mercurius/lib/utils/validation.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const isUndefined = (value: unknown): value is undefined => {
return typeof value === 'undefined';
};

export const isNull = (value: unknown): value is null => value === null;

export const isArray = <T>(value: unknown): value is T[] => {
return Array.isArray(value);
};
51 changes: 51 additions & 0 deletions packages/mercurius/tests/e2e/code-first-plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { INestApplication } from '@nestjs/common';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { FastifyInstance } from 'fastify';
import { ApplicationModule } from '../plugins/code-first-plugin/app.module';
import { NEW_PLUGIN_URL } from '../plugins/mocks/utils/constants';

describe('Code-first with plugins', () => {
let app: INestApplication;

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [ApplicationModule],
}).compile();

app = module.createNestApplication(new FastifyAdapter());
await app.init();
});

it('should get the plugin', async () => {
const fastifyInstance: FastifyInstance = app.getHttpAdapter().getInstance();
await fastifyInstance.ready();

const response = await fastifyInstance.inject({
method: 'GET',
url: NEW_PLUGIN_URL,
});
const data = JSON.parse(response.body);
expect(fastifyInstance.printPlugins().includes('mockPlugin')).toBe(true);
expect(response.statusCode).toBe(200);
expect(data.from).toBe(NEW_PLUGIN_URL);
});

it('it should query dog', async () => {
const fastifyInstance: FastifyInstance = app.getHttpAdapter().getInstance();
await fastifyInstance.ready();

const response = await fastifyInstance.graphql(`
{
getAnimalName
}
`);
expect(response.data).toEqual({
getAnimalName: 'dog',
});
});

afterEach(async () => {
await app.close();
});
});
72 changes: 72 additions & 0 deletions packages/mercurius/tests/e2e/graphql-federation-plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { INestApplication } from '@nestjs/common';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { FastifyInstance } from 'fastify';
import { AppModule as PostsModule } from '../plugins/graphql-federation-plugin/posts-service/federation-posts.module';
import { AppModule as UsersModule } from '../plugins/graphql-federation-plugin/users-service/federation-users.module';
import {
BASE_PLUGIN_URL,
NEW_PLUGIN_URL,
} from '../plugins/mocks/utils/constants';

describe('GraphQL Federation with plugins', () => {
let app: INestApplication;

describe('UsersService', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [UsersModule],
}).compile();

app = module.createNestApplication(new FastifyAdapter());
await app.init();
});

it('should get the plugin for users', async () => {
const fastifyInstance: FastifyInstance = app
.getHttpAdapter()
.getInstance();
await fastifyInstance.ready();

const response = await fastifyInstance.inject({
method: 'GET',
url: NEW_PLUGIN_URL,
});
const data = JSON.parse(response.body);
expect(fastifyInstance.printPlugins().includes('mockPlugin')).toBe(true);
expect(response.statusCode).toBe(200);
expect(data.from).toBe(NEW_PLUGIN_URL);
});
});

describe('PostsService', () => {
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [PostsModule],
}).compile();

app = module.createNestApplication(new FastifyAdapter());
await app.init();
});

it('should get the plugin for posts', async () => {
const fastifyInstance: FastifyInstance = app
.getHttpAdapter()
.getInstance();
await fastifyInstance.ready();

const response = await fastifyInstance.inject({
method: 'GET',
url: BASE_PLUGIN_URL,
});
const data = JSON.parse(response.body);
expect(fastifyInstance.printPlugins().includes('mockPlugin')).toBe(true);
expect(response.statusCode).toBe(200);
expect(data.from).toBe(BASE_PLUGIN_URL);
});
});

afterEach(async () => {
await app.close();
});
});
55 changes: 55 additions & 0 deletions packages/mercurius/tests/e2e/graphql-gateway-plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { INestApplication } from '@nestjs/common';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module';
import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module';
import { AppModule as GatewayModule } from '../plugins/graphql-federation-plugin/gateway/gateway.module';
import { BASE_PLUGIN_URL } from '../plugins/mocks/utils/constants';

describe('GraphQL Gateway', () => {
let postsApp: INestApplication;
let usersApp: INestApplication;
let gatewayApp: INestApplication;

beforeEach(async () => {
const usersModule = await Test.createTestingModule({
imports: [UsersModule],
}).compile();

usersApp = usersModule.createNestApplication(new FastifyAdapter());
await usersApp.listen(3011);

const postsModule = await Test.createTestingModule({
imports: [PostsModule],
}).compile();

postsApp = postsModule.createNestApplication(new FastifyAdapter());
await postsApp.listen(3012);

const gatewayModule = await Test.createTestingModule({
imports: [GatewayModule],
}).compile();

gatewayApp = gatewayModule.createNestApplication(new FastifyAdapter());
await gatewayApp.init();

await gatewayApp.getHttpAdapter().getInstance().ready();
});

it('should get the plugin url', () => {
return request(gatewayApp.getHttpServer())
.get(BASE_PLUGIN_URL)
.expect(200)
.expect('Content-Type', /json/)
.expect({
from: BASE_PLUGIN_URL,
});
});

afterEach(async () => {
await postsApp.close();
await usersApp.close();
await gatewayApp.close();
});
});
26 changes: 26 additions & 0 deletions packages/mercurius/tests/plugins/code-first-plugin/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { MercuriusDriverConfig } from '../../../lib';
import { MercuriusDriver } from '../../../lib/drivers';
import { mockPlugin } from '../mocks/mock.plugin';
import { NEW_PLUGIN_URL } from '../mocks/utils/constants';
import { DogsModule } from './dogs/dogs.module';

@Module({
imports: [
DogsModule,
GraphQLModule.forRoot<MercuriusDriverConfig>({
driver: MercuriusDriver,
autoSchemaFile: true,
plugins: [
{
plugin: mockPlugin,
options: {
url: NEW_PLUGIN_URL,
},
},
],
}),
],
})
export class ApplicationModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { DogsResolver } from './dogs.resolver';

@Module({
providers: [DogsResolver],
})
export class DogsModule {}
Loading