import type { ApiDocument, JsonObject, JsonValue, LocalWorkspace, } from "./types"; import { normalizeFilename } from "./parse"; function parentDirectory(filename: string): string { const index = filename.lastIndexOf("/"); return index < 0 ? "" : filename.slice(0, index + 1); } function normalizeRelative(base: string, relative: string): string { const segments = `${parentDirectory(base)}${relative}`.split("/"); const output: string[] = []; for (const segment of segments) { if (!segment || segment === ".") continue; if (segment === "..") { if (!output.length) throw new TypeError("Reference escapes the local workspace"); output.pop(); } else output.push(segment); } return normalizeFilename(output.join("/")); } function pointerValue(value: JsonValue, fragment: string): JsonValue { if (!fragment || fragment === "#") return value; if (!fragment.startsWith("#/")) throw new SyntaxError( `Only JSON Pointer fragments are supported: ${fragment}`, ); let current: JsonValue = value; for (const raw of fragment.slice(2).split("/")) { const token = decodeURIComponent(raw) .replace(/~1/gu, "/") .replace(/~0/gu, "~"); if (Array.isArray(current)) { if (!/^(?:0|[1-9]\d*)$/u.test(token) || Number(token) >= current.length) throw new ReferenceError(`Array reference token not found: ${token}`); current = current[Number(token)]!; } else if ( current && typeof current === "object" && Object.hasOwn(current, token) ) current = current[token]!; else throw new ReferenceError(`Reference token not found: ${token}`); } return current; } export function createWorkspace( entry: ApiDocument, supporting: readonly ApiDocument[] = [], ): LocalWorkspace { const documents = new Map(); for (const document of [entry, ...supporting]) { if (documents.has(document.filename)) throw new TypeError(`Duplicate local filename: ${document.filename}`); documents.set(document.filename, document); } return { entry: entry.filename, documents }; } export function resolveLocalReference( workspace: LocalWorkspace, reference: string, from = workspace.entry, ): { document: ApiDocument; value: JsonValue; key: string } { if ( /^[a-z][a-z0-9+.-]*:/iu.test(reference) || reference.startsWith("//") || reference.startsWith("/") ) throw new TypeError( `Remote or absolute references are disabled: ${reference}`, ); const hash = reference.indexOf("#"); const filePart = hash < 0 ? reference : reference.slice(0, hash); const fragment = hash < 0 ? "" : reference.slice(hash); const filename = filePart ? normalizeRelative(from, filePart) : from; const document = workspace.documents.get(filename); if (!document) throw new ReferenceError( `Local reference file was not supplied: ${filename}`, ); return { document, value: pointerValue(document.value, fragment), key: `${filename}${fragment}`, }; } function asObject(value: JsonValue | undefined): JsonObject | undefined { return value && typeof value === "object" && !Array.isArray(value) ? value : undefined; } export interface SampleResult { value: JsonValue; notices: string[]; } export function generateSchemaSample( workspace: LocalWorkspace, schema: JsonValue, from = workspace.entry, ): SampleResult { const notices: string[] = []; const active = new Set(); let nodes = 0; const build = ( value: JsonValue, filename: string, depth: number, ): JsonValue => { nodes += 1; if (nodes > 2_000 || depth > 20) { notices.push( "Sample generation reached its bounded depth or node limit.", ); return null; } const object = asObject(value); if (!object) return value; if (typeof object.$ref === "string") { const resolved = resolveLocalReference(workspace, object.$ref, filename); if (active.has(resolved.key)) { notices.push(`Reference cycle stopped at ${resolved.key}.`); return { $cycle: resolved.key }; } active.add(resolved.key); try { return build(resolved.value, resolved.document.filename, depth + 1); } finally { active.delete(resolved.key); } } for (const key of ["example", "default", "const"] as const) if (object[key] !== undefined) return object[key]!; if (Array.isArray(object.enum) && object.enum.length) return object.enum[0]!; if (Array.isArray(object.allOf)) { const merged: JsonObject = Object.create(null) as JsonObject; for (const child of object.allOf) { const sample = build(child, filename, depth + 1); if (asObject(sample)) Object.assign(merged, sample); else notices.push("A non-object allOf sample could not be merged."); } return merged; } for (const key of ["oneOf", "anyOf"] as const) if (Array.isArray(object[key]) && object[key]!.length) { notices.push(`${key} uses the first alternative.`); return build(object[key]![0]!, filename, depth + 1); } const type = typeof object.type === "string" ? object.type : object.properties ? "object" : object.items ? "array" : undefined; if (type === "object") { const output: JsonObject = Object.create(null) as JsonObject; const properties = asObject(object.properties) ?? {}; for (const [key, child] of Object.entries(properties).slice(0, 100)) output[key] = build(child, filename, depth + 1); return output; } if (type === "array") return object.items ? [build(object.items, filename, depth + 1)] : []; if (type === "integer" || type === "number") return typeof object.minimum === "number" ? object.minimum : 0; if (type === "boolean") return true; if (type === "null") return null; const format = typeof object.format === "string" ? object.format : ""; return ( ( { date: "2026-09-01", "date-time": "2026-09-01T12:00:00Z", email: "user@example.test", uuid: "00000000-0000-4000-8000-000000000000", uri: "https://example.test/resource", hostname: "example.test", ipv4: "192.0.2.1", ipv6: "2001:db8::1", binary: "", } as Record )[format] ?? (typeof object.pattern === "string" ? `` : "string") ); }; return { value: build(schema, from, 0), notices: [...new Set(notices)] }; }