All files / src/flowsheet-cloning cloneValidation.ts

13.63% Statements 3/22
0% Branches 0/28
0% Functions 0/2
17.64% Lines 3/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139      103x                                                           103x                                         103x                                                                                                                                                                        
import type { FlowsheetCloneValidationError } from "@/api/apiStore.gen";
 
export const CLONE_VALIDATION_ERROR_CODE =
  "flowsheet_clone_validation_failed" satisfies `${FlowsheetCloneValidationError["code"]}`;
 
export type CloneDeficiencyCode =
  `${FlowsheetCloneValidationError["categories"][number]["code"]}`;
 
export type CloneProblemItem = {
  flowsheetId: number;
  flowsheetName: string;
  objectName: string;
  objectType: string;
  fieldName: string;
  referenceName: string | null;
};
 
export type CloneProblemCategory = {
  code: string;
  totalCount: number;
  items: CloneProblemItem[];
  truncated: boolean;
};
 
export type CloneValidationProblem = {
  categories: CloneProblemCategory[];
  flowsheetId: number;
  flowsheetName: string;
};
 
export const CLONE_DEFICIENCY_COPY: Record<
  CloneDeficiencyCode,
  { label: string; title: string; description: string }
> = {
  formula_references: {
    label: "Formulas",
    title: "Formula references need attention",
    description:
      "One or more formulas refer to an item or property that is no longer available in this flowsheet.",
  },
  flowsheet_relationships: {
    label: "Linked items",
    title: "Linked flowsheet items need attention",
    description:
      "Some flowsheet items point to content that is missing or no longer belongs to this flowsheet.",
  },
  flowsheet_structure: {
    label: "Structure",
    title: "Flowsheet structure needs attention",
    description:
      "Part of the flowsheet hierarchy is incomplete or inconsistent.",
  },
};
 
export const UNKNOWN_CLONE_DEFICIENCY_COPY = {
  label: "Other",
  title: "Saved flowsheet data needs attention",
  description:
    "Some saved content is inconsistent and must be corrected before this action can continue.",
};
 
function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}
 
function parseProblemItem(value: unknown): CloneProblemItem | null {
  if (
    !isRecord(value) ||
    typeof value.flowsheet_id !== "number" ||
    typeof value.flowsheet_name !== "string" ||
    typeof value.object_name !== "string" ||
    typeof value.object_type !== "string" ||
    typeof value.field_name !== "string" ||
    (value.reference_name !== null && typeof value.reference_name !== "string")
  ) {
    return null;
  }
  return {
    flowsheetId: value.flowsheet_id,
    flowsheetName: value.flowsheet_name,
    objectName: value.object_name,
    objectType: value.object_type,
    fieldName: value.field_name,
    referenceName: value.reference_name,
  };
}
 
function parseProblemCategory(value: unknown): CloneProblemCategory | null {
  if (
    !isRecord(value) ||
    typeof value.code !== "string" ||
    typeof value.total_count !== "number" ||
    value.total_count < 1 ||
    !Array.isArray(value.items) ||
    typeof value.truncated !== "boolean"
  ) {
    return null;
  }
  const items = value.items.map(parseProblemItem);
  Iif (items.some((item) => item === null)) return null;
  return {
    code: value.code,
    totalCount: value.total_count,
    items: items as CloneProblemItem[],
    truncated: value.truncated,
  };
}
 
/** Parse only the stable clone-validation contract; never expose raw API errors. */
export function parseCloneValidationProblem(
  error: unknown,
): CloneValidationProblem | null {
  Iif (!isRecord(error) || !isRecord(error.data)) return null;
  const { data } = error;
  if (
    data.code !== CLONE_VALIDATION_ERROR_CODE ||
    !Array.isArray(data.categories) ||
    data.categories.length === 0 ||
    typeof data.flowsheet_id !== "number" ||
    typeof data.flowsheet_name !== "string"
  ) {
    return null;
  }
  const categories = data.categories.map(parseProblemCategory);
  Iif (categories.some((category) => category === null)) return null;
 
  return {
    categories: categories as CloneProblemCategory[],
    flowsheetId: data.flowsheet_id,
    flowsheetName: data.flowsheet_name,
  };
}
 
export function cloneDeficiencyCopy(issue: string) {
  return issue in CLONE_DEFICIENCY_COPY
    ? CLONE_DEFICIENCY_COPY[issue as CloneDeficiencyCode]
    : UNKNOWN_CLONE_DEFICIENCY_COPY;
}