Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

Add support for customValidateFn and rename formatError to customFormatErrorFn #479

Merged
merged 2 commits into from Apr 6, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
jamesmoriarty marked this conversation as resolved.
Show resolved Hide resolved

* **`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