Skip to content

Commit

Permalink
regroup data validations functions in one
Browse files Browse the repository at this point in the history
  • Loading branch information
Timi-Duban committed Jul 10, 2021
1 parent 2fa5618 commit 4d675bb
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ interface BuildDefaultMutationResolversInput {
}

interface GetDocumentSelectorInput {
// TODO: put in common with the single resolver variables type, that have the same fields
variables: {
_id?: string;
input: any; // SingleInput
Expand Down
20 changes: 5 additions & 15 deletions packages/graphql/server/resolvers/mutators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ to the client.
*/

import {
validateDocument,
validateData,
validateDatas,
modifierToData,
dataToModifier,
dataToModifier
} from "./validation";
import { runCallbacks } from "@vulcanjs/core";

Expand Down Expand Up @@ -71,7 +70,6 @@ const validateMutationData = async ({
mutatorName,
context,
properties,
validationFunction,
}: {
model: VulcanGraphqlModel; // data model
mutatorName: DefaultMutatorName;
Expand All @@ -81,12 +79,8 @@ const validateMutationData = async ({
validationFunction?: Function;
}): Promise<void> => {
const { typeName } = model.graphql;
// basic simple schema validatio
const simpleSchemaValidationErrors = data
? validationFunction
? validationFunction(data, model, context) // for update, we use validateData instead of validateDocument
: validateDocument(data, model, context)
: []; // delete mutator has no data, so we skip the simple schema validation
// basic simple schema validation
const simpleSchemaValidationErrors = validateDatas({document: data, model, context, mutatorName});
// custom validation
const customValidationErrors = await runCallbacks({
hookName: `${typeName}.${mutatorName}.validate`,
Expand Down Expand Up @@ -227,12 +221,9 @@ export const createMutator = async <TModel extends VulcanDocument>({
});
}


/* If user is logged in, check if userId field is in the schema and add it to document if needed */
if (currentUser) {
// TODO: clean this using "has"
const userIdInSchema = "userId" in schema;
if (!!userIdInSchema && !data.userId) data.userId = currentUser._id;
if (schema.hasOwnProperty('userId') && !data.userId) data.userId = currentUser._id;
}
/*
Expand Down Expand Up @@ -388,7 +379,6 @@ export const updateMutator = async <TModel extends VulcanDocument>({
mutatorName,
context,
properties,
validationFunction: validateData, // TODO: update uses validateData instead of validateDocument => why?
});
}

Expand Down
152 changes: 54 additions & 98 deletions packages/graphql/server/resolvers/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import _forEach from "lodash/forEach";
import { VulcanModel } from "@vulcanjs/model";
import { ContextWithUser } from "./typings";
import { DefaultMutatorName } from "../../typings";
import { canCreateField, canUpdateField } from "@vulcanjs/permissions";
import { toSimpleSchema, ValidationError } from "@vulcanjs/schema";

Expand Down Expand Up @@ -74,125 +75,80 @@ const validateDocumentPermissions = (
);
return validationErrors;
};

interface ValidateDatasInput {
document: VulcanDocument;
model: VulcanModel;
context: any;
mutatorName: DefaultMutatorName;
validationContextName?: string;
}
/*
If document is not trusted, run validation steps:
1. Check that the current user has permission to edit each field
1. Check that the current user has permission to insert each field
2. Run SimpleSchema validation step
*/
export const validateDocument = (
document: VulcanDocument,
model: VulcanModel,
context: any,
export const validateDatas = ({
document,
model,
context,
mutatorName,
validationContextName = "defaultContext" // TODO: what is this?
): Array<ValidationError> => {
}: ValidateDatasInput): Array<ValidationError> => {
const { schema } = model;

let validationErrors: Array<ValidationError> = [];

// validate creation permissions (and other Vulcan-specific constraints)
// delete mutator has no data, so we skip the simple schema validation
if (mutatorName === 'delete') {
return validationErrors;
}
// validate operation permissions on each field (and other Vulcan-specific constraints)
validationErrors = validationErrors.concat(
validateDocumentPermissions(document, document, schema, context, "create")
validateDocumentPermissions(document, document, schema, context, mutatorName)
);
// build the schema on the fly
// TODO: does it work with nested schema???
const simpleSchema = toSimpleSchema(schema);
// run simple schema validation (will check the actual types, required fields, etc....)
const validationContext = simpleSchema.namedContext(validationContextName);
validationContext.validate(document);

if (!validationContext.isValid()) {
const errors = (validationContext as any).validationErrors();
errors.forEach((error) => {
// eslint-disable-next-line no-console
// console.log(error);
if (error.type.includes("intlError")) {
const intlError = JSON.parse(error.type.replace("intlError|", ""));
validationErrors = validationErrors.concat(intlError);
} else {
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: {
modelName: model.name,
// typeName: collection.options.typeName,
...error,
},
});
}
});
// validate the schema, depends on which operation you want to do.
if (mutatorName === 'create') {
validationContext.validate(document);
}
if (mutatorName === 'update') {
const modifier: Modifier = dataToModifier(document);
const set = modifier.$set;
const unset = modifier.$unset
validationContext.validate(
{ $set: set, $unset: unset },
{ modifier: true, extendedCustomContext: { documentId: document._id } }
);
}

return validationErrors;
};

/*
If document is not trusted, run validation steps:
1. Check that the current user has permission to insert each field
2. Run SimpleSchema validation step
*/
export const validateModifier = (
modifier: Modifier,
document: VulcanDocument,
model: VulcanModel,
context,
validationContextName = "defaultContext"
) => {
const { schema } = model;
const set = modifier.$set;
const unset = modifier.$unset;

let validationErrors: Array<ValidationError> = [];

// 1. check that the current user has permission to edit each field
validationErrors = validationErrors.concat(
validateDocumentPermissions(document, document, schema, context, "update")
);

// 2. run SS validation
const validationContext = toSimpleSchema(schema).namedContext(
validationContextName
);
validationContext.validate(
{ $set: set, $unset: unset },
{ modifier: true, extendedCustomContext: { documentId: document._id } }
);

if (!validationContext.isValid()) {
const errors = validationContext.validationErrors();
errors.forEach((error) => {
// eslint-disable-next-line no-console
// console.log(error);
if (error?.type?.includes("intlError")) {
validationErrors = validationErrors.concat(
JSON.parse(error.type.replace("intlError|", ""))
);
} else {
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: {
modelName: model.name,
// typeName: collection.options.typeName,
...error,
},
});
}
});
const errors = (validationContext as any).validationErrors();
errors.forEach((error) => {
// eslint-disable-next-line no-console
// console.log(error);
if (error.type.includes("intlError")) {
const intlError = JSON.parse(error.type.replace("intlError|", ""));
validationErrors = validationErrors.concat(intlError);
} else {
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: {
modelName: model.name,
// typeName: collection.options.typeName,
...error,
},
});
}
});
}

return validationErrors;
};

export const validateData = (
data: VulcanDocument,
model: VulcanModel,
context
) => {
return validateModifier(dataToModifier(data), data, model, context);
};
};
Loading

0 comments on commit 4d675bb

Please sign in to comment.