mirror of
https://github.com/vale981/apollo-server
synced 2025-03-06 02:01:40 -05:00
Merge pull request #426 from PhiloInc/graphiql-function
Allow GraphiQLOptions to be a function
This commit is contained in:
commit
54d7d9eeb4
16 changed files with 197 additions and 135 deletions
|
@ -1,6 +1,7 @@
|
|||
# Changelog
|
||||
|
||||
### VNEXT
|
||||
* Allow GraphiQLOptions to be a function ([@NeoPhi](https://github.com/NeoPhi)) on [#426](https://github.com/apollographql/graphql-server/pull/426)
|
||||
|
||||
### v0.8.5
|
||||
* Fix: graphql-server-micro now properly returns response promises [#401](https://github.com/apollographql/graphql-server/pull/401)
|
||||
|
|
|
@ -53,6 +53,10 @@ export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFu
|
|||
};
|
||||
}
|
||||
|
||||
export interface ExpressGraphiQLOptionsFunction {
|
||||
(req?: express.Request): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
/* This middleware returns the html for the GraphiQL interactive query UI
|
||||
*
|
||||
* GraphiQLData arguments
|
||||
|
@ -64,22 +68,13 @@ export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFu
|
|||
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI
|
||||
*/
|
||||
|
||||
export function graphiqlExpress(options: GraphiQL.GraphiQLData) {
|
||||
return (req: express.Request, res: express.Response) => {
|
||||
const q = req.url && url.parse(req.url, true).query || {};
|
||||
const query = q.query || '';
|
||||
const operationName = q.operationName || '';
|
||||
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: options.endpointURL,
|
||||
subscriptionsEndpoint: options.subscriptionsEndpoint,
|
||||
query: query || options.query,
|
||||
variables: q.variables && JSON.parse(q.variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
export function graphiqlExpress(options: GraphiQL.GraphiQLData | ExpressGraphiQLOptionsFunction) {
|
||||
return (req: express.Request, res: express.Response, next) => {
|
||||
const query = req.url && url.parse(req.url, true).query;
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiQLString);
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
}, error => next(error));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export {
|
||||
ExpressGraphQLOptionsFunction,
|
||||
ExpressHandler,
|
||||
ExpressGraphiQLOptionsFunction,
|
||||
graphqlExpress,
|
||||
graphiqlExpress,
|
||||
} from './expressApollo';
|
||||
|
|
|
@ -70,38 +70,38 @@ graphqlHapi.attributes = {
|
|||
version: '0.0.1',
|
||||
};
|
||||
|
||||
export interface GraphiQLPluginOptions {
|
||||
path: string;
|
||||
route?: any;
|
||||
graphiqlOptions: GraphiQL.GraphiQLData;
|
||||
export interface HapiGraphiQLOptionsFunction {
|
||||
(req?: Request): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
const graphiqlHapi: IRegister = function(server: Server, options: GraphiQLPluginOptions, next) {
|
||||
server.method('getGraphiQLParams', getGraphiQLParams);
|
||||
server.method('renderGraphiQL', renderGraphiQL);
|
||||
export interface HapiGraphiQLPluginOptions {
|
||||
path: string;
|
||||
route?: any;
|
||||
graphiqlOptions: GraphiQL.GraphiQLData | HapiGraphiQLOptionsFunction;
|
||||
}
|
||||
|
||||
const config = Object.assign(options.route || {}, {
|
||||
plugins: {
|
||||
graphiql: options.graphiqlOptions,
|
||||
},
|
||||
pre: [{
|
||||
assign: 'graphiqlParams',
|
||||
method: 'getGraphiQLParams',
|
||||
}, {
|
||||
assign: 'graphiQLString',
|
||||
method: 'renderGraphiQL(route, pre.graphiqlParams)',
|
||||
}],
|
||||
});
|
||||
const graphiqlHapi: IRegister = function(server: Server, options: HapiGraphiQLPluginOptions, next) {
|
||||
if (!options || !options.graphiqlOptions) {
|
||||
throw new Error('Apollo Server GraphiQL requires options.');
|
||||
}
|
||||
|
||||
if (arguments.length !== 3) {
|
||||
throw new Error(`Apollo Server GraphiQL expects exactly 3 arguments, got ${arguments.length}`);
|
||||
}
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: options.path || '/graphql',
|
||||
config,
|
||||
path: options.path || '/graphiql',
|
||||
config: options.route || {},
|
||||
handler: (request, reply) => {
|
||||
reply(request.pre['graphiQLString']).header('Content-Type', 'text/html');
|
||||
const query = request.query;
|
||||
GraphiQL.resolveGraphiQLString(query, options.graphiqlOptions, request).then(graphiqlString => {
|
||||
reply(graphiqlString).header('Content-Type', 'text/html');
|
||||
}, error => reply(error));
|
||||
},
|
||||
});
|
||||
next();
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
graphiqlHapi.attributes = {
|
||||
|
@ -109,25 +109,4 @@ graphiqlHapi.attributes = {
|
|||
version: '0.0.1',
|
||||
};
|
||||
|
||||
function getGraphiQLParams(request, reply) {
|
||||
const q = request.query || {};
|
||||
const query = q.query || '';
|
||||
const variables = q.variables;
|
||||
const operationName = q.operationName || '';
|
||||
reply({ query, variables, operationName});
|
||||
}
|
||||
|
||||
function renderGraphiQL(route, graphiqlParams: any, reply) {
|
||||
const graphiqlOptions = route.settings.plugins['graphiql'];
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: graphiqlOptions.endpointURL,
|
||||
subscriptionsEndpoint: graphiqlOptions.subscriptionsEndpoint,
|
||||
query: graphiqlParams.query || graphiqlOptions.query,
|
||||
variables: graphiqlParams.variables && JSON.parse(graphiqlParams.variables) || graphiqlOptions.variables,
|
||||
operationName: graphiqlParams.operationName || graphiqlOptions.operationName,
|
||||
passHeader: graphiqlOptions.passHeader,
|
||||
});
|
||||
reply(graphiQLString);
|
||||
}
|
||||
|
||||
export { graphqlHapi, graphiqlHapi };
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export { IRegister,
|
||||
HapiOptionsFunction,
|
||||
HapiPluginOptions,
|
||||
GraphiQLPluginOptions,
|
||||
HapiGraphiQLOptionsFunction,
|
||||
HapiGraphiQLPluginOptions,
|
||||
graphqlHapi, graphiqlHapi } from './hapiApollo';
|
||||
|
|
|
@ -120,7 +120,7 @@ export const schema = new GraphQLSchema({
|
|||
export interface CreateAppOptions {
|
||||
excludeParser?: boolean;
|
||||
graphqlOptions?: GraphQLOptions | {(): GraphQLOptions | Promise<{}>};
|
||||
graphiqlOptions?: GraphiQL.GraphiQLData;
|
||||
graphiqlOptions?: GraphiQL.GraphiQLData | {(): GraphiQL.GraphiQLData | Promise<{}>};
|
||||
}
|
||||
|
||||
export interface CreateAppFunc {
|
||||
|
@ -630,7 +630,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
|
|||
app = createApp({graphqlOptions: {
|
||||
schema,
|
||||
formatError: (err) => {
|
||||
throw new Error('I should be catched');
|
||||
throw new Error('I should be caught');
|
||||
},
|
||||
}});
|
||||
const req = request(app)
|
||||
|
@ -727,6 +727,32 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('allows options to be a function', () => {
|
||||
app = createApp({graphiqlOptions: () => ({
|
||||
endpointURL: '/graphql',
|
||||
})});
|
||||
|
||||
const req = request(app)
|
||||
.get('/graphiql')
|
||||
.set('Accept', 'text/html');
|
||||
return req.then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles options function errors', () => {
|
||||
app = createApp({graphiqlOptions: () => {
|
||||
throw new Error('I should be caught');
|
||||
}});
|
||||
|
||||
const req = request(app)
|
||||
.get('/graphiql')
|
||||
.set('Accept', 'text/html');
|
||||
return req.then((response) => {
|
||||
expect(response.status).to.equal(500);
|
||||
});
|
||||
});
|
||||
|
||||
it('presents options variables', () => {
|
||||
app = createApp({graphiqlOptions: {
|
||||
endpointURL: '/graphql',
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
export { KoaGraphQLOptionsFunction, KoaHandler, graphqlKoa, graphiqlKoa } from './koaApollo';
|
||||
export {
|
||||
KoaGraphQLOptionsFunction,
|
||||
KoaHandler,
|
||||
KoaGraphiQLOptionsFunction,
|
||||
graphqlKoa,
|
||||
graphiqlKoa,
|
||||
} from './koaApollo';
|
||||
|
|
|
@ -44,22 +44,19 @@ export function graphqlKoa(options: GraphQLOptions | KoaGraphQLOptionsFunction):
|
|||
};
|
||||
}
|
||||
|
||||
export function graphiqlKoa(options: GraphiQL.GraphiQLData) {
|
||||
export interface KoaGraphiQLOptionsFunction {
|
||||
(ctx: koa.Context): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
export function graphiqlKoa(options: GraphiQL.GraphiQLData | KoaGraphiQLOptionsFunction) {
|
||||
return (ctx: koa.Context) => {
|
||||
|
||||
const q = ctx.request.query || {};
|
||||
const query = q.query || '';
|
||||
const operationName = q.operationName || '';
|
||||
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: options.endpointURL,
|
||||
subscriptionsEndpoint: options.subscriptionsEndpoint,
|
||||
query: query || options.query,
|
||||
variables: q.variables && JSON.parse(q.variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
const query = ctx.request.query;
|
||||
GraphiQL.resolveGraphiQLString(query, options, ctx).then(graphiqlString => {
|
||||
ctx.set('Content-Type', 'text/html');
|
||||
ctx.body = graphiQLString;
|
||||
ctx.body = graphiqlString;
|
||||
}, error => {
|
||||
ctx.status = 500;
|
||||
ctx.body = error.message;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export {
|
||||
LambdaHandler,
|
||||
IHeaders,
|
||||
LambdaGraphQLOptionsFunction,
|
||||
LambdaGraphiQLOptionsFunction,
|
||||
graphqlLambda,
|
||||
graphiqlLambda,
|
||||
} from './lambdaApollo';
|
||||
|
|
|
@ -67,6 +67,10 @@ export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFun
|
|||
};
|
||||
}
|
||||
|
||||
export interface LambdaGraphiQLOptionsFunction {
|
||||
(event: any, context: lambda.Context): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
/* This Lambda Function Handler returns the html for the GraphiQL interactive query UI
|
||||
*
|
||||
* GraphiQLData arguments
|
||||
|
@ -78,21 +82,10 @@ export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFun
|
|||
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI
|
||||
*/
|
||||
|
||||
export function graphiqlLambda(options: GraphiQL.GraphiQLData) {
|
||||
export function graphiqlLambda(options: GraphiQL.GraphiQLData | LambdaGraphiQLOptionsFunction) {
|
||||
return (event, lambdaContext: lambda.Context, callback: lambda.Callback) => {
|
||||
const q = event.queryStringParameters || {};
|
||||
const query = q.query || '';
|
||||
const variables = q.variables || '{}';
|
||||
const operationName = q.operationName || '';
|
||||
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: options.endpointURL,
|
||||
subscriptionsEndpoint: options.subscriptionsEndpoint,
|
||||
query: query || options.query,
|
||||
variables: q.variables && JSON.parse(variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
const query = event.queryStringParameters;
|
||||
GraphiQL.resolveGraphiQLString(query, options, event, lambdaContext).then(graphiqlString => {
|
||||
callback(
|
||||
null,
|
||||
{
|
||||
|
@ -100,8 +93,14 @@ export function graphiqlLambda(options: GraphiQL.GraphiQLData) {
|
|||
'headers': {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
'body': graphiQLString,
|
||||
'body': graphiqlString,
|
||||
},
|
||||
);
|
||||
}, error => {
|
||||
callback(null, {
|
||||
statusCode: 500,
|
||||
body: error.message,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
export { MicroGraphQLOptionsFunction,
|
||||
microGraphql, microGraphiql } from './microApollo';
|
||||
export {
|
||||
MicroGraphQLOptionsFunction,
|
||||
MicroGraphiQLOptionsFunction,
|
||||
microGraphql,
|
||||
microGraphiql,
|
||||
} from './microApollo';
|
||||
|
|
|
@ -56,21 +56,21 @@ export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFuncti
|
|||
};
|
||||
}
|
||||
|
||||
export function microGraphiql(options: GraphiQL.GraphiQLData): RequestHandler {
|
||||
return (req: IncomingMessage, res: ServerResponse) => {
|
||||
const q = req.url && url.parse(req.url, true).query || {};
|
||||
const query = q.query || '';
|
||||
const operationName = q.operationName || '';
|
||||
export interface MicroGraphiQLOptionsFunction {
|
||||
(req?: IncomingMessage): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: options.endpointURL,
|
||||
query: query || options.query,
|
||||
variables: q.variables && JSON.parse(q.variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
export function microGraphiql(options: GraphiQL.GraphiQLData | MicroGraphiQLOptionsFunction): RequestHandler {
|
||||
return (req: IncomingMessage, res: ServerResponse) => {
|
||||
const query = req.url && url.parse(req.url, true).query || {};
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiQLString);
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
}, error => {
|
||||
res.statusCode = 500;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export { GraphiQLData, renderGraphiQL } from './renderGraphiQL';
|
||||
export { resolveGraphiQLString } from './resolveGraphiQLString';
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { GraphiQLData, renderGraphiQL } from './renderGraphiQL';
|
||||
|
||||
export type GraphiQLParams = {
|
||||
query?: string,
|
||||
variables?: string,
|
||||
operationName?: string,
|
||||
};
|
||||
|
||||
function isOptionsFunction(arg: GraphiQLData | Function): arg is Function {
|
||||
return typeof arg === 'function';
|
||||
}
|
||||
|
||||
async function resolveGraphiQLOptions(options: GraphiQLData | Function, ...args): Promise<GraphiQLData> {
|
||||
if (isOptionsFunction(options)) {
|
||||
try {
|
||||
return await options(...args);
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid options provided for GraphiQL: ${e.message}`);
|
||||
}
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
function createGraphiQLParams(query: any = {}): GraphiQLParams {
|
||||
return {
|
||||
query: query.query || '',
|
||||
variables: query.variables,
|
||||
operationName: query.operationName || '',
|
||||
};
|
||||
}
|
||||
|
||||
function createGraphiQLData(params: GraphiQLParams, options: GraphiQLData): GraphiQLData {
|
||||
return {
|
||||
endpointURL: options.endpointURL,
|
||||
subscriptionsEndpoint: options.subscriptionsEndpoint,
|
||||
query: params.query || options.query,
|
||||
variables: params.variables && JSON.parse(params.variables) || options.variables,
|
||||
operationName: params.operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveGraphiQLString(query: any = {}, options: GraphiQLData | Function, ...args): Promise<string> {
|
||||
const graphiqlParams = createGraphiQLParams(query);
|
||||
const graphiqlOptions = await resolveGraphiQLOptions(options, ...args);
|
||||
const graphiqlData = createGraphiQLData(graphiqlParams, graphiqlOptions);
|
||||
return renderGraphiQL(graphiqlData);
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
export {
|
||||
RestifyGraphQLOptionsFunction,
|
||||
RestifyHandler,
|
||||
RestifyGraphiQLOptionsFunction,
|
||||
graphqlRestify,
|
||||
graphiqlRestify,
|
||||
} from './restifyApollo';
|
||||
|
|
|
@ -54,6 +54,10 @@ export function graphqlRestify(options: GraphQLOptions | RestifyGraphQLOptionsFu
|
|||
};
|
||||
}
|
||||
|
||||
export interface RestifyGraphiQLOptionsFunction {
|
||||
(req?: restify.Request): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
/* This middleware returns the html for the GraphiQL interactive query UI
|
||||
*
|
||||
* GraphiQLData arguments
|
||||
|
@ -65,23 +69,19 @@ export function graphqlRestify(options: GraphQLOptions | RestifyGraphQLOptionsFu
|
|||
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI
|
||||
*/
|
||||
|
||||
export function graphiqlRestify(options: GraphiQL.GraphiQLData) {
|
||||
export function graphiqlRestify(options: GraphiQL.GraphiQLData | RestifyGraphiQLOptionsFunction) {
|
||||
return (req: restify.Request, res: restify.Response, next: restify.Next) => {
|
||||
const q = req.url && url.parse(req.url, true).query || {};
|
||||
const query = q.query || '';
|
||||
const operationName = q.operationName || '';
|
||||
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: options.endpointURL,
|
||||
subscriptionsEndpoint: options.subscriptionsEndpoint,
|
||||
query: query || options.query,
|
||||
variables: q.variables && JSON.parse(q.variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
const query = req.url && url.parse(req.url, true).query || {};
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiQLString);
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
next();
|
||||
}, error => {
|
||||
res.statusCode = 500;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
next(false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue