Skip to content

Commit

Permalink
Merge pull request #46 from apollostack/apollo-hapi
Browse files Browse the repository at this point in the history
Apollo hapi
  • Loading branch information
helfer authored Jul 27, 2016
2 parents dd05d27 + 622a46a commit eba3f3e
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 25 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { apolloExpress, graphiqlExpress } from './integrations/expressApollo';
export { ApolloHAPI, GraphiQLHAPI } from './integrations/hapiApollo';
4 changes: 3 additions & 1 deletion src/integrations/expressApollo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ describe('renderGraphiQL', () => {
});
});

testSuite(createApp);
describe('integration:Express', () => {
testSuite(createApp);
});
33 changes: 33 additions & 0 deletions src/integrations/hapiApollo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as hapi from 'hapi';
import { ApolloHAPI, GraphiQLHAPI } from './hapiApollo';

import testSuite, { Schema, CreateAppOptions } from './integrations.test';

function createApp(options: CreateAppOptions = {}) {
const server = new hapi.Server();

server.connection({
host: 'localhost',
port: 8000,
});

options.apolloOptions = options.apolloOptions || { schema: Schema };

server.register({
register: new ApolloHAPI(),
options: options.apolloOptions,
routes: { prefix: '/graphql' },
});

server.register({
register: new GraphiQLHAPI(),
options: { endpointURL: '/graphql' },
routes: { prefix: '/graphiql' },
});

return server.listener;
}

describe('integration:HAPI', () => {
testSuite(createApp);
});
135 changes: 114 additions & 21 deletions src/integrations/hapiApollo.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,145 @@
import * as hapi from 'hapi';
import * as graphql from 'graphql';
import * as GraphiQL from '../modules/renderGraphiQL';
import { runQuery } from '../core/runQuery';
import ApolloOptions from './apolloOptions';


export interface IRegister {
(server: hapi.Server, options: any, next: any): void;
attributes?: any;
}

export interface HapiApolloOptions {
schema: graphql.GraphQLSchema;
formatError?: Function;
rootValue?: any;
context?: any;
logFunction?: Function;
export interface HAPIOptionsFunction {
(req?: hapi.Request): ApolloOptions | Promise<ApolloOptions>;
}

export class HapiApollo {
export class ApolloHAPI {
constructor() {
this.register.attributes = {
name: 'graphql',
version: '0.0.1',
};
}

public register: IRegister = (server: hapi.Server, options: HapiApolloOptions, next) => {
public register: IRegister = (server: hapi.Server, options: ApolloOptions | HAPIOptionsFunction, next) => {
server.route({
method: 'GET',
path: '/test',
handler: (request, reply) => {
reply('test passed');
method: 'POST',
path: '/',
handler: async (request, reply) => {
let optionsObject: ApolloOptions;
if (isOptionsFunction(options)) {
try {
optionsObject = await options(request);
} catch (e) {
reply(`Invalid options provided to ApolloServer: ${e.message}`).code(500);
}
} else {
optionsObject = options;
}

if (!request.payload) {
reply('POST body missing.').code(500);
return;
}

const responses = await processQuery(request.payload, optionsObject);

if (responses.length > 1) {
reply(responses);
} else {
const gqlResponse = responses[0];
if (gqlResponse.errors && typeof gqlResponse.data === 'undefined') {
reply(gqlResponse).code(400);
} else {
reply(gqlResponse);
}
}

},
});
next();
}
}

export class GraphiQLHAPI {
constructor() {
this.register.attributes = {
name: 'graphiql',
version: '0.0.1',
};
}

public register: IRegister = (server: hapi.Server, options: GraphiQL.GraphiQLData, next) => {
server.route({
method: 'POST',
method: 'GET',
path: '/',
handler: (request, reply) => {
return runQuery({
schema: options.schema,
query: request.payload,
}).then(gqlResponse => {
reply({ data: gqlResponse.data });
}).catch(errors => {
reply({ errors: errors }).code(500);
});
const q = request.query || {};
const query = q.query || '';
const variables = q.variables || '{}';
const operationName = q.operationName || '';

const graphiQLString = GraphiQL.renderGraphiQL({
endpointURL: options.endpointURL,
query: query || options.query,
variables: JSON.parse(variables) || options.variables,
operationName: operationName || options.operationName,
});
reply(graphiQLString).header('Content-Type', 'text/html');
},
});
next();
}
}

async function processQuery(body, optionsObject) {
const formatErrorFn = optionsObject.formatError || graphql.formatError;

let isBatch = true;
// TODO: do something different here if the body is an array.
// Throw an error if body isn't either array or object.
if (!Array.isArray(body)) {
isBatch = false;
body = [body];
}

let responses: Array<graphql.GraphQLResult> = [];
for (let payload of body) {
try {
const operationName = payload.operationName;
let variables = payload.variables;

if (typeof variables === 'string') {
// TODO: catch errors
variables = JSON.parse(variables);
}

let params = {
schema: optionsObject.schema,
query: payload.query,
variables: variables,
rootValue: optionsObject.rootValue,
context: optionsObject.context,
operationName: operationName,
logFunction: optionsObject.logFunction,
validationRules: optionsObject.validationRules,
formatError: formatErrorFn,
formatResponse: optionsObject.formatResponse,
};

if (optionsObject.formatParams) {
params = optionsObject.formatParams(params);
}

responses.push(await runQuery(params));
} catch (e) {
responses.push({ errors: [formatErrorFn(e)] });
}
}
return responses;
}

function isOptionsFunction(arg: ApolloOptions | HAPIOptionsFunction): arg is HAPIOptionsFunction {
return typeof arg === 'function';
}
4 changes: 1 addition & 3 deletions src/integrations/integrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,7 @@ export default (createApp: CreateAppFunc) => {
const app = createApp({excludeParser: true});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testString }',
});
.send();
return req.then((res) => {
expect(res.status).to.equal(500);
return expect(res.error.text).to.contain('POST body missing.');
Expand Down
1 change: 1 addition & 0 deletions src/test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ require('source-map-support').install();
import '../core/runQuery.test';
import '../modules/operationStore.test';
import '../integrations/expressApollo.test';
import '../integrations/hapiApollo.test';
import './testApolloServerHTTP';

0 comments on commit eba3f3e

Please sign in to comment.