@@ -6,7 +6,13 @@ import {
6
6
GqlContextType as ContextKey ,
7
7
GqlExecutionContext ,
8
8
} from '@nestjs/graphql' ;
9
- import { isNotFalsy , setHas , setOf , simpleSwitch } from '@seedcompany/common' ;
9
+ import {
10
+ entries ,
11
+ isNotFalsy ,
12
+ setHas ,
13
+ setOf ,
14
+ simpleSwitch ,
15
+ } from '@seedcompany/common' ;
10
16
import * as Edge from 'edgedb' ;
11
17
import * as EdgeDBTags from 'edgedb/dist/errors/tags.js' ;
12
18
import { GraphQLError , GraphQLResolveInfo } from 'graphql' ;
@@ -17,11 +23,14 @@ import {
17
23
Exception ,
18
24
getParentTypes ,
19
25
getPreviousList ,
26
+ InputException ,
20
27
JsonSet ,
28
+ NotFoundException ,
21
29
} from '~/common' ;
22
30
import type { ConfigService } from '~/core' ;
23
31
import * as Neo from '../database/errors' ;
24
32
import { ExclusivityViolationError } from '../edgedb/exclusivity-violation.error' ;
33
+ import { ResourcesHost } from '../resources/resources.host' ;
25
34
import { isSrcFrame } from './is-src-frame' ;
26
35
import { normalizeFramePath } from './normalize-frame-path' ;
27
36
@@ -35,7 +44,10 @@ export interface ExceptionJson {
35
44
36
45
@Injectable ( )
37
46
export class ExceptionNormalizer {
38
- constructor ( @Inject ( 'CONFIG' ) private readonly config ?: ConfigService ) { }
47
+ constructor (
48
+ @Inject ( 'CONFIG' ) private readonly config ?: ConfigService ,
49
+ private readonly resources ?: ResourcesHost ,
50
+ ) { }
39
51
40
52
normalize ( ex : Error , context ?: ArgumentsHost ) : ExceptionJson {
41
53
const {
@@ -114,13 +126,22 @@ export class ExceptionNormalizer {
114
126
} ;
115
127
}
116
128
129
+ const gqlContext =
130
+ context &&
131
+ context . getType < ContextKey > ( ) === 'graphql' &&
132
+ // schema input validation errors don't create an execution context correctly
133
+ ! ( ex instanceof GraphQLError )
134
+ ? GqlExecutionContext . create ( context as any )
135
+ : undefined ;
136
+
137
+ ex = this . wrapIDNotFoundError ( ex , gqlContext ) ;
138
+
117
139
if ( ex instanceof ExclusivityViolationError ) {
118
- ex = DuplicateException . fromDB ( ex , context ) ;
140
+ ex = DuplicateException . fromDB ( ex , gqlContext ) ;
119
141
} else if ( ex instanceof Edge . EdgeDBError ) {
120
142
// Mask actual DB error with a nicer user error message.
121
143
let message = 'Failed' ;
122
- if ( context && context . getType < ContextKey > ( ) === 'graphql' ) {
123
- const gqlContext = GqlExecutionContext . create ( context as any ) ;
144
+ if ( gqlContext ) {
124
145
const info = gqlContext . getInfo < GraphQLResolveInfo > ( ) ;
125
146
if ( info . operation . operation === 'mutation' ) {
126
147
message += ` to ${ lowerCase ( info . fieldName ) } ` ;
@@ -161,6 +182,43 @@ export class ExceptionNormalizer {
161
182
return { codes : [ 'Server' ] } ;
162
183
}
163
184
185
+ /**
186
+ * Convert ID not found database errors from user input
187
+ * to user input NotFound error with that input path.
188
+ */
189
+ private wrapIDNotFoundError (
190
+ ex : Error ,
191
+ gqlContext : GqlExecutionContext | undefined ,
192
+ ) {
193
+ if ( ! ( ex instanceof Edge . CardinalityViolationError ) ) {
194
+ return ex ;
195
+ }
196
+
197
+ const matched = ex . message . match ( / ' ( .+ ) ' w i t h i d ' ( .+ ) ' d o e s n o t e x i s t / ) ;
198
+ if ( ! matched ) {
199
+ return ex ;
200
+ }
201
+ const [ _ , type , id ] = matched ;
202
+
203
+ const inputPath = entries ( InputException . getFlattenInput ( gqlContext ) ) . find (
204
+ ( [ _ , value ] ) => value === id ,
205
+ ) ?. [ 0 ] ;
206
+ if ( ! inputPath ) {
207
+ return ex ;
208
+ }
209
+
210
+ const typeName = this . resources
211
+ ? this . resources . getByEdgeDB ( type ) . name
212
+ : type ;
213
+ const wrapped = new NotFoundException (
214
+ `${ typeName } could not be found` ,
215
+ inputPath ,
216
+ ex ,
217
+ ) ;
218
+ wrapped . stack = ex . stack ;
219
+ return wrapped ;
220
+ }
221
+
164
222
private httpException ( ex : Nest . HttpException ) {
165
223
const res = ex . getResponse ( ) ;
166
224
const {
0 commit comments