public async validate()

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);
  }