Skip to content

Commit 4ddf251

Browse files
committed
Automatically handle not found ID errors from user input
1 parent d83b135 commit 4ddf251

File tree

1 file changed

+53
-2
lines changed

1 file changed

+53
-2
lines changed

src/core/exception/exception.normalizer.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import {
66
GqlContextType as ContextKey,
77
GqlExecutionContext,
88
} 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';
1016
import * as Edge from 'edgedb';
1117
import * as EdgeDBTags from 'edgedb/dist/errors/tags.js';
1218
import { GraphQLError, GraphQLResolveInfo } from 'graphql';
@@ -17,11 +23,14 @@ import {
1723
Exception,
1824
getParentTypes,
1925
getPreviousList,
26+
InputException,
2027
JsonSet,
28+
NotFoundException,
2129
} from '~/common';
2230
import type { ConfigService } from '~/core';
2331
import * as Neo from '../database/errors';
2432
import { ExclusivityViolationError } from '../edgedb/exclusivity-violation.error';
33+
import { ResourcesHost } from '../resources/resources.host';
2534
import { isSrcFrame } from './is-src-frame';
2635
import { normalizeFramePath } from './normalize-frame-path';
2736

@@ -35,7 +44,10 @@ export interface ExceptionJson {
3544

3645
@Injectable()
3746
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+
) {}
3951

4052
normalize(ex: Error, context?: ArgumentsHost): ExceptionJson {
4153
const {
@@ -119,6 +131,8 @@ export class ExceptionNormalizer {
119131
? GqlExecutionContext.create(context as any)
120132
: undefined;
121133

134+
ex = this.wrapIDNotFoundError(ex, gqlContext);
135+
122136
if (ex instanceof ExclusivityViolationError) {
123137
ex = DuplicateException.fromDB(ex, gqlContext);
124138
} else if (ex instanceof Edge.EdgeDBError) {
@@ -165,6 +179,43 @@ export class ExceptionNormalizer {
165179
return { codes: ['Server'] };
166180
}
167181

182+
/**
183+
* Convert ID not found database errors from user input
184+
* to user input NotFound error with that input path.
185+
*/
186+
private wrapIDNotFoundError(
187+
ex: Error,
188+
gqlContext: GqlExecutionContext | undefined,
189+
) {
190+
if (!(ex instanceof Edge.CardinalityViolationError)) {
191+
return ex;
192+
}
193+
194+
const matched = ex.message.match(/'(.+)' with id '(.+)' does not exist/);
195+
if (!matched) {
196+
return ex;
197+
}
198+
const [_, type, id] = matched;
199+
200+
const inputPath = entries(InputException.getFlattenInput(gqlContext)).find(
201+
([_, value]) => value === id,
202+
)?.[0];
203+
if (!inputPath) {
204+
return ex;
205+
}
206+
207+
const typeName = this.resources
208+
? this.resources.getByEdgeDB(type).name
209+
: type;
210+
const wrapped = new NotFoundException(
211+
`${typeName} could not be found`,
212+
inputPath,
213+
ex,
214+
);
215+
wrapped.stack = ex.stack;
216+
return wrapped;
217+
}
218+
168219
private httpException(ex: Nest.HttpException) {
169220
const res = ex.getResponse();
170221
const {

0 commit comments

Comments
 (0)