in src/services/form-validation/FormValidationService.ts [30:130]
public async validate(
formSchemaInput: DeepReadonly<FormSchema>,
submissionInput: DeepReadonly<FormSubmission>,
): Promise<FormSubmission['data']> {
// TODO: include subforms logic
// Copying values in order to avoid external data mutations
const formSchema = _.cloneDeep<FormSchema>(formSchemaInput);
let submission = _.cloneDeep<FormSubmission>(submissionInput);
// TODO: evalContext
// TODO: this.model and this.token
const normalizedFormSchema = {
...formSchema,
components: this.normalizeComponents(formSchema.components),
};
submission = this.normalizeSubmission(normalizedFormSchema.components, submission);
const unsets: Array<{
key: string;
data: unknown;
}> = [];
let unsetsEnabled = false;
const isEmptyData = _.isEmpty(submission.data);
const form = await Formio.createForm(normalizedFormSchema, {
server: true,
language: this.language,
i18n: this.i18n,
hooks: {
setDataValue(this: any, value: never, key: string, data: never) {
if (!unsetsEnabled) {
return value;
}
// Check if this component is not persistent.
if (
Object.prototype.hasOwnProperty.call(this.component, 'persistent') &&
(!this.component.persistent || this.component.persistent === 'client-only')
) {
unsets.push({ key, data });
// Check if this component is conditionally hidden and does not set clearOnHide to false.
} else if (
(!Object.prototype.hasOwnProperty.call(this.component, 'clearOnHide') || this.component.clearOnHide) &&
(!this.conditionallyVisible() || !this.parentVisible)
) {
// unsets.push({ key, data });
} else if (this.component.type === 'password' && value === this.defaultValue) {
unsets.push({ key, data });
}
return value;
},
},
});
// Set the validation config.
form.validator.config = {
// db: this.model, // TODO: might be needed in future
// token: this.token, // TODO: might be needed in future
form: normalizedFormSchema,
submission,
};
// Set the submission data
form.data = submission.data;
// Perform calculations and conditions.
form.calculateValue();
form.checkConditions();
// Reset the data
form.data = {};
// Set the value to the submission.
unsetsEnabled = true;
form.setValue(submission, {
sanitize: true,
});
// Check the validity of the form.
const isValid: boolean = await form.checkAsyncValidity(null, true);
if (isValid) {
// Clear the non-persistent fields.
unsets.forEach((unset) => _.unset(unset.data, unset.key));
submission.data = isEmptyData ? {} : form.data;
// fix for memory leak
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (Formio as any).forms[form.id];
return submission.data; // TODO: Check if we need another return data structure
}
const details: Array<ValidationErrorDetailsItem> = [];
form.errors.forEach((error: { messages: ValidationErrorDetailsItem[] }) =>
error.messages.forEach((message) => details.push({ ...message, message: entities.decode(message.message) })),
);
// fix for memory leak
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (Formio as any).forms[form.id];
// Return the validation errors.
throw new FormValidationError(details);
}