import type { MockContext } from './respond.ts' /** * Replace `{{ path }}` placeholders inside a JSON value. * {{params.id}} {{query.q}} {{headers.x-foo}} * {{faker.person.fullName}} {{faker.number.int}} (zero-arg faker methods) * If a string is exactly one placeholder, the resolved (typed) value is used — * so {{faker.number.int}} yields a number, not "123". */ const RE = /\{\{\s*([\w.$-]+)\s*\}\}/g function resolve(path: string, ctx: MockContext): unknown { const parts = path.split('.') let cur: any = parts[0] === 'faker' ? ctx.faker : parts[0] === 'params' ? ctx.params : parts[0] === 'query' ? ctx.query : parts[0] === 'headers' ? ctx.headers : undefined if (cur === undefined) return undefined for (let i = 1; i < parts.length; i++) { cur = cur?.[parts[i]] if (cur === undefined) return undefined } return typeof cur === 'function' ? cur.call(parts[0] === 'faker' ? undefined : cur) : cur } export function hasTemplate(v: unknown): boolean { if (typeof v === 'string') return RE.test(v) if (Array.isArray(v)) return v.some(hasTemplate) if (v && typeof v === 'object') return Object.values(v).some(hasTemplate) return false } export function applyTemplate(value: unknown, ctx: MockContext): unknown { if (typeof value === 'string') { const whole = value.match(/^\{\{\s*([\w.$-]+)\s*\}\}$/) if (whole) { const r = resolve(whole[1], ctx) return r === undefined ? value : r } return value.replace(RE, (m, p) => { const r = resolve(p, ctx) return r === undefined ? m : String(r) }) } if (Array.isArray(value)) return value.map((v) => applyTemplate(v, ctx)) if (value && typeof value === 'object') { const out: Record = {} for (const [k, v] of Object.entries(value)) out[k] = applyTemplate(v, ctx) return out } return value }