Skip to content
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

feat: remove handler for validation and parse errors #1510

51 changes: 19 additions & 32 deletions packages/core/src/plugins/use-masked-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,67 @@ import { handleStreamOrSingleExecutionResult } from '../utils.js';

export const DEFAULT_ERROR_MESSAGE = 'Unexpected error.';

export type FormatErrorHandler = (error: unknown, message: string, isDev: boolean) => Error;
export type MaskErrorFn = (error: unknown, message: string) => Error;

export type SerializableGraphQLErrorLike = Error & {
name: 'GraphQLError';
toJSON(): { message: string };
saihaj marked this conversation as resolved.
Show resolved Hide resolved
};

export function isGraphQLError(error: unknown): error is Error & { originalError?: Error } {
return error instanceof Error && error.name === 'GraphQLError';
}

function createSerializableError(message: string, originalError?: Error) {
export function createSerializableGraphQLError(message: string): SerializableGraphQLErrorLike {
const error = new Error(message);
error.name = 'GraphQLError';
Object.defineProperty(error, 'toJSON', {
value() {
if (originalError) {
return {
message: error.message,
extensions: {
originalError: {
name: originalError.name,
message: originalError.message,
stack: originalError.stack,
},
},
};
}
return {
message: error.message,
};
},
});
return error;
return error as SerializableGraphQLErrorLike;
}

export const formatError: FormatErrorHandler = (err, message, isDev) => {
export const defaultMaskErrorFn: MaskErrorFn = (err, message) => {
if (isGraphQLError(err)) {
if (err?.originalError) {
if (isGraphQLError(err.originalError)) {
return err;
} else if (isDev) {
return createSerializableError(message, err.originalError);
} else {
return createSerializableError(message);
}
return createSerializableGraphQLError(message);
}
return err;
}
return createSerializableError(message);
return createSerializableGraphQLError(message);
};
saihaj marked this conversation as resolved.
Show resolved Hide resolved

export type UseMaskedErrorsOpts = {
/** The function used for format/identify errors. */
formatError?: FormatErrorHandler;
/** The function used for identify and mask errors. */
maskErrorFn?: MaskErrorFn;
saihaj marked this conversation as resolved.
Show resolved Hide resolved
/** The error message that shall be used for masked errors. */
errorMessage?: string;

isDev?: boolean;
};

const makeHandleResult =
(format: FormatErrorHandler, message: string, isDev: boolean) =>
(maskErrorFn: MaskErrorFn, message: string) =>
({ result, setResult }: { result: ExecutionResult; setResult: (result: ExecutionResult) => void }) => {
if (result.errors != null) {
setResult({ ...result, errors: result.errors.map(error => format(error, message, isDev)) });
setResult({ ...result, errors: result.errors.map(error => maskErrorFn(error, message)) });
}
};

export const useMaskedErrors = (opts?: UseMaskedErrorsOpts): Plugin => {
const format = opts?.formatError ?? formatError;
const maskErrorFn = opts?.maskErrorFn ?? defaultMaskErrorFn;
const message = opts?.errorMessage || DEFAULT_ERROR_MESSAGE;
saihaj marked this conversation as resolved.
Show resolved Hide resolved
const isDev = opts?.isDev ?? process.env.NODE_ENV === 'development';
const handleResult = makeHandleResult(format, message, isDev);
const handleResult = makeHandleResult(maskErrorFn, message);

return {
onPluginInit(context) {
context.registerContextErrorHandler(({ error, setError }) => {
setError(format(error, message, isDev));
setError(maskErrorFn(error, message));
});
},
onExecute() {
Expand All @@ -92,7 +79,7 @@ export const useMaskedErrors = (opts?: UseMaskedErrorsOpts): Plugin => {
return handleStreamOrSingleExecutionResult(payload, handleResult);
},
onSubscribeError({ error, setError }) {
setError(format(error, message, isDev));
setError(maskErrorFn(error, message));
},
};
},
Expand Down
30 changes: 15 additions & 15 deletions packages/core/test/plugins/use-masked-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
collectAsyncIteratorValues,
createTestkit,
} from '@envelop/testing';
import { useMaskedErrors, DEFAULT_ERROR_MESSAGE, FormatErrorHandler } from '../../src/plugins/use-masked-errors.js';
import { useMaskedErrors, DEFAULT_ERROR_MESSAGE, MaskErrorFn } from '../../src/plugins/use-masked-errors.js';
import { useExtendContext } from '@envelop/core';
import { useAuth0 } from '../../../plugins/auth0/src/index.js';
import { GraphQLError } from 'graphql';
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('useMaskedErrors', () => {
try {
await testInstance.execute(`query { secretWithExtensions }`);
} catch (err) {
expect(err).toMatchInlineSnapshot(`[Error: My Custom Error Message.]`);
expect(err).toMatchInlineSnapshot(`[GraphQLError: My Custom Error Message.]`);
}
});
it('Should properly mask context creation errors', async () => {
Expand Down Expand Up @@ -211,7 +211,7 @@ describe('useMaskedErrors', () => {
expect(result.errors).toBeDefined();
expect(result.errors).toMatchInlineSnapshot(`
Array [
[Error: My Custom subscription error message.],
[GraphQLError: My Custom subscription error message.],
]
`);
});
Expand Down Expand Up @@ -251,7 +251,7 @@ describe('useMaskedErrors', () => {
try {
await collectAsyncIteratorValues(resultStream);
} catch (err) {
expect(err).toMatchInlineSnapshot(`[Error: My AsyncIterable Custom Error Message.]`);
expect(err).toMatchInlineSnapshot(`[GraphQLError: My AsyncIterable Custom Error Message.]`);
}
});

Expand Down Expand Up @@ -323,13 +323,13 @@ describe('useMaskedErrors', () => {
try {
await testInstance.execute(`query { secret }`, {}, { request: { headers: { authorization: 'Something' } } });
} catch (err) {
expect(err).toMatchInlineSnapshot(`[Error: Unexpected error.]`);
expect(err).toMatchInlineSnapshot(`[GraphQLError: Unexpected error.]`);
}

try {
await testInstance.execute(`query { secret }`, {}, { request: { headers: { authorization: 'Something else' } } });
} catch (err) {
expect(err).toMatchInlineSnapshot(`[Error: Unexpected error.]`);
expect(err).toMatchInlineSnapshot(`[GraphQLError: Unexpected error.]`);
}
});

Expand Down Expand Up @@ -359,12 +359,12 @@ describe('useMaskedErrors', () => {
`);
});

it('should use custom error formatter for execution errors', async () => {
const customErrorFormatter: FormatErrorHandler = e =>
it('should use custom error mask function for execution errors', async () => {
const customErrorMaskFn: MaskErrorFn = e =>
new GraphQLError('Custom error message for ' + e, null, null, null, null, null, {
custom: true,
});
const testInstance = createTestkit([useMaskedErrors({ formatError: customErrorFormatter })], schema);
const testInstance = createTestkit([useMaskedErrors({ maskErrorFn: customErrorMaskFn })], schema);
const result = await testInstance.execute(`query { secret }`);
assertSingleExecutionValue(result);
expect(result).toMatchInlineSnapshot(`
Expand All @@ -381,13 +381,13 @@ describe('useMaskedErrors', () => {
`);
});

it('should use custom error formatter for subscribe (AsyncIterable) subscription errors', async () => {
const customErrorFormatter: FormatErrorHandler = e =>
it('should use custom error mask function for subscribe (AsyncIterable) subscription errors', async () => {
const customErrorMaskFn: MaskErrorFn = e =>
new GraphQLError('Custom error message for ' + e, null, null, null, null, null, {
custom: true,
});
expect.assertions(2);
const testInstance = createTestkit([useMaskedErrors({ formatError: customErrorFormatter })], schema);
const testInstance = createTestkit([useMaskedErrors({ maskErrorFn: customErrorMaskFn })], schema);
const resultStream = await testInstance.execute(`subscription { streamError }`);
assertStreamExecutionValue(resultStream);
try {
Expand All @@ -398,14 +398,14 @@ describe('useMaskedErrors', () => {
}
});

it('should use custom error formatter for errors while building the context', async () => {
const customErrorFormatter: FormatErrorHandler = e =>
it('should use custom error mask function for errors while building the context', async () => {
const customErrorMaskFn: MaskErrorFn = e =>
new GraphQLError('Custom error message for ' + e, null, null, null, null, null, {
custom: true,
});
const testInstance = createTestkit(
[
useMaskedErrors({ formatError: customErrorFormatter }),
useMaskedErrors({ maskErrorFn: customErrorMaskFn }),
useExtendContext(() => {
throw new GraphQLError('Custom error');
return {};
Expand Down