/** * cli:apply-form-directives — validate.ts */ import { ApplyFormDirectivesInputSchema, type ValidationResult } from './types.js' export function validate(raw: unknown): ValidationResult { const parsed = ApplyFormDirectivesInputSchema.safeParse(raw) if (parsed.success) { const errors: string[] = [] const warnings: string[] = [] const { fields, sections } = parsed.data // A form has at most ONE current-user FK — flag (don't fail) extras so the // judgment stays honest (a self-ref FK must never be the "Me" shortcut). const meFks = fields.filter(f => f.currentUserFk === true) if (meFks.length > 1) { warnings.push(`Multiple currentUserFk fields (${meFks.map(f => f.key).join(', ')}) — a form has exactly one signed-in-user FK.`) } if (sections !== undefined) { // ONE membership vocabulary per call: sections[] OR per-field section // strings, never both (they would race on fields..section). const perFieldSections = fields.filter(f => f.section !== undefined) if (perFieldSections.length > 0) { errors.push(`Do not mix sections[] with per-field section directives in one call (${perFieldSections.map(f => f.key).join(', ')}) — use sections[] alone.`) } const keys = sections.map(s => s.key) const dupKeys = keys.filter((k, i) => keys.indexOf(k) !== i) if (dupKeys.length > 0) { errors.push(`Duplicate section keys: ${[...new Set(dupKeys)].join(', ')}.`) } const memberOf = new Map() for (const s of sections) { for (const key of s.fields) { const already = memberOf.get(key) if (already !== undefined && already !== s.key) { errors.push(`Field "${key}" belongs to two sections (${already}, ${s.key}) — a field has exactly one section.`) } memberOf.set(key, s.key) } if (s.fields.length === 1) { warnings.push(`Section "${s.key}" holds a single field — a one-field card is noise; merge it or drop the section.`) } } if (sections.length > 4) { warnings.push(`${sections.length} sections — the rubric caps a form at 2-4 meaningful sections.`) } } return { valid: errors.length === 0, errors, warnings } } return { valid: false, errors: parsed.error.issues.map(i => `${i.path.join('.') || '(root)'}: ${i.message}`), warnings: [], } }