refactor: share interactive payload normalizers

This commit is contained in:
Peter Steinberger
2026-04-23 18:27:56 +01:00
parent 61ab68f5c9
commit 4e97b57c17

View File

@@ -46,17 +46,9 @@ export type MessagePresentationTone = "info" | "success" | "warning" | "danger"
export type MessagePresentationButtonStyle = InteractiveButtonStyle;
export type MessagePresentationButton = {
label: string;
value?: string;
url?: string;
style?: MessagePresentationButtonStyle;
};
export type MessagePresentationButton = InteractiveReplyButton;
export type MessagePresentationOption = {
label: string;
value: string;
};
export type MessagePresentationOption = InteractiveReplyOption;
export type MessagePresentationTextBlock = {
type: "text";
@@ -124,11 +116,18 @@ function normalizePresentationTone(value: unknown): MessagePresentationTone | un
: undefined;
}
function normalizeInteractiveButton(raw: unknown): InteractiveReplyButton | undefined {
function toRecord(raw: unknown): Record<string, unknown> | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return undefined;
}
const record = raw as Record<string, unknown>;
return raw as Record<string, unknown>;
}
function normalizeButton(raw: unknown): InteractiveReplyButton | undefined {
const record = toRecord(raw);
if (!record) {
return undefined;
}
const label = normalizeOptionalString(record.label) ?? normalizeOptionalString(record.text);
const value =
normalizeOptionalString(record.value) ??
@@ -146,11 +145,11 @@ function normalizeInteractiveButton(raw: unknown): InteractiveReplyButton | unde
};
}
function normalizeInteractiveOption(raw: unknown): InteractiveReplyOption | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
function normalizeOption(raw: unknown): InteractiveReplyOption | undefined {
const record = toRecord(raw);
if (!record) {
return undefined;
}
const record = raw as Record<string, unknown>;
const label = normalizeOptionalString(record.label) ?? normalizeOptionalString(record.text);
const value = normalizeOptionalString(record.value);
if (!label || !value) {
@@ -159,30 +158,28 @@ function normalizeInteractiveOption(raw: unknown): InteractiveReplyOption | unde
return { label, value };
}
function normalizeList<T>(value: unknown, normalizeEntry: (entry: unknown) => T | undefined): T[] {
return Array.isArray(value)
? value.map((entry) => normalizeEntry(entry)).filter((entry): entry is T => Boolean(entry))
: [];
}
function normalizeInteractiveBlock(raw: unknown): InteractiveReplyBlock | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
const record = toRecord(raw);
if (!record) {
return undefined;
}
const record = raw as Record<string, unknown>;
const type = normalizeOptionalLowercaseString(record.type);
if (type === "text") {
const text = normalizeOptionalString(record.text);
return text ? { type: "text", text } : undefined;
}
if (type === "buttons") {
const buttons = Array.isArray(record.buttons)
? record.buttons
.map((entry) => normalizeInteractiveButton(entry))
.filter((entry): entry is InteractiveReplyButton => Boolean(entry))
: [];
const buttons = normalizeList(record.buttons, normalizeButton);
return buttons.length > 0 ? { type: "buttons", buttons } : undefined;
}
if (type === "select") {
const options = Array.isArray(record.options)
? record.options
.map((entry) => normalizeInteractiveOption(entry))
.filter((entry): entry is InteractiveReplyOption => Boolean(entry))
: [];
const options = normalizeList(record.options, normalizeOption);
return options.length > 0
? {
type: "select",
@@ -195,50 +192,19 @@ function normalizeInteractiveBlock(raw: unknown): InteractiveReplyBlock | undefi
}
export function normalizeInteractiveReply(raw: unknown): InteractiveReply | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
const record = toRecord(raw);
if (!record) {
return undefined;
}
const record = raw as Record<string, unknown>;
const blocks = Array.isArray(record.blocks)
? record.blocks
.map((entry) => normalizeInteractiveBlock(entry))
.filter((entry): entry is InteractiveReplyBlock => Boolean(entry))
: [];
const blocks = normalizeList(record.blocks, normalizeInteractiveBlock);
return blocks.length > 0 ? { blocks } : undefined;
}
function normalizePresentationButton(raw: unknown): MessagePresentationButton | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return undefined;
}
const record = raw as Record<string, unknown>;
const label = normalizeOptionalString(record.label) ?? normalizeOptionalString(record.text);
const value =
normalizeOptionalString(record.value) ??
normalizeOptionalString(record.callbackData) ??
normalizeOptionalString(record.callback_data);
const url = normalizeOptionalString(record.url);
if (!label || (!value && !url)) {
return undefined;
}
return {
label,
...(value ? { value } : {}),
...(url ? { url } : {}),
style: normalizeButtonStyle(record.style),
};
}
function normalizePresentationOption(raw: unknown): MessagePresentationOption | undefined {
const option = normalizeInteractiveOption(raw);
return option ? { label: option.label, value: option.value } : undefined;
}
function normalizePresentationBlock(raw: unknown): MessagePresentationBlock | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
const record = toRecord(raw);
if (!record) {
return undefined;
}
const record = raw as Record<string, unknown>;
const type = normalizeOptionalLowercaseString(record.type);
if (type === "text" || type === "context") {
const text = normalizeOptionalString(record.text);
@@ -248,19 +214,11 @@ function normalizePresentationBlock(raw: unknown): MessagePresentationBlock | un
return { type: "divider" };
}
if (type === "buttons") {
const buttons = Array.isArray(record.buttons)
? record.buttons
.map((entry) => normalizePresentationButton(entry))
.filter((entry): entry is MessagePresentationButton => Boolean(entry))
: [];
const buttons = normalizeList(record.buttons, normalizeButton);
return buttons.length > 0 ? { type: "buttons", buttons } : undefined;
}
if (type === "select") {
const options = Array.isArray(record.options)
? record.options
.map((entry) => normalizePresentationOption(entry))
.filter((entry): entry is MessagePresentationOption => Boolean(entry))
: [];
const options = normalizeList(record.options, normalizeOption);
return options.length > 0
? {
type: "select",
@@ -273,15 +231,11 @@ function normalizePresentationBlock(raw: unknown): MessagePresentationBlock | un
}
export function normalizeMessagePresentation(raw: unknown): MessagePresentation | undefined {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
const record = toRecord(raw);
if (!record) {
return undefined;
}
const record = raw as Record<string, unknown>;
const blocks = Array.isArray(record.blocks)
? record.blocks
.map((entry) => normalizePresentationBlock(entry))
.filter((entry): entry is MessagePresentationBlock => Boolean(entry))
: [];
const blocks = normalizeList(record.blocks, normalizePresentationBlock);
const title = normalizeOptionalString(record.title);
if (!title && blocks.length === 0) {
return undefined;