Skip to content

Commit

Permalink
Add support for customValidateFn and rename formatError to customForm…
Browse files Browse the repository at this point in the history
…atErrorFn

* formatError is deprecated.
* customFormatErrorFn to replace formatError.
  • Loading branch information
jamesmoriarty authored and jliu670 committed Aug 14, 2020
1 parent aa3fcb4 commit 9e60e9c
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 21 deletions.
18 changes: 12 additions & 6 deletions README.md
Expand Up @@ -77,10 +77,6 @@ The `graphqlHTTP` function accepts the following options:

* **`pretty`**: If `true`, any JSON response will be pretty-printed.

* **`formatError`**: An optional function which will be used to format any
errors produced by fulfilling a GraphQL operation. If no function is
provided, GraphQL's default spec-compliant [`formatError`][] function will be used.

* **`extensions`**: An optional function for adding additional metadata to the
GraphQL response as a key-value object. The result will be added to
`"extensions"` field in the resulting JSON. This is often a useful place to
Expand All @@ -91,6 +87,16 @@ The `graphqlHTTP` function accepts the following options:
* **`validationRules`**: Optional additional validation rules queries must
satisfy in addition to those defined by the GraphQL spec.

* **`customValidateFn`**: An optional function which will be used to validate
instead of default `validate` from `graphql-js`.

* **`customFormatErrorFn`**: An optional function which will be used to format any
errors produced by fulfilling a GraphQL operation. If no function is
provided, GraphQL's default spec-compliant [`formatError`][] function will be used.

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

In addition to an object defining each option, options can also be provided as
a function (or async function) which returns this options object. This function
is provided the arguments `(request, response, graphQLParams)` and is called
Expand Down Expand Up @@ -288,10 +294,10 @@ graphqlHTTP.getGraphQLParams(request).then(params => {
## Debugging Tips

During development, it's useful to get more information from errors, such as
stack traces. Providing a function to `formatError` enables this:
stack traces. Providing a function to `customFormatErrorFn` enables this:

```js
formatError: error => ({
customFormatErrorFn: error => ({
message: error.message,
locations: error.locations,
stack: error.stack ? error.stack.split('\n') : [],
Expand Down
107 changes: 103 additions & 4 deletions src/__tests__/http-test.js
Expand Up @@ -28,6 +28,7 @@ import {
GraphQLString,
GraphQLError,
BREAK,
validate,
} from 'graphql';
import graphqlHTTP from '../';

Expand Down Expand Up @@ -1205,7 +1206,7 @@ describe('test harness', () => {
urlString(),
graphqlHTTP({
schema: TestSchema,
formatError(error) {
customFormatErrorFn(error) {
return { message: 'Custom error format: ' + error.message };
},
}),
Expand Down Expand Up @@ -1236,7 +1237,7 @@ describe('test harness', () => {
urlString(),
graphqlHTTP({
schema: TestSchema,
formatError(error) {
customFormatErrorFn(error) {
return {
message: error.message,
locations: error.locations,
Expand Down Expand Up @@ -1466,7 +1467,7 @@ describe('test harness', () => {
});
});

it('allows for custom error formatting of poorly formed requests', async () => {
it('`formatError` is deprecated', async () => {
const app = server();

get(
Expand All @@ -1480,6 +1481,46 @@ describe('test harness', () => {
}),
);

const spy = sinon.spy(console, 'warn');

const response = await request(app).get(
urlString({
variables: 'who:You',
query: 'query helloWho($who: String){ test(who: $who) }',
}),
);

expect(
spy.calledWith(
'`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.',
),
);
expect(response.status).to.equal(400);
expect(JSON.parse(response.text)).to.deep.equal({
errors: [
{
message: 'Custom error format: Variables are invalid JSON.',
},
],
});

spy.restore();
});

