Skip to content

Commit 58edba1

Browse files
RobertLowenodkz
authored andcommitted
Adds ManyValidationError and other changes
- Refactors some code - Changes *ValidationError to be of GraphQLError - Adds validationsForDocument for extracting validations
1 parent 5753c87 commit 58edba1

File tree

10 files changed

+319
-120
lines changed

10 files changed

+319
-120
lines changed

src/errors/ManyValidationError.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { getValidationErrorOTC } from './ValidationError';
2+
import { SchemaComposer, ObjectTypeComposer } from 'graphql-compose';
3+
import { GraphQLError } from 'graphql';
4+
5+
import { ValidationsWithMessage, ManyValidationsWithMessage } from './validationsForDocument';
6+
7+
export class ManyValidationError extends GraphQLError {
8+
public errors: Array<ValidationsWithMessage | null>;
9+
10+
constructor(data: ManyValidationsWithMessage) {
11+
super(data.message, undefined, undefined, undefined, undefined, undefined, {
12+
validations: data.errors,
13+
});
14+
15+
this.errors = data.errors;
16+
(this as any).__proto__ = ManyValidationError.prototype;
17+
}
18+
}
19+
20+
export function getManyValidationErrorOTC(schemaComposer: SchemaComposer<any>): ObjectTypeComposer {
21+
return schemaComposer.getOrCreateOTC('ManyValidationError', (otc) => {
22+
const ValidationError: ObjectTypeComposer = getValidationErrorOTC(schemaComposer);
23+
otc.addFields({
24+
message: {
25+
description: 'Combined error message from all validators',
26+
type: 'String',
27+
},
28+
errors: {
29+
description: 'List of validator errors',
30+
type: ValidationError.List,
31+
},
32+
});
33+
});
34+
}

src/errors/ValidationError.ts

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,52 @@
1-
import type { Error as MongooseError } from 'mongoose';
21
import { SchemaComposer, ObjectTypeComposer } from 'graphql-compose';
2+
import { GraphQLError } from 'graphql';
33

4-
interface Opts {
5-
/** Adds prefix to error `path` property. It's useful for createMany resolver which shows `idx` of broken record */
6-
pathPrefix?: string;
7-
}
4+
import type { Validations, ValidationsWithMessage } from './validationsForDocument';
85

