refactor: share ACP metadata readers

This commit is contained in:
Vincent Koc
2026-05-30 08:53:37 +02:00
parent 26bf8f0dc8
commit 9b605846bb
2 changed files with 49 additions and 40 deletions

24
src/acp/meta.test.ts Normal file
View File

@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { readBool, readNonNegativeInteger, readNumber, readString } from "./meta.js";
describe("ACP metadata readers", () => {
it("returns the first normalized string value", () => {
expect(readString({ old: " ", current: " session-1 " }, ["old", "current"])).toBe("session-1");
});
it("preserves false boolean values", () => {
expect(readBool({ enabled: false, fallback: true }, ["enabled", "fallback"])).toBe(false);
});
it("accepts finite numbers and rejects non-numeric values", () => {
expect(readNumber({ first: "1", second: 0 }, ["first", "second"])).toBe(0);
expect(readNumber({ first: Number.POSITIVE_INFINITY }, ["first"])).toBeUndefined();
});
it("accepts zero as a non-negative integer", () => {
expect(readNonNegativeInteger({ count: 0, fallback: 2 }, ["count", "fallback"])).toBe(0);
expect(
readNonNegativeInteger({ count: -1, fallback: 2.5 }, ["count", "fallback"]),
).toBeUndefined();
});
});

View File

@@ -1,65 +1,50 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
function readMetaValue<T>(
meta: Record<string, unknown> | null | undefined,
keys: string[],
normalize: (value: unknown) => T | undefined,
): T | undefined {
if (!meta) {
return undefined;
}
for (const key of keys) {
const normalized = normalize(meta[key]);
if (normalized !== undefined) {
return normalized;
}
}
return undefined;
}
export function readString(
meta: Record<string, unknown> | null | undefined,
keys: string[],
): string | undefined {
if (!meta) {
return undefined;
}
for (const key of keys) {
const value = normalizeOptionalString(meta[key]);
if (value) {
return value;
}
}
return undefined;
return readMetaValue(meta, keys, normalizeOptionalString);
}
export function readBool(
meta: Record<string, unknown> | null | undefined,
keys: string[],
): boolean | undefined {
if (!meta) {
return undefined;
}
for (const key of keys) {
const value = meta[key];
if (typeof value === "boolean") {
return value;
}
}
return undefined;
return readMetaValue(meta, keys, (value) => (typeof value === "boolean" ? value : undefined));
}
export function readNumber(
meta: Record<string, unknown> | null | undefined,
keys: string[],
): number | undefined {
if (!meta) {
return undefined;
}
for (const key of keys) {
const value = meta[key];
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
}
return undefined;
return readMetaValue(meta, keys, (value) =>
typeof value === "number" && Number.isFinite(value) ? value : undefined,
);
}
export function readNonNegativeInteger(
meta: Record<string, unknown> | null | undefined,
keys: string[],
): number | undefined {
if (!meta) {
return undefined;
}
for (const key of keys) {
const value = meta[key];
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
return value;
}
}
return undefined;
return readMetaValue(meta, keys, (value) =>
typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined,
);
}