Skip to content

Commit 33f6659

Browse files
committed
Updates updateById and updateOne for new error handling
1 parent da05d7a commit 33f6659

File tree

5 files changed

+120
-25
lines changed

5 files changed

+120
-25
lines changed

src/resolvers/__tests__/updateById-test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,23 @@ describe('updateById() ->', () => {
114114
args: {
115115
record: { _id: user1.id, name: 'some name', valid: 'AlwaysFails' },
116116
},
117+
projection: {
118+
errors: true,
119+
},
117120
});
118-
expect(result.errors).toEqual([{ message: 'this is a validate message', path: 'valid' }]);
121+
expect(result.errors).toEqual([
122+
{ message: 'this is a validate message', path: 'valid', value: 'AlwaysFails' },
123+
]);
124+
});
125+
126+
it('should throw GraphQLError if client does not request errors field in payload', async () => {
127+
await expect(
128+
updateById(UserModel, UserTC).resolve({
129+
args: {
130+
record: { _id: user1.id, name: 'some name', valid: 'AlwaysFails' },
131+
},
132+
})
133+
).rejects.toThrowError('User validation failed: valid: this is a validate message');
119134
});
120135

121136
it('should change data via args.record in model', async () => {

src/resolvers/__tests__/updateOne-test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,24 @@ describe('updateOne() ->', () => {
147147
filter: { _id: user1.id },
148148
record: { valid: 'AlwaysFails' },
149149
},
150+
projection: {
151+
errors: true,
152+
},
150153
});
154+
expect(result.errors).toEqual([
155+
{ message: 'this is a validate message', path: 'valid', value: 'AlwaysFails' },
156+
]);
157+
});
151158

152-
expect(result.errors).toEqual([{ message: 'this is a validate message', path: 'valid' }]);
159+
it('should throw GraphQLError if client does not request errors field in payload', async () => {
160+
await expect(
161+
updateOne(UserModel, UserTC).resolve({
162+
args: {
163+
filter: { _id: user1.id },
164+
record: { valid: 'AlwaysFails' },
165+
},
166+
})
167+
).rejects.toThrowError('User validation failed: valid: this is a validate message');
153168
});
154169