9-
export class ValidationError extends Error {
10-
public errors: Array<{
11-
path: string;
12-
message: string;
13-
value: any;
14-
}>;
6+
export class ValidationError extends GraphQLError {
7+
public errors: Validations;
158

16-
constructor(data: MongooseError.ValidationError, opts?: Opts) {
17-
super(data.message);
18-
this.errors = [];
19-
Object.keys(data.errors).forEach((key) => {
20-
const e = data.errors[key];
21-
this.errors.push({
22-
path: opts?.pathPrefix ? `${opts?.pathPrefix}${e.path}` : e.path,
23-
message: e.message,
24-
value: e.value,
25-
});
9+
constructor(validation: ValidationsWithMessage) {
10+
super(validation.message, undefined, undefined, undefined, undefined, undefined, {
11+
validations: validation.errors,
2612
});
2713

14+
this.errors = validation.errors;
15+
2816
(this as any).__proto__ = ValidationError.prototype;
2917
}
3018
}
3119

32-
export function getValidationErrorOTC(schemaComposer: SchemaComposer<any>): ObjectTypeComposer {
33-
return schemaComposer.getOrCreateOTC('ValidationError', (otc) => {
34-
const ValidatorError = schemaComposer.getOrCreateOTC('ValidatorError', (otc) => {
35-
otc.addFields({
36-
message: {
37-
description: 'Validation error message',
38-
type: 'String',
39-
},
40-
path: {
41-
description: 'Source of the validation error from the model path',
42-
type: 'String',
43-
},
44-
value: {
45-
description: 'Field value which occurs the validation error',
46-
type: 'JSON',
47-
},
48-
});
20+
export function getValidationOTC(schemaComposer: SchemaComposer<any>): ObjectTypeComposer {
21+
return schemaComposer.getOrCreateOTC('Validation', (otc) => {
22+
otc.addFields({
23+
message: {
24+
description: 'Validation error message',
25+
type: 'String',
26+
},
27+
path: {
28+
description: 'Source of the validation error from the model path',
29+
type: 'String',
30+
},
31+
value: {
32+
description: 'Field value which occurs the validation error',
33+
type: 'JSON',
34+
},
4935
});
36+
});
37+
}
5038

39+
export function getValidationErrorOTC(schemaComposer: SchemaComposer<any>): ObjectTypeComposer {
40+
return schemaComposer.getOrCreateOTC('ValidationError', (otc) => {
41+
const Validation = getValidationOTC(schemaComposer);
5142
otc.addFields({
5243
message: {
5344
description: 'Combined error message from all validators',
5445
type: 'String',
5546
},
5647
errors: {
5748
description: 'List of validator errors',
58-
type: ValidatorError.NonNull.List,
49+
type: Validation.NonNull.List,
5950
},
6051
});
6152
});

src/errors/index.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { InterfaceTypeComposer, SchemaComposer } from 'graphql-compose';
22
import { MongoError, getMongoErrorOTC } from './MongoError';
33
import { ValidationError, getValidationErrorOTC } from './ValidationError';
4+
import { ManyValidationError, getManyValidationErrorOTC } from './ManyValidationError';
45
import { RuntimeError, getRuntimeErrorOTC } from './RuntimeError';
56

6-
export { MongoError, ValidationError, RuntimeError };
7+
export { MongoError, ValidationError, ManyValidationError, RuntimeError };
78

89
export function getErrorInterface(schemaComposer: SchemaComposer<any>): InterfaceTypeComposer {
910
const ErrorInterface = schemaComposer.getOrCreateIFTC('ErrorInterface', (iftc) => {
@@ -44,3 +45,43 @@ export function getErrorInterface(schemaComposer: SchemaComposer<any>): Interfac
4445

4546
return ErrorInterface;
4647
}
48+
49+
export function getManyErrorInterface(schemaComposer: SchemaComposer<any>): InterfaceTypeComposer {
50+
const ErrorInterface = schemaComposer.getOrCreateIFTC('ManyErrorInterface', (iftc) => {
51+
iftc.addFields({
52+
message: {
53+
description: 'Generic error message',
54+
type: 'String',
55+
},
56+
});
57+
58+
const ManyValidationErrorOTC = getManyValidationErrorOTC(schemaComposer);
59+
const MongoErrorOTC = getMongoErrorOTC(schemaComposer);
60+
const RuntimeErrorOTC = getRuntimeErrorOTC(schemaComposer);
61+
62+
ManyValidationErrorOTC.addInterface(iftc);
63+
MongoErrorOTC.addInterface(iftc);
64+
RuntimeErrorOTC.addInterface(iftc);
65+
66+
schemaComposer.addSchemaMustHaveType(ManyValidationErrorOTC);
67+
schemaComposer.addSchemaMustHaveType(MongoErrorOTC);
68+
schemaComposer.addSchemaMustHaveType(RuntimeErrorOTC);
69+
70+
const ManyValidationErrorType = ManyValidationErrorOTC.getType();
71+
const MongoErrorType = MongoErrorOTC.getType();
72+
const RuntimeErrorType = RuntimeErrorOTC.getType();
73+
74+
iftc.setResolveType((value) => {
75+
switch (value?.name) {
76+
case 'ManyValidationError':
77+
return ManyValidationErrorType;
78+
case 'MongoError':
79+
return MongoErrorType;
80+
default:
81+
return RuntimeErrorType;
82+
}
83+
});
84+
});
85+
86+
return ErrorInterface;
87+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { Error as MongooseError } from 'mongoose';
2+
import type { Document } from 'mongoose';
3+
4+
export type Validation = {
5+
path: string;
6+
message: string;
7+
value: any;
8+
};
9+
10+
export type Validations = Array<Validation>;
11+
12+
export type ValidationsWithMessage = {
13+
message: string;
14+
errors: Array<Validation>;
15+
};
16+
17+
export type ManyValidations = Array<ValidationsWithMessage | null>;
18+
19+
export type ManyValidationsWithMessage = {
20+
message: string;
21+
errors: ManyValidations;
22+
};
23+
24+
export async function validationsForDocument(
25+
doc: Document
26+
): Promise<ValidationsWithMessage | null> {
27+
const validations: MongooseError.ValidationError | null = await new Promise(function (resolve) {
28+
doc.validate(resolve);
29+
});
30+
31+
return Promise.resolve(
32+
validations && validations.errors
33+
? {
34+
message: validations.message,
35+
errors: Object.keys(validations.errors).map((key) => {
36+
const { message, value } = validations.errors[key];
37+
return {
38+
path: key,
39+
message,
40+
value,
41+
};
42+
}),
43+
}
44+
: null
45+
);
46+
}

src/resolvers/__tests__/createMany-test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ describe('createMany() ->', () => {
9494
args: {
9595
records: [{ name: 'newName', contacts: { email: 'mail' } }],
9696
},
97+
projection: { error: true },
9798
});
9899
expect(result.recordIds).toBeTruthy();
99100
});
@@ -105,6 +106,7 @@ describe('createMany() ->', () => {
105106
{ name: 'newName0', contacts: { email: 'mail' } },
106107
{ name: 'newName1', contacts: { email: 'mail' } },
107108
],
109+
projection: { error: true },
108110
},
109111
});
110112
expect(result.createCount).toBe(2);
@@ -134,6 +136,7 @@ describe('createMany() ->', () => {
134136
{ name: checkedName, contacts: { email: 'mail' } },
135137
{ name: checkedName, contacts: { email: 'mail' } },
136138
],
139+
projection: { error: true },
137140
},
138141
});
139142

@@ -147,6 +150,7 @@ describe('createMany() ->', () => {
147150
const result = await createMany(UserModel, UserTC).resolve({
148151
args: {
149152
records: [{ name: 'NewUser', contacts: { email: 'mail' } }],
153+
projection: { error: true },
150154
},
151155
});
152156
expect(result.records[0]._id).toBe(result.recordIds[0]);
@@ -155,6 +159,7 @@ describe('createMany() ->', () => {
155159
it('should return mongoose documents', async () => {
156160
const result = await createMany(UserModel, UserTC).resolve({
157161
args: { records: [{ name: 'NewUser', contacts: { email: 'mail' } }] },
162+
projection: { error: true },
158163
});
159164
expect(result.records[0]).toBeInstanceOf(UserModel);
160165
});
@@ -166,6 +171,7 @@ describe('createMany() ->', () => {
166171
{ name: 'NewUser0', contacts: { email: 'mail' } },
167172
{ name: 'NewUser1', contacts: { email: 'mail' } },
168173
],
174+
projection: { error: true },
169175
},
170176
context: { ip: '1.1.1.1' },
171177
beforeRecordMutate: (record: any, rp: ExtendedResolveParams) => {
@@ -203,6 +209,7 @@ describe('createMany() ->', () => {
203209
{ name: 'NewUser0', contacts: { email: 'mail' } },
204210
{ name: 'NewUser1', contacts: { email: 'mail' } },
205211
],
212+
projection: { error: true },
206213
},
207214
context: { ip: '1.1.1.1' },
208215
beforeRecordMutate: (record: any, rp: ExtendedResolveParams) => {

src/resolvers/__tests__/updateOne-test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ describe('updateOne() ->', () => {
171171
expect(result.error.message).toEqual(
172172
'User validation failed: valid: this is a validate message'
173173
);
174+
174175
expect(result.error.errors).toEqual([
175176
{
176177
message: 'this is a validate message',

0 commit comments

Comments
 (0)