feat(server): introduce user friendly server errors (#7111)

This commit is contained in:
liuyi
2024-06-17 11:30:58 +08:00
committed by GitHub
parent 5307a55f8a
commit 54fc1197ad
65 changed files with 3170 additions and 924 deletions

View File

@@ -102,11 +102,8 @@ describe('GraphQL fetcher', () => {
)
);
await expect(gql({ query, variables: void 0 })).rejects
.toMatchInlineSnapshot(`
[
[GraphQLError: error],
]
`);
await expect(
gql({ query, variables: void 0 })
).rejects.toMatchInlineSnapshot(`[GraphQLError: error]`);
});
});

View File

@@ -1,26 +1,59 @@
import { GraphQLError as BaseGraphQLError } from 'graphql';
import { identity } from 'lodash-es';
interface KnownGraphQLErrorExtensions {
code: number;
status: string;
originalError?: unknown;
import { type ErrorDataUnion, ErrorNames } from './schema';
export interface UserFriendlyErrorResponse {
status: number;
code: string;
type: string;
name: ErrorNames;
message: string;
args?: any;
stacktrace?: string;
}
export class UserFriendlyError implements UserFriendlyErrorResponse {
status = this.response.status;
code = this.response.code;
type = this.response.type;
name = this.response.name;
message = this.response.message;
args = this.response.args;
stacktrace = this.response.stacktrace;
static fromAnyError(response: any) {
if (response instanceof GraphQLError) {
return new UserFriendlyError(response.extensions);
}
if (typeof response === 'object' && response.type && response.name) {
return new UserFriendlyError(response);
}
return new UserFriendlyError({
status: 500,
code: 'INTERNAL_SERVER_ERROR',
type: 'INTERNAL_SERVER_ERROR',
name: ErrorNames.INTERNAL_SERVER_ERROR,
message: 'Internal server error',
});
}
constructor(private readonly response: UserFriendlyErrorResponse) {}
}
export class GraphQLError extends BaseGraphQLError {
// @ts-expect-error better to be a known type without any type casting
override extensions!: KnownGraphQLErrorExtensions;
}
export function findGraphQLError(
errOrArr: any,
filter: (err: GraphQLError) => boolean = identity
): GraphQLError | undefined {
if (errOrArr instanceof GraphQLError) {
return filter(errOrArr) ? errOrArr : undefined;
} else if (Array.isArray(errOrArr)) {
return errOrArr.find(err => err instanceof GraphQLError && filter(err));
} else {
return undefined;
}
override extensions!: UserFriendlyErrorResponse;
}
type ToPascalCase<S extends string> = S extends `${infer A}_${infer B}`
? `${Capitalize<Lowercase<A>>}${ToPascalCase<B>}`
: Capitalize<Lowercase<S>>;
export type ErrorData = {
[K in ErrorNames]: Extract<
ErrorDataUnion,
{ __typename?: `${ToPascalCase<K>}DataType` }
>;
};

View File

@@ -195,9 +195,9 @@ export const gqlFetcherFactory = (
const result = (await res.json()) as ExecutionResult;
if (res.status >= 400 || result.errors) {
if (result.errors && result.errors.length > 0) {
throw result.errors.map(
error => new GraphQLError(error.message, error)
);
// throw the first error is enough
const firstError = result.errors[0];
throw new GraphQLError(firstError.message, firstError);
} else {
throw new GraphQLError('Empty GraphQL error body');
}

File diff suppressed because it is too large Load Diff