Skip to content

Commit e7ccde9

Browse files
committed
refactor: create helpers/validate and move all validation logic ther for reducing copy-paste & keeping resolver logic slim
1 parent 1916b3d commit e7ccde9

File tree

9 files changed

+105
-96
lines changed

9 files changed

+105
-96
lines changed

src/errors/ManyValidationError.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import { getValidationErrorOTC } from './ValidationError';
22
import { SchemaComposer, ObjectTypeComposer } from 'graphql-compose';
33
import { GraphQLError } from 'graphql';
4+
import { ValidationsWithMessage } from '../resolvers/helpers/validate';
45

5-
import { ValidationsWithMessage, ManyValidationsWithMessage } from './validationsForDocument';
6+
/**
7+
* If we validating multiple documents, we are using array with nulls in some positions.
8+
* It helps to indicate idx of input records which have validation errors; and `null` for records without errors.
9+
* So keep strict order in this array with input records array.
10+
*/
11+
export type ManyValidationsByIdx = Array<ValidationsWithMessage | null>;
12+
13+
export type ManyValidationsWithMessage = {
14+
message: string;
15+
errors: ManyValidationsByIdx;
16+
};
617

718
export class ManyValidationError extends GraphQLError {
8-
public errors: Array<ValidationsWithMessage | null>;
19+
public errors: ManyValidationsByIdx;
920

1021
constructor(data: ManyValidationsWithMessage) {
1122
super(data.message, undefined, undefined, undefined, undefined, undefined, {

src/errors/ValidationError.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { SchemaComposer, ObjectTypeComposer } from 'graphql-compose';
22
import { GraphQLError } from 'graphql';
3-
4-
import type { Validations, ValidationsWithMessage } from './validationsForDocument';
3+
import type { ValidationErrorData, ValidationsWithMessage } from '../resolvers/helpers/validate';
54

65
export class ValidationError extends GraphQLError {
7-
public errors: Validations;
6+
public errors: ValidationErrorData[];
87

98
constructor(validation: ValidationsWithMessage) {
109
super(validation.message, undefined, undefined, undefined, undefined, undefined, {

src/errors/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
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';
4+
import {
5+
ManyValidationError,
6+
getManyValidationErrorOTC,
7+
ManyValidationsByIdx,
8+
} from './ManyValidationError';
59
import { RuntimeError, getRuntimeErrorOTC } from './RuntimeError';
610

7-
export { MongoError, ValidationError, ManyValidationError, RuntimeError };
11+
export { MongoError, ValidationError, ManyValidationError, ManyValidationsByIdx, RuntimeError };
812

913
export function getErrorInterface(schemaComposer: SchemaComposer<any>): InterfaceTypeComposer {
1014
const ErrorInterface = schemaComposer.getOrCreateIFTC('ErrorInterface', (iftc) => {

src/errors/validationsForDocument.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/resolvers/createMany.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@ import type { Model, Document } from 'mongoose';
33
import { recordHelperArgs } from './helpers';
44
import type { GenResolverOpts } from './index';
55
import { addManyErrorCatcherField } from './helpers/addErrorCatcherField';
6-
import { ManyValidationError } from '../errors';
7-
import {
8-
validationsForDocument,
9-
ValidationsWithMessage,
10-
ManyValidations,
11-
} from '../errors/validationsForDocument';
6+
import { validateManyAndThrow } from './helpers/validate';
127

138
export default function createMany<TSource = Document, TContext = any>(
149
model: Model<any>,
@@ -89,30 +84,17 @@ export default function createMany<TSource = Document, TContext = any>(
8984
}
9085
}
9186

92-
const manyValidations: ManyValidations = [];
9387
const docs = [];
94-
9588
for (const record of recordData) {
9689
// eslint-disable-next-line new-cap
9790
let doc: Document = new model(record);
9891
if (resolveParams.beforeRecordMutate) {
9992
doc = await resolveParams.beforeRecordMutate(doc, resolveParams);
10093
}
101-
102-
const validations: ValidationsWithMessage | null = await validationsForDocument(doc);
103-
104-
manyValidations.push(validations ? validations : null);
10594
docs.push(doc);
10695
}
10796

108-
const hasValidationError = !manyValidations.every((error) => error === null);
109-
if (hasValidationError) {
110-
throw new ManyValidationError({
111-
message: 'Cannot execute createMany, some documents contain errors',
112-
errors: manyValidations,
113-
});
114-
}
115-
97+
await validateManyAndThrow(docs);
11698
await model.create(docs, { validateBeforeSave: false });
11799

118100
return {

src/resolvers/createOne.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import type { Model, Document } from 'mongoose';
33
import { recordHelperArgs } from './helpers';
44
import type { ExtendedResolveParams, GenResolverOpts } from './index';
55
import { addErrorCatcherField } from './helpers/addErrorCatcherField';
6-
import { ValidationError } from '../errors';
7-
import { validationsForDocument, ValidationsWithMessage } from '../errors/validationsForDocument';
6+
import { validateAndThrow } from './helpers/validate';
87

98
export default function createOne<TSource = Document, TContext = any>(
109
model: Model<any>,
@@ -76,11 +75,7 @@ export default function createOne<TSource = Document, TContext = any>(
7675
if (!doc) return null;
7776
}
7877

79-
const validations: ValidationsWithMessage | null = await validationsForDocument(doc);
80-
if (validations) {
81-
throw new ValidationError(validations);
82-
}
83-
78+
await validateAndThrow(doc);
8479
await doc.save({ validateBeforeSave: false });
8580
return {
8681
record: doc,

src/resolvers/helpers/validate.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { Error as MongooseError } from 'mongoose';
2+
import type { Document } from 'mongoose';
3+
import { ValidationError, ManyValidationError, ManyValidationsByIdx } from '../../errors';
4+
5+
export type ValidationErrorData = {
6+
path: string;
7+
message: string;
8+
value: any;
9+
};
10+
11+
export type ValidationsWithMessage = {
12+
message: string;
13+
errors: Array<ValidationErrorData>;
14+
};
15+
16+
export async function validateDoc(doc: Document): Promise<ValidationsWithMessage | null> {
17+
const validations: MongooseError.ValidationError | null = await new Promise((resolve) => {
18+
doc.validate(resolve);
19+
});
20+
21+
return validations?.errors
22+
? {
23+
message: validations.message,
24+
errors: Object.keys(validations.errors).map((key) => {
25+
// transform object to array[{ path, message, value }, {}, ...]
26+
const { message, value } = validations.errors[key];
27+
return {
28+
path: key,
29+
message,
30+
value,
31+
};
32+
}),
33+
}
34+
: null;
35+
}
36+
37+
/**
38+
* Make async validation for mongoose document.
39+
* And if it has validation errors then throw one Error with embedding all validation errors into it.
40+
*/
41+
export async function validateAndThrow(doc: Document): Promise<void> {
42+
const validations: ValidationsWithMessage | null = await validateDoc(doc);
43+
if (validations) {
44+
throw new ValidationError(validations);
45+
}
46+
}
47+
48+
/**
49+
* Make async validation for array of mongoose documents.
50+
* And if they have validation errors then throw one Error with embedding
51+
* all validation errors for every document separately.
52+
* If document does not have error then in embedded errors' array will
53+
* be `null` at the same idx position.
54+
*/
55+
export async function validateManyAndThrow(docs: Document[]): Promise<void> {
56+
const manyValidations: ManyValidationsByIdx = [];
57+
let hasValidationError = false;
58+
59+
for (const doc of docs) {
60+
const validations: ValidationsWithMessage | null = await validateDoc(doc);
61+
62+
if (validations) {
63+
manyValidations.push(validations);
64+
hasValidationError = true;
65+
} else {
66+
manyValidations.push(null);
67+
}
68+
}
69+
70+
if (hasValidationError) {
71+
throw new ManyValidationError({
72+
message: 'Some documents contain validation errors',
73+
errors: manyValidations,
74+
});
75+
}
76+
}

src/resolvers/updateById.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { recordHelperArgs } from './helpers/record';
44
import findById from './findById';
55
import { addErrorCatcherField } from './helpers/addErrorCatcherField';
66
import type { ExtendedResolveParams, GenResolverOpts } from './index';
7-
import { validationsForDocument, ValidationsWithMessage } from '../errors/validationsForDocument';
8-
import { ValidationError } from '../errors';
7+
import { validateAndThrow } from './helpers/validate';
98

109
export default function updateById<TSource = Document, TContext = any>(
1110
model: Model<any>,
@@ -96,12 +95,7 @@ export default function updateById<TSource = Document, TContext = any>(
9695
}
9796

9897
doc.set(recordData);
99-
100-
const validations: ValidationsWithMessage | null = await validationsForDocument(doc);
101-
if (validations) {
102-
throw new ValidationError(validations);
103-
}
104-
98+
await validateAndThrow(doc);
10599
await doc.save({ validateBeforeSave: false });
106100

107101
return {

src/resolvers/updateOne.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import type { ExtendedResolveParams, GenResolverOpts } from './index';
44
import { skipHelperArgs, recordHelperArgs, filterHelperArgs, sortHelperArgs } from './helpers';
55
import findOne from './findOne';
66
import { addErrorCatcherField } from './helpers/addErrorCatcherField';
7-
import { ValidationError } from '../errors';
8-
import { validationsForDocument, ValidationsWithMessage } from '../errors/validationsForDocument';
7+
import { validateAndThrow } from './helpers/validate';
98

109
export default function updateOne<TSource = Document, TContext = any>(
1110
model: Model<any>,
@@ -92,12 +91,7 @@ export default function updateOne<TSource = Document, TContext = any>(
9291

9392
if (recordData) {
9493
doc.set(recordData);
95-
96-
const validations: ValidationsWithMessage | null = await validationsForDocument(doc);
97-
if (validations) {
98-
throw new ValidationError(validations);
99-
}
100-
94+
await validateAndThrow(doc);
10195
await doc.save({ validateBeforeSave: false });
10296
}
10397

0 commit comments

Comments
 (0)