it('allows for custom error formatting of poorly formed requests', async () => {
const app = server();

get(
app,
urlString(),
graphqlHTTP({
schema: TestSchema,
customFormatErrorFn(error) {
return { message: 'Custom error format: ' + error.message };
},
}),
);

const response = await request(app).get(
urlString({
variables: 'who:You',
Expand Down Expand Up @@ -1859,6 +1900,64 @@ describe('test harness', () => {
});
});

describe('Custom validate function', () => {
it('returns data', async () => {
const app = server();

get(
app,
urlString(),
graphqlHTTP({
schema: TestSchema,
customValidateFn(schema, documentAST, validationRules) {
return validate(schema, documentAST, validationRules);
},
}),
);

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

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

it('returns validation errors', async () => {
const app = server();

get(
app,
urlString(),
graphqlHTTP({
schema: TestSchema,
customValidateFn(schema, documentAST, validationRules) {
const errors = validate(schema, documentAST, validationRules);

const error = new GraphQLError(`custom error ${errors.length}`);

return [error];
},
}),
);

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

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

describe('Custom validation rules', () => {
const AlwaysInvalidRule = function(context) {
return {
Expand Down Expand Up @@ -1946,7 +2045,7 @@ describe('test harness', () => {
urlString(),
graphqlHTTP({
schema: TestSchema,
formatError: () => null,
customFormatErrorFn: () => null,
extensions({ result }) {
return { preservedErrors: (result: any).errors };
},
Expand Down
52 changes: 41 additions & 11 deletions src/index.js
Expand Up @@ -72,18 +72,34 @@ export type OptionsData = {
*/
pretty?: ?boolean,

/**
* An optional array of validation rules that will be applied on the document
* in additional to those defined by the GraphQL spec.
*/
validationRules?: ?Array<(ValidationContext) => ASTVisitor>,

/**
* An optional function which will be used to validate instead of default `validate`
* from `graphql-js`.
*/
customValidateFn?: ?(
schema: GraphQLSchema,
documentAST: DocumentNode,
rules: $ReadOnlyArray<any>,
) => $ReadOnlyArray<GraphQLError>,

/**
* An optional function which will be used to format any errors produced by
* fulfilling a GraphQL operation. If no function is provided, GraphQL's
* default spec-compliant `formatError` function will be used.
*/
formatError?: ?(error: GraphQLError) => mixed,
customFormatErrorFn?: ?(error: GraphQLError) => mixed,

/**
* An optional array of validation rules that will be applied on the document
* in additional to those defined by the GraphQL spec.
* `formatError` is deprecated and replaced by `customFormatErrorFn`. It will
* be removed in version 1.0.0.
*/
validationRules?: ?Array<(ValidationContext) => ASTVisitor>,
formatError?: ?(error: GraphQLError) => mixed,

/**
* An optional function for adding additional metadata to the GraphQL response
Expand Down Expand Up @@ -158,7 +174,8 @@ function graphqlHTTP(options: Options): Middleware {
let context;
let params;
let pretty;
let formatErrorFn;
let formatErrorFn = formatError;
let validateFn = validate;
let extensionsFn;
let showGraphiQL;
let query;
Expand Down Expand Up @@ -201,7 +218,6 @@ function graphqlHTTP(options: Options): Middleware {
const rootValue = optionsData.rootValue;
const fieldResolver = optionsData.fieldResolver;
const graphiql = optionsData.graphiql;

context = optionsData.context || request;

let validationRules = specifiedRules;
Expand Down Expand Up @@ -251,7 +267,12 @@ function graphqlHTTP(options: Options): Middleware {
}

// Validate AST, reporting any errors.
const validationErrors = validate(schema, documentAST, validationRules);
const validationErrors = validateFn(
schema,
documentAST,
validationRules,
);

if (validationErrors.length > 0) {
// Return 400: Bad Request if any validation errors exist.
response.statusCode = 400;
Expand Down Expand Up @@ -333,9 +354,7 @@ function graphqlHTTP(options: Options): Middleware {
}
// Format any encountered errors.
if (result && result.errors) {
(result: any).errors = result.errors.map(
formatErrorFn || formatError,
);
(result: any).errors = result.errors.map(formatErrorFn);
}

// If allowed to show GraphiQL, present it instead of JSON.
Expand Down Expand Up @@ -380,7 +399,18 @@ function graphqlHTTP(options: Options): Middleware {
);
}

formatErrorFn = optionsData.formatError;
if (optionsData.formatError) {
// eslint-disable-next-line no-console
console.warn(
'`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.',
);
}

validateFn = optionsData.customValidateFn || validateFn;
formatErrorFn =
optionsData.customFormatErrorFn ||
optionsData.formatError ||
formatErrorFn;
extensionsFn = optionsData.extensions;
pretty = optionsData.pretty;
return optionsData;
Expand Down

0 comments on commit 9e60e9c

Please sign in to comment.