blob: 027553cdc591454f2634ee060f08f8edc0687f89 (
plain)
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
|
/**
* Pure parser for the `rendererId: "table"` custom-field payload.
*
* This is the FRONTEND-side renderer contract for tabular custom fields: a
* backend that wants a table emits a `custom` field with `rendererId: "table"`
* and a payload of `{ columns: string[]; rows: (string|number)[][] }`. Cells are
* coerced to strings. Anything that does not match the shape returns `null`, so
* the renderer gracefully skips it (never throws on hostile/partial data).
*/
export interface TableData {
readonly columns: readonly string[];
readonly rows: readonly (readonly string[])[];
}
function isStringArray(v: unknown): v is unknown[] {
return Array.isArray(v);
}
function coerceCell(v: unknown): string | null {
if (typeof v === "string") return v;
if (typeof v === "number" && Number.isFinite(v)) return String(v);
if (typeof v === "boolean") return String(v);
return null;
}
export function parseTablePayload(payload: unknown): TableData | null {
if (typeof payload !== "object" || payload === null) return null;
const obj = payload as Record<string, unknown>;
const rawColumns = obj.columns;
const rawRows = obj.rows;
if (!isStringArray(rawColumns) || !isStringArray(rawRows)) return null;
const columns: string[] = [];
for (const col of rawColumns) {
if (typeof col !== "string") return null;
columns.push(col);
}
const rows: string[][] = [];
for (const row of rawRows) {
if (!Array.isArray(row)) return null;
const cells: string[] = [];
for (const cell of row) {
const c = coerceCell(cell);
if (c === null) return null;
cells.push(c);
}
rows.push(cells);
}
return { columns, rows };
}
|