Source

validators/ClauseSequenceValidator.ts

import {
  DEFAULT_ERROR_MESSAGES,
  sf,
  validator,
  Validator,
  ValidatorOptions,
} from "@decaf-ts/decorator-validation";
import { isEqual } from "@decaf-ts/reflection";
import { Clause } from "../query/Clause";
import { MandatoryPriorities } from "../query/constants";
import { QueryError } from "../query/errors";
import { PersistenceKeys } from "../persistence/constants";

/**
 * @summary Validates a {@link Sequence}'s {@link Clause}s
 *
 * @param {string} [message]
 *
 * @class ClauseSequenceValidator
 * @extends Validator
 *
 * @category Validation
 * @subcategory Validators
 */
@validator(PersistenceKeys.CLAUSE_SEQUENCE)
export class ClauseSequenceValidator extends Validator {
  constructor(
    message: string = DEFAULT_ERROR_MESSAGES[PersistenceKeys.CLAUSE_SEQUENCE]
  ) {
    super(message);
  }

  private validateSequence(
    clauses: Clause<any>[],
    message?: string
  ): string | undefined {
    return MandatoryPriorities.every(
      (p) => !!clauses.find((c) => c.getPriority() === p)
    )
      ? undefined
      : this.getMessage(
          sf(message || this.message, "Missing required Clause Priorities")
        );
  }

  /**
   * @summary Verifies the model for errors
   * @param {string} value
   * @param {ValidatorOptions} [options]
   *
   * @return Errors
   *
   * @override
   *
   * @see Validator
   */
  public hasErrors(value: any, options?: ValidatorOptions): string | undefined {
    try {
      if (
        !value ||
        !Array.isArray(value) ||
        !value.length ||
        !value.every((e) => e instanceof Clause)
      )
        return this.getMessage(
          sf(
            (options || {}).message || this.message,
            "No or invalid Clauses found"
          )
        );

      const clauses: Clause<any>[] = value as Clause<any>[];

      const clauseErrors = clauses.reduce(
        (accum: string | undefined, c: Clause<any>) => {
          const errs = c.hasErrors();
          if (errs)
            if (accum)
              accum += sf(
                "\nClause {0}: {1}",
                c.constructor.name,
                errs.toString()
              );
            else
              accum = sf(
                "Clause {0}: {1}",
                c.constructor.name,
                errs.toString()
              );
          return accum;
        },
        undefined
      );

      if (clauseErrors)
        return this.getMessage(
          sf((options || {}).message || this.message, clauseErrors.toString())
        );

      const verifyPriority = () => {
        const priorities = clauses.map((c) => c.getPriority());
        const allUnique = new Set(priorities).size === priorities.length;
        if (!allUnique) return "Not all clauses  have unique priorities";
        const sorted = priorities.sort((a, b) => {
          return b - a;
        });

        return isEqual(priorities, sorted)
          ? true
          : "Clauses  are not properly sorted";
      };

      const priorityCheck = verifyPriority();
      if (priorityCheck !== true)
        return this.getMessage(
          sf((options || {}).message || this.message, "Invalid prioritization")
        );

      const sequenceCheck = this.validateSequence(
        clauses,
        (options || {}).message
      );
      if (sequenceCheck)
        return this.getMessage(
          sf((options || {}).message || this.message, "Invalid sequence")
        );
    } catch (e: any) {
      throw new QueryError(
        sf("Failed to verify clause sequence {0}: {1}", value, e)
      );
    }
  }
}