import { readFileSync, writeFileSync } from "node:fs"; import type { HttpExchangeFixture } from "./types.js"; export function serializeFixture(fx: HttpExchangeFixture): string { return `${JSON.stringify(fx, null, 2)}\n`; } export function parseFixture(text: string): HttpExchangeFixture { let raw: unknown; try { raw = JSON.parse(text); } catch { throw new Error("Invalid JSON"); } assertFixture(raw); return raw; } function assertFixture(raw: unknown): asserts raw is HttpExchangeFixture { if (raw === null || typeof raw !== "object" || Array.isArray(raw)) { throw new Error("Fixture must be an object"); } const obj = raw as Record; if (obj.request === null || typeof obj.request !== "object" || Array.isArray(obj.request)) { throw new Error("Fixture.request must be an object"); } const req = obj.request as Record; if (typeof req.method !== "string") throw new Error("Fixture.request.method must be a string"); if (typeof req.url !== "string") throw new Error("Fixture.request.url must be a string"); assertStringRecord(req.headers, "Fixture.request.headers"); if (req.body !== null && typeof req.body !== "string") { throw new Error("Fixture.request.body must be a string or null"); } if (obj.response === null || typeof obj.response !== "object" || Array.isArray(obj.response)) { throw new Error("Fixture.response must be an object"); } const res = obj.response as Record; if (typeof res.status !== "number") throw new Error("Fixture.response.status must be a number"); if (res.statusText !== undefined && typeof res.statusText !== "string") { throw new Error("Fixture.response.statusText must be a string if present"); } assertStringRecord(res.headers, "Fixture.response.headers"); if (typeof res.body !== "string") throw new Error("Fixture.response.body must be a string"); if (obj.meta !== undefined) { if (obj.meta === null || typeof obj.meta !== "object" || Array.isArray(obj.meta)) { throw new Error("Fixture.meta must be an object if present"); } const meta = obj.meta as Record; for (const [k, v] of Object.entries(meta)) { if (v !== null && typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") { throw new Error(`Fixture.meta.${k} must be a string, number, boolean, or null`); } } } } function assertStringRecord(val: unknown, label: string): asserts val is Record { if (val === null || typeof val !== "object" || Array.isArray(val)) { throw new Error(`${label} must be an object`); } for (const [k, v] of Object.entries(val as Record)) { if (typeof v !== "string") { throw new Error(`${label}.${k} must be a string`); } } } export function saveFixture(path: string, fx: HttpExchangeFixture): void { writeFileSync(path, serializeFixture(fx), "utf-8"); } export function loadFixture(path: string): HttpExchangeFixture { return parseFixture(readFileSync(path, "utf-8")); }