1
- import { unwrapResolverError } from '@apollo/server/errors' ;
2
1
import { Injectable } from '@nestjs/common' ;
3
2
import { ModuleRef } from '@nestjs/core' ;
4
- import {
5
- GraphQLErrorExtensions as ErrorExtensions ,
6
- GraphQLFormattedError as FormattedError ,
7
- GraphQLError ,
8
- } from 'graphql' ;
3
+ import { GraphQLError } from 'graphql' ;
4
+ import { handleStreamOrSingleExecutionResult } from 'graphql-yoga' ;
9
5
import { LazyGetter } from 'lazy-get-decorator' ;
10
- import { JsonSet } from '~/common' ;
11
6
import { ExceptionFilter } from '../exception/exception.filter' ;
12
- import { ExceptionNormalizer } from '../exception/exception.normalizer' ;
7
+ import {
8
+ ExceptionNormalizer ,
9
+ NormalizedException ,
10
+ } from '../exception/exception.normalizer' ;
11
+ import { Plugin } from './plugin.decorator' ;
13
12
14
13
declare module 'graphql' {
15
14
interface GraphQLErrorExtensions {
@@ -19,6 +18,7 @@ declare module 'graphql' {
19
18
}
20
19
}
21
20
21
+ @Plugin ( )
22
22
@Injectable ( )
23
23
export class GraphqlErrorFormatter {
24
24
constructor ( private readonly moduleRef : ModuleRef ) { }
@@ -30,60 +30,61 @@ export class GraphqlErrorFormatter {
30
30
return this . moduleRef . get ( ExceptionFilter , { strict : false } ) ;
31
31
}
32
32
33
- formatError = (
34
- formatted : FormattedError ,
35
- error : unknown | /* but probably a */ GraphQLError ,
36
- ) : FormattedError => {
37
- const { message , ... extensions } = this . getErrorExtensions (
38
- formatted ,
39
- error ,
40
- ) ;
33
+ onValidate : Plugin [ 'onValidate' ] =
34
+ ( ) =>
35
+ ( { result , setResult } ) => {
36
+ if ( result . length > 0 ) {
37
+ const errors = result . map ( ( error ) => this . formatError ( error ) ) ;
38
+ setResult ( errors ) ;
39
+ }
40
+ } ;
41
41
42
- const codes = ( extensions . codes ??= new JsonSet ( [ 'Server' ] ) ) ;
43
- delete extensions . code ;
42
+ onExecute : Plugin [ 'onExecute' ] = ( ) => ( {
43
+ onExecuteDone : ( params ) =>
44
+ handleStreamOrSingleExecutionResult ( params , ( { result, setResult } ) => {
45
+ if ( result . errors && result . errors . length > 0 ) {
46
+ const errors = result . errors . map ( ( error ) => this . formatError ( error ) ) ;
47
+ setResult ( { ...result , errors } ) ;
48
+ }
49
+ } ) ,
50
+ } ) ;
44
51
45
- // Schema & validation errors don't have meaningful stack traces, so remove them
46
- const worthlessTrace = codes . has ( 'Validation' ) || codes . has ( 'GraphQL' ) ;
47
- if ( worthlessTrace ) {
48
- delete extensions . stacktrace ;
52
+ formatError = ( error : unknown ) => {
53
+ if ( ! ( error instanceof GraphQLError ) ) {
54
+ // I don't think this happens.
55
+ return new GraphQLError (
56
+ error instanceof Error ? error . message : String ( error ) ,
57
+ ) ;
49
58
}
50
59
51
- return {
52
- message :
53
- message && typeof message === 'string' ? message : formatted . message ,
54
- extensions,
55
- locations : formatted . locations ,
56
- path : formatted . path ,
57
- } ;
58
- } ;
60
+ const normalized =
61
+ error . originalError instanceof NormalizedException
62
+ ? error . originalError . normalized
63
+ : this . normalizer . normalize ( {
64
+ ex : error . originalError ?? error ,
65
+ gql : error ,
66
+ } ) ;
59
67
60
- private getErrorExtensions (
61
- formatted : FormattedError ,
62
- error : unknown | /* but probably a */ GraphQLError ,
63
- ) : ErrorExtensions {
64
- // ExceptionNormalizer has already been called
65
- if ( formatted . extensions ?. codes instanceof Set ) {
66
- return { ...formatted . extensions } ;
68
+ // If this is an error has not gone through the ExceptionFilter,
69
+ // the logging was skipped - log error now.
70
+ if ( ! ( error . originalError instanceof NormalizedException ) ) {
71
+ this . filter . logIt ( normalized , error . originalError ?? error ) ;
67
72
}
68
73
69
- const original = unwrapResolverError ( error ) ;
70
- // Safety check
71
- if ( ! ( original instanceof Error ) ) {
72
- return { ...formatted . extensions } ;
74
+ const { message, stack, code : _ , ...extensions } = normalized ;
75
+ const { codes } = extensions ;
76
+
77
+ // Schema & validation errors don't have meaningful stack traces, so remove them
78
+ const worthlessTrace = codes . has ( 'Validation' ) || codes . has ( 'GraphQL' ) ;
79
+ if ( ! worthlessTrace ) {
80
+ extensions . stacktrace = stack . split ( '\n' ) ;
73
81
}
74
82
75
- // Some errors do not go through the global exception filter.
76
- // ResolveField() calls is one of them.
77
- // Normalized & log here.
78
- const normalized = this . normalizer . normalize ( {
79
- ex : original ,
80
- gql : error instanceof GraphQLError ? error : undefined ,
83
+ return new GraphQLError ( message , {
84
+ nodes : error . nodes ,
85
+ positions : error . positions ,
86
+ path : error . path ,
87
+ extensions : { ...error . extensions , ...extensions } ,
81
88
} ) ;
82
- this . filter . logIt ( normalized , original ) ;
83
- const { stack, ...extensions } = normalized ;
84
- return {
85
- ...extensions ,
86
- stacktrace : stack . split ( '\n' ) ,
87
- } ;
88
- }
89
+ } ;
89
90
}
0 commit comments