refactor(server): server errors (#5741)

standardize the error raising in both GraphQL Resolvers and Controllers.

Now, All user aware errors should be throwed with `HttpException`'s variants, for example `NotFoundException`.

> Directly throwing `GraphQLError` are forbidden.
The GraphQL errorFormatter will handle it automatically and set `code`, `status` in error extensions.

At the same time, the frontend `GraphQLError` should be imported from `@affine/graphql`, which introduce a better error extensions type.

----
controller example:
```js
@Get('/docs/${id}')
doc() {
  // ...
  // imported from '@nestjs/common'
  throw new NotFoundException('Doc is not found.');
  // ...
}
```
the above will response as:
```
status: 404 Not Found
{
  "message": "Doc is not found.",
  "statusCode": 404,
  "error": "Not Found"
}
```

resolver example:
```js
@Mutation()
invite() {
  // ...
  throw new PayloadTooLargeException('Workspace seats is full.')
  // ...
}
```

the above will response as:
```
status: 200 Ok
{
  "data": null,
  "errors": [
    {
      "message": "Workspace seats is full.",
      "extensions": {
        "code": 404,
        "status": "Not Found"
      }
    }
  ]
}
```

for frontend GraphQLError user-friend, a helper function introduced:

```js
import { findGraphQLError } from '@affine/graphql'

fetch(query)
  .catch(errOrArr => {
    const e = findGraphQLError(errOrArr, e => e.extensions.code === 404)
    if (e) {
      // handle
    }
})
```
This commit is contained in:
liuyi
2024-01-31 08:43:03 +00:00
parent 72d9cc1e5b
commit 26db1d436d
22 changed files with 310 additions and 193 deletions

View File

@@ -0,0 +1,26 @@
import { GraphQLError as BaseGraphQLError } from 'graphql';
import { identity } from 'lodash-es';
interface KnownGraphQLErrorExtensions {
code: number;
status: string;
originalError?: unknown;
stacktrace?: string;
}
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;
}
}

View File

@@ -1,3 +1,4 @@
export * from './error';
export * from './fetcher';
export * from './graphql';
export * from './schema';
@@ -18,5 +19,3 @@ export function getBaseUrl(): string {
}
export const fetcher = gqlFetcherFactory(getBaseUrl() + '/graphql');
export { GraphQLError } from 'graphql';