Skip to content

Add support for customParseFn option #147

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

Merged
merged 1 commit into from
Jul 28, 2021
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ The `graphqlHTTP` function accepts the following options:
errors produced by fulfilling a GraphQL operation. If no function is
provided, GraphQL's default spec-compliant [`formatError`][] function will be used.

- **`customParseFn`**: An optional function which will be used to create a document
instead of the default `parse` from `graphql-js`.

- **`formatError`**: is deprecated and replaced by `customFormatErrorFn`. It will be
removed in version 1.0.0.

Expand Down
180 changes: 120 additions & 60 deletions src/__tests__/http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import request from 'supertest';
import Koa from 'koa';
import mount from 'koa-mount';
import session from 'koa-session';
import parse from 'co-body';
import parseBody from 'co-body';
import getRawBody from 'raw-body';

import {
Expand All @@ -23,8 +23,10 @@ import {
GraphQLString,
GraphQLError,
BREAK,
Source,
validate,
execute,
parse,
} from 'graphql';
import graphqlHTTP from '../';

Expand Down Expand Up @@ -903,7 +905,7 @@ describe('GraphQL-HTTP tests', () => {
const app = server();
app.use(async function (ctx, next) {
if (ctx.is('application/graphql')) {
ctx.request.body = await parse.text(ctx);
ctx.request.body = await parseBody.text(ctx);
}
await next();
});
Expand All @@ -927,7 +929,7 @@ describe('GraphQL-HTTP tests', () => {
const app = server();
app.use(async function (ctx, next) {
if (ctx.is('*/*')) {
ctx.request.body = await parse.text(ctx);
ctx.request.body = await parseBody.text(ctx);
}
await next();
});
Expand Down Expand Up @@ -2021,6 +2023,121 @@ describe('GraphQL-HTTP tests', () => {
});
});

describe('Custom execute', () => {
it('allow to replace default execute', async () => {
const app = server();

let seenExecuteArgs;

app.use(
mount(
urlString(),
graphqlHTTP(() => ({
schema: TestSchema,
async customExecuteFn(...args) {
seenExecuteArgs = args;
const result = await Promise.resolve(execute(...args));
result.data.test2 = 'Modification';
return result;
},
})),
),
);

const response = await request(app.listen())
.get(urlString({ query: '{test}', raw: '' }))
.set('Accept', 'text/html');

expect(response.text).to.equal(
'{"data":{"test":"Hello World","test2":"Modification"}}',
);
expect(seenExecuteArgs).to.not.equal(null);
});

it('catches errors thrown from custom execute function', async () => {
const app = server();

app.use(
mount(
urlString(),
graphqlHTTP(() => ({
schema: TestSchema,
customExecuteFn() {
throw new Error('I did something wrong');
},
})),
),
);

const response = await request(app.listen())
.get(urlString({ query: '{test}', raw: '' }))
.set('Accept', 'text/html');

expect(response.status).to.equal(400);
expect(response.text).to.equal(
'{"errors":[{"message":"I did something wrong"}]}',
);
});
});

describe('Custom parse function', () => {
it('can replace default parse functionality', async () => {
const app = server();

let seenParseArgs;

app.use(
mount(
urlString(),
graphqlHTTP(() => {
return {
schema: TestSchema,
customParseFn(args) {
seenParseArgs = args;
return parse(new Source('{test}', 'Custom parse function'));
},
};
}),
),
);

const response = await request(app.listen()).get(
urlString({ query: '----' }),
);

expect(response.status).to.equal(200);
expect(response.text).to.equal('{"data":{"test":"Hello World"}}');
expect(seenParseArgs).property('body', '----');
});

it('can throw errors', async () => {
const app = server();

app.use(
mount(
urlString(),
graphqlHTTP(() => {
return {
schema: TestSchema,
customParseFn() {
throw new GraphQLError('my custom parse error');
},
};
}),
),
);

const response = await request(app.listen()).get(
urlString({ query: '----' }),
);

expect(response.status).to.equal(400);
expect(response.text).to.equal(
'{"errors":[{"message":"my custom parse error"}]}',
);
});
});

describe('Custom result extensions', () => {
it('allows for adding extensions', async () => {
const app = server();
Expand Down Expand Up @@ -2123,61 +2240,4 @@ describe('GraphQL-HTTP tests', () => {
);
});
});

describe('Custom execute', () => {
it('allow to replace default execute', async () => {
const app = server();

let seenExecuteArgs;

app.use(
mount(
urlString(),
graphqlHTTP(() => ({
schema: TestSchema,
async customExecuteFn(...args) {
seenExecuteArgs = args;
const result = await Promise.resolve(execute(...args));
result.data.test2 = 'Modification';
return result;
},
})),
),
);

const response = await request(app.listen())
.get(urlString({ query: '{test}', raw: '' }))
.set('Accept', 'text/html');

expect(response.text).to.equal(
'{"data":{"test":"Hello World","test2":"Modification"}}',
);
expect(seenExecuteArgs).to.not.equal(null);
});

it('catches errors thrown from custom execute function', async () => {
const app = server();

app.use(
mount(
urlString(),
graphqlHTTP(() => ({
schema: TestSchema,
customExecuteFn() {
throw new Error('I did something wrong');
},
})),
),
);

const response = await request(app.listen())
.get(urlString({ query: '{test}', raw: '' }))
.set('Accept', 'text/html');

expect(response.status).to.equal(400);
expect(response.text).to.equal(
'{"errors":[{"message":"I did something wrong"}]}',
);
});
});
});
10 changes: 9 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ export type OptionsData = {
*/
customFormatErrorFn?: ?(error: GraphQLError, context?: ?any) => mixed,

/**
* An optional function which will be used to create a document instead of
* the default `parse` from `graphql-js`.
*/
customParseFn?: ?(source: Source) => DocumentNode,

/**
* `formatError` is deprecated and replaced by `customFormatErrorFn`. It will
* be removed in version 1.0.0.
Expand Down Expand Up @@ -152,6 +158,7 @@ function graphqlHTTP(options: Options): Middleware {
let formatErrorFn = formatError;
let validateFn = validate;
let executeFn = execute;
let parseFn = parse;
let extensionsFn;
let showGraphiQL;
let query;
Expand Down Expand Up @@ -202,6 +209,7 @@ function graphqlHTTP(options: Options): Middleware {

validateFn = optionsData.customValidateFn || validateFn;
executeFn = optionsData.customExecuteFn || executeFn;
parseFn = optionsData.customParseFn || parseFn;
formatErrorFn =
optionsData.customFormatErrorFn ||
optionsData.formatError ||
Expand Down Expand Up @@ -253,7 +261,7 @@ function graphqlHTTP(options: Options): Middleware {

// Parse source to AST, reporting any syntax error.
try {
documentAST = parse(source);
documentAST = parseFn(source);
} catch (syntaxError) {
// Return 400: Bad Request if any syntax errors errors exist.
response.status = 400;
Expand Down