File size: 2,302 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { DIFF_INDICATORS, DIFF_LAYOUTS, DIFF_THEMES } from "./types.js";
import type { DiffViewerPayload } from "./types.js";

const OVERFLOW_VALUES = ["scroll", "wrap"] as const;

export function parseViewerPayloadJson(raw: string): DiffViewerPayload {
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch {
    throw new Error("Diff payload is not valid JSON.");
  }

  if (!isDiffViewerPayload(parsed)) {
    throw new Error("Diff payload has invalid shape.");
  }

  return parsed;
}

function isDiffViewerPayload(value: unknown): value is DiffViewerPayload {
  if (!isRecord(value)) {
    return false;
  }

  if (typeof value.prerenderedHTML !== "string") {
    return false;
  }

  if (!Array.isArray(value.langs) || !value.langs.every((lang) => typeof lang === "string")) {
    return false;
  }

  if (!isViewerOptions(value.options)) {
    return false;
  }

  const hasFileDiff = isRecord(value.fileDiff);
  const hasBeforeAfterFiles = isRecord(value.oldFile) && isRecord(value.newFile);
  if (!hasFileDiff && !hasBeforeAfterFiles) {
    return false;
  }

  return true;
}

function isViewerOptions(value: unknown): boolean {
  if (!isRecord(value)) {
    return false;
  }

  if (!isRecord(value.theme)) {
    return false;
  }
  if (value.theme.light !== "pierre-light" || value.theme.dark !== "pierre-dark") {
    return false;
  }

  if (!includesValue(DIFF_LAYOUTS, value.diffStyle)) {
    return false;
  }
  if (!includesValue(DIFF_INDICATORS, value.diffIndicators)) {
    return false;
  }
  if (!includesValue(DIFF_THEMES, value.themeType)) {
    return false;
  }
  if (!includesValue(OVERFLOW_VALUES, value.overflow)) {
    return false;
  }

  if (typeof value.disableLineNumbers !== "boolean") {
    return false;
  }
  if (typeof value.expandUnchanged !== "boolean") {
    return false;
  }
  if (typeof value.backgroundEnabled !== "boolean") {
    return false;
  }
  if (typeof value.unsafeCSS !== "string") {
    return false;
  }

  return true;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

function includesValue<T extends readonly string[]>(values: T, value: unknown): value is T[number] {
  return typeof value === "string" && values.includes(value as T[number]);
}