mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
refactor(core): standardize frontend error handling (#10667)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { GraphQLError, UserFriendlyError } from '..';
|
||||
|
||||
describe('user friendly error', () => {
|
||||
it('should create from graphql error', () => {
|
||||
const gqlError = new GraphQLError('test', {
|
||||
extensions: {
|
||||
status: 400,
|
||||
code: 'BAD_REQUEST',
|
||||
type: 'BAD_REQUEST',
|
||||
name: 'SOME_ERROR_NAME',
|
||||
message: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
const error = UserFriendlyError.fromAny(gqlError);
|
||||
|
||||
expect(error.name).toBe('SOME_ERROR_NAME');
|
||||
expect(error.status).toBe(400);
|
||||
expect(error.code).toBe('BAD_REQUEST');
|
||||
expect(error.type).toBe('BAD_REQUEST');
|
||||
expect(error.message).toBe('test');
|
||||
});
|
||||
|
||||
it('should create from any error', () => {
|
||||
const error = UserFriendlyError.fromAny(new Error('test'));
|
||||
expect(error.message).toBe('test');
|
||||
});
|
||||
|
||||
it('should create from object', () => {
|
||||
const error = UserFriendlyError.fromAny({
|
||||
name: 'SOME_ERROR_NAME',
|
||||
status: 400,
|
||||
code: 'BAD_REQUEST',
|
||||
type: 'BAD_REQUEST',
|
||||
message: 'test',
|
||||
});
|
||||
|
||||
expect(error.message).toBe('test');
|
||||
});
|
||||
|
||||
it('should create from string', () => {
|
||||
const error = UserFriendlyError.fromAny('test error');
|
||||
expect(error.message).toBe('test error');
|
||||
});
|
||||
|
||||
it('should create fallback error', () => {
|
||||
const error = UserFriendlyError.fromAny(null);
|
||||
|
||||
expect(error.message).toBe(
|
||||
'Unhandled error raised. Please contact us for help.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should test network error', () => {
|
||||
const error = UserFriendlyError.fromAny({
|
||||
name: 'NETWORK_ERROR',
|
||||
status: 500,
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
type: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'test',
|
||||
});
|
||||
|
||||
expect(error.isNetworkError()).toBe(true);
|
||||
|
||||
const error2 = UserFriendlyError.fromAny({
|
||||
name: 'SOME_ERROR_NAME',
|
||||
status: 400,
|
||||
code: 'BAD_REQUEST',
|
||||
type: 'BAD_REQUEST',
|
||||
message: 'test',
|
||||
});
|
||||
|
||||
expect(error2.isNetworkError()).toBe(false);
|
||||
});
|
||||
|
||||
it('should test name', () => {
|
||||
const error = UserFriendlyError.fromAny({
|
||||
name: 'SOME_ERROR_NAME',
|
||||
status: 400,
|
||||
code: 'BAD_REQUEST',
|
||||
type: 'BAD_REQUEST',
|
||||
message: 'test',
|
||||
});
|
||||
|
||||
// @ts-expect-error test name
|
||||
expect(error.is('SOME_ERROR_NAME')).toBe(true);
|
||||
});
|
||||
|
||||
it('should test status', () => {
|
||||
const error = UserFriendlyError.fromAny({
|
||||
name: 'SOME_ERROR_NAME',
|
||||
status: 400,
|
||||
code: 'BAD_REQUEST',
|
||||
type: 'BAD_REQUEST',
|
||||
message: 'test',
|
||||
});
|
||||
|
||||
expect(error.isStatus(400)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { ErrorDataUnion, ErrorNames } from '@affine/graphql';
|
||||
import { GraphQLError as BaseGraphQLError } from 'graphql';
|
||||
|
||||
export type ErrorName = keyof typeof ErrorNames | 'NETWORK_ERROR';
|
||||
|
||||
export interface UserFriendlyErrorResponse {
|
||||
status: number;
|
||||
code: string;
|
||||
type: string;
|
||||
name: ErrorName;
|
||||
message: string;
|
||||
data?: any;
|
||||
stacktrace?: string;
|
||||
}
|
||||
|
||||
function UnknownError(message: string) {
|
||||
return new UserFriendlyError({
|
||||
status: 500,
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
type: 'INTERNAL_SERVER_ERROR',
|
||||
name: 'INTERNAL_SERVER_ERROR',
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
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` }
|
||||
>;
|
||||
};
|
||||
|
||||
export class GraphQLError extends BaseGraphQLError {
|
||||
// @ts-expect-error better to be a known type without any type casting
|
||||
override extensions!: UserFriendlyErrorResponse;
|
||||
}
|
||||
|
||||
export class UserFriendlyError
|
||||
extends Error
|
||||
implements UserFriendlyErrorResponse
|
||||
{
|
||||
readonly status = this.response.status;
|
||||
readonly code = this.response.code;
|
||||
readonly type = this.response.type;
|
||||
override readonly name = this.response.name;
|
||||
override readonly message = this.response.message;
|
||||
readonly data = this.response.data;
|
||||
readonly stacktrace = this.response.stacktrace;
|
||||
|
||||
static fromAny(anything: any) {
|
||||
if (anything instanceof UserFriendlyError) {
|
||||
return anything;
|
||||
}
|
||||
|
||||
switch (typeof anything) {
|
||||
case 'string':
|
||||
return UnknownError(anything);
|
||||
case 'object': {
|
||||
if (anything) {
|
||||
if (anything instanceof GraphQLError) {
|
||||
return new UserFriendlyError(anything.extensions);
|
||||
} else if (anything.type && anything.name && anything.message) {
|
||||
return new UserFriendlyError(anything);
|
||||
} else if (anything.message) {
|
||||
return UnknownError(anything.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UnknownError('Unhandled error raised. Please contact us for help.');
|
||||
}
|
||||
|
||||
constructor(private readonly response: UserFriendlyErrorResponse) {
|
||||
super(response.message);
|
||||
}
|
||||
|
||||
is(name: ErrorName) {
|
||||
return this.name === name;
|
||||
}
|
||||
|
||||
isStatus(status: number) {
|
||||
return this.status === status;
|
||||
}
|
||||
|
||||
static isNetworkError(error: UserFriendlyError) {
|
||||
return error.name === 'NETWORK_ERROR';
|
||||
}
|
||||
|
||||
static notNetworkError(error: UserFriendlyError) {
|
||||
return !UserFriendlyError.isNetworkError(error);
|
||||
}
|
||||
|
||||
isNetworkError() {
|
||||
return UserFriendlyError.isNetworkError(this);
|
||||
}
|
||||
|
||||
notNetworkError() {
|
||||
return UserFriendlyError.notNetworkError(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user