155170
it('should skip records', async () => {

src/resolvers/createOne.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export default function createOne<TSource = Document, TContext = any>(
8181
if (!doc) return null;
8282
}
8383

84-
const validationErrors = doc.validateSync();
84+
const validationErrors: any = await new Promise(function (resolve) {
85+
doc.validate(null, null, resolve);
86+
});
8587
const errors: {
8688
path: string;
8789
message: string;

src/resolvers/updateById.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { Resolver, ObjectTypeComposer } from 'graphql-compose';
1+
import { Resolver, ObjectTypeComposer, mapEachKey } from 'graphql-compose';
22
import type { Model, Document } from 'mongoose';
33
import { getOrCreateErrorInterface } from '../utils/getOrCreateErrorInterface';
44
import { recordHelperArgs } from './helpers/record';
55
import findById from './findById';
6+
import { GraphQLError } from 'graphql';
67

78
import type { ExtendedResolveParams, GenResolverOpts } from './index';
89

@@ -81,6 +82,8 @@ export default function updateById<TSource = Document, TContext = any>(
8182
resolveParams.args._id = recordData._id;
8283
delete recordData._id;
8384

85+
// this feels weird
86+
const requestsErrors: any = resolveParams?.projection?.errors;
8487
// We should get all data for document, cause Mongoose model may have hooks/middlewares
8588
// which required some fields which not in graphql projection
8689
// So empty projection returns all fields.
@@ -99,34 +102,60 @@ export default function updateById<TSource = Document, TContext = any>(
99102
if (recordData) {
100103
doc.set(recordData);
101104

102-
const validationErrors = doc.validateSync();
103-
let errors: {
105+
const validationErrors: any = await new Promise(function (resolve) {
106+
doc.validate(null, null, resolve);
107+
});
108+
const errors: {
104109
path: string;
105110
message: string;
106-
}[];
111+
value: any;
112+
}[] = [];
113+
107114
if (validationErrors && validationErrors.errors) {
108-
errors = [];
115+
if (!requestsErrors) {
116+
// if client does not request `errors` field we throw Exception on to level
117+
throw new GraphQLError(
118+
validationErrors.message,
119+
undefined,
120+
undefined,
121+
undefined,
122+
undefined,
123+
undefined,
124+
{
125+
validationErrors: mapEachKey(validationErrors.errors, (e: any) => {
126+
return {
127+
path: e.path,
128+
message: e.message,
129+
value: e.value,
130+
};
131+
}),
132+
}
133+
);
134+
}
109135
Object.keys(validationErrors.errors).forEach((key) => {
136+
const { message, value } = validationErrors.errors[key];
110137
errors.push({
111138
path: key,
112-
message: validationErrors.errors[key].properties.message,
139+
message,
140+
value,
113141
});
114142
});
115143
return {
116144
record: null,
117145
recordId: null,
118146
errors,
119147
};
148+
} else {
149+
await doc.save();
150+
return {
151+
record: doc,
152+
recordId: tc.getRecordIdFn()(doc),
153+
errors: null,
154+
};
120155
}
121-
122-
await doc.save();
123156
}
124157

125-
return {
126-
record: doc,
127-
recordId: tc.getRecordIdFn()(doc),
128-
errors: null,
129-
};
158+
return null;
130159
}) as any,
131160
});
132161

src/resolvers/updateOne.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type { Resolver, ObjectTypeComposer } from 'graphql-compose';
1+
import { Resolver, ObjectTypeComposer, mapEachKey } from 'graphql-compose';
22
import type { Model, Document } from 'mongoose';
33
import type { ExtendedResolveParams, GenResolverOpts } from './index';
44
import { getOrCreateErrorInterface } from '../utils/getOrCreateErrorInterface';
55
import { skipHelperArgs, recordHelperArgs, filterHelperArgs, sortHelperArgs } from './helpers';
66
import findOne from './findOne';
7+
import { GraphQLError } from 'graphql';
78

89
export default function updateOne<TSource = Document, TContext = any>(
910
model: Model<any>,
@@ -82,6 +83,9 @@ export default function updateOne<TSource = Document, TContext = any>(
8283
)
8384
);
8485
}
86+
87+
// this feels weird
88+
const requestsErrors: any = resolveParams?.projection?.errors;
8589
// We should get all data for document, cause Mongoose model may have hooks/middlewares
8690
// which required some fields which not in graphql projection
8791
// So empty projection returns all fields.
@@ -93,30 +97,60 @@ export default function updateOne<TSource = Document, TContext = any>(
9397
doc = await resolveParams.beforeRecordMutate(doc, resolveParams);
9498
}
9599

96-
if (doc && recordData) {
100+
if (recordData) {
97101
doc.set(recordData);
98102

99-
const validationErrors = doc.validateSync();
100-
let errors: {
103+
const validationErrors: any = await new Promise(function (resolve) {
104+
doc.validate(null, null, resolve);
105+
});
106+
const errors: {
101107
path: string;
102108
message: string;
103-
}[];
109+
value: any;
110+
}[] = [];
111+
104112
if (validationErrors && validationErrors.errors) {
105-
errors = [];
113+
if (!requestsErrors) {
114+
// if client does not request `errors` field we throw Exception on to level
115+
throw new GraphQLError(
116+
validationErrors.message,
117+
undefined,
118+
undefined,
119+
undefined,
120+
undefined,
121+
undefined,
122+
{
123+
validationErrors: mapEachKey(validationErrors.errors, (e: any) => {
124+
return {
125+
path: e.path,
126+
message: e.message,
127+
value: e.value,
128+
};
129+
}),
130+
}
131+
);
132+
}
106133
Object.keys(validationErrors.errors).forEach((key) => {
134+
const { message, value } = validationErrors.errors[key];
107135
errors.push({
108136
path: key,
109-
message: validationErrors.errors[key].properties.message,
137+
message,
138+
value,
110139
});
111140
});
112141
return {
113142
record: null,
114143
recordId: null,
115144
errors,
116145
};
146+
} else {
147+
await doc.save();
148+
return {
149+
record: doc,
150+
recordId: tc.getRecordIdFn()(doc),
151+
errors: null,
152+
};
117153
}
118-
119-
await doc.save();
120154
}
121155

122156
if (doc) {

0 commit comments

Comments
 (0)