{"version":3,"file":"validate.mjs","names":["SearchConstants","MULTI_VALUE_OPERATORS","NO_VALUE_OPERATORS","OPERATORS_BY_TYPE","SINGLE_VALUE_OPERATORS","checkValueFormat","AiCondition","AiSearchSchemaContext","QueryIntent","ValidateIntentOptions","explainUnknownField","fieldPath","isLogicalOp","op","validateCondition","cond","ctx","options","errors","where","field","trim","push","filterFields","find","f","path","explained","allowed","type","default","includes","operator","join","has","value","undefined","formatError","values","length","IN_CLAUSE_MAX_ELEMENTS","v","validateIntent","intent","columnNames","Set","columns","map","c","name","filters","conditions","forEach","i","groups","group","gi","sorts","sort","col","entityName","orderable","grouping","groupBy","aggregates","agg","aggregatable","key","const","pageSize","Number","isInteger","MAX_PAGE_SIZE"],"sources":["../../src/searchIntent/validate.ts"],"sourcesContent":["import { SearchConstants } from '@univerus/udp-stencil-component-library';\nimport {\n  MULTI_VALUE_OPERATORS,\n  NO_VALUE_OPERATORS,\n  OPERATORS_BY_TYPE,\n  SINGLE_VALUE_OPERATORS,\n  checkValueFormat\n} from './operators';\nimport type { AiCondition, AiSearchSchemaContext, QueryIntent } from './types';\n\nexport interface ValidateIntentOptions {\n  /**\n   * Host hook to explain an unknown filter field path more precisely than the\n   * generic message — e.g. Data Builder resolves navigation chains to tell the\n   * model the valid leaf fields on the target entity so a repair round can\n   * succeed in one shot. Return undefined to fall back to the generic message.\n   */\n  explainUnknownField?: (fieldPath: string) => string | undefined;\n}\n\nconst isLogicalOp = (op: unknown): op is 'and' | 'or' =>\n  op === 'and' || op === 'or';\n\nconst validateCondition = (\n  cond: AiCondition,\n  ctx: AiSearchSchemaContext,\n  options: ValidateIntentOptions | undefined,\n  errors: string[],\n  where: string\n): void => {\n  // The tool schema requires `field`, but tool arguments are model output:\n  // handle a missing one here rather than handing undefined to the host's\n  // explainUnknownField hook, which reasonably assumes a path string.\n  if (typeof cond?.field !== 'string' || cond.field.trim() === '') {\n    errors.push(\n      `${where}: \"field\" is required — name the field with a path from the filterable fields list exactly as written.`\n    );\n    return;\n  }\n\n  const field = ctx.filterFields.find((f) => f.path === cond.field);\n  if (!field) {\n    const explained = options?.explainUnknownField?.(cond.field);\n    errors.push(\n      `${where}: ${explained ?? `\"${cond.field}\" is not a filterable field. Use a path from the filterable fields list exactly as written.`}`\n    );\n    return;\n  }\n\n  const allowed = OPERATORS_BY_TYPE[field.type] ?? OPERATORS_BY_TYPE.default;\n  if (!allowed.includes(cond.operator)) {\n    errors.push(\n      `${where}: operator \"${cond.operator}\" is not allowed for ${field.type} field \"${cond.field}\". Allowed: ${allowed.join(', ')}.`\n    );\n    return;\n  }\n\n  if (NO_VALUE_OPERATORS.has(cond.operator)) return;\n\n  if (SINGLE_VALUE_OPERATORS.has(cond.operator)) {\n    if (cond.value === undefined || cond.value === null || cond.value === '') {\n      errors.push(\n        `${where}: operator \"${cond.operator}\" on \"${cond.field}\" requires a value.`\n      );\n      return;\n    }\n    // contains/starts_with do substring matching — format checks would reject partial values\n    if (cond.operator !== 'contains' && cond.operator !== 'starts_with') {\n      const formatError = checkValueFormat(field.type, cond.value);\n      if (formatError) errors.push(`${where}: \"${cond.field}\" ${formatError}.`);\n    }\n    return;\n  }\n\n  if (MULTI_VALUE_OPERATORS.has(cond.operator)) {\n    const values = cond.values ?? [];\n    if (cond.operator === 'between' && values.length !== 2) {\n      errors.push(\n        `${where}: \"between\" on \"${cond.field}\" requires exactly 2 values (low, high), got ${values.length}.`\n      );\n      return;\n    }\n    if (\n      cond.operator !== 'between' &&\n      (values.length < 1 ||\n        values.length > SearchConstants.IN_CLAUSE_MAX_ELEMENTS)\n    ) {\n      errors.push(\n        `${where}: \"${cond.operator}\" on \"${cond.field}\" requires 1-${SearchConstants.IN_CLAUSE_MAX_ELEMENTS} values, got ${values.length}.`\n      );\n      return;\n    }\n    for (const v of values) {\n      const formatError = checkValueFormat(field.type, v);\n      if (formatError) errors.push(`${where}: \"${cond.field}\" ${formatError}.`);\n    }\n  }\n};\n\n/**\n * Validates an intent against the schema context, enforcing the same gates\n * the manual UIs apply (searchable / orderable / aggregatable / groupBy\n * required for aggregates). Returns [] when the intent is safe to compile.\n * Error strings are written for the model — precise enough that a single\n * repair round can fix the intent.\n */\nexport const validateIntent = (\n  intent: QueryIntent,\n  ctx: AiSearchSchemaContext,\n  options?: ValidateIntentOptions\n): string[] => {\n  const errors: string[] = [];\n  const columnNames = new Set(ctx.columns.map((c) => c.name));\n\n  if (intent.filters) {\n    // `op` decides how the whole filter state joins; downstream (compile, diff)\n    // it is read as given, so a missing one is an error, not a silent \"and\".\n    if (!isLogicalOp(intent.filters.op)) {\n      errors.push('filters.op is required and must be \"and\" or \"or\".');\n    }\n    intent.filters.conditions?.forEach((cond, i) =>\n      validateCondition(cond, ctx, options, errors, `filters.conditions[${i}]`)\n    );\n    intent.filters.groups?.forEach((group, gi) => {\n      if (!isLogicalOp(group?.op)) {\n        errors.push(`filters.groups[${gi}].op must be \"and\" or \"or\".`);\n      }\n      if (!group.conditions?.length) {\n        errors.push(\n          `filters.groups[${gi}]: a group must contain at least one condition.`\n        );\n        return;\n      }\n      group.conditions.forEach((cond, i) =>\n        validateCondition(\n          cond,\n          ctx,\n          options,\n          errors,\n          `filters.groups[${gi}].conditions[${i}]`\n        )\n      );\n    });\n  }\n\n  if (intent.sorts) {\n    intent.sorts.forEach((sort, i) => {\n      const col = ctx.columns.find((c) => c.name === sort.field);\n      if (!col) {\n        errors.push(\n          `sorts[${i}]: \"${sort.field}\" is not a column on ${ctx.entityName}.`\n        );\n      } else if (!col.orderable) {\n        errors.push(`sorts[${i}]: column \"${sort.field}\" is not sortable.`);\n      }\n    });\n  }\n\n  if (intent.grouping) {\n    const { groupBy = [], aggregates = [] } = intent.grouping;\n    groupBy.forEach((name, i) => {\n      if (!columnNames.has(name))\n        errors.push(\n          `grouping.groupBy[${i}]: \"${name}\" is not a column on ${ctx.entityName}.`\n        );\n    });\n    aggregates.forEach((agg, i) => {\n      const col = ctx.columns.find((c) => c.name === agg.field);\n      if (!col) {\n        errors.push(\n          `grouping.aggregates[${i}]: \"${agg.field}\" is not a column on ${ctx.entityName}.`\n        );\n      } else if (!col.aggregatable) {\n        errors.push(\n          `grouping.aggregates[${i}]: column \"${agg.field}\" does not support aggregation.`\n        );\n      }\n    });\n    if (aggregates.length > 0 && groupBy.length === 0) {\n      errors.push('grouping: aggregates require at least one groupBy column.');\n    }\n  }\n\n  if (intent.columns) {\n    for (const key of ['showOnly', 'show', 'hide'] as const) {\n      intent.columns[key]?.forEach((name, i) => {\n        if (!columnNames.has(name))\n          errors.push(\n            `columns.${key}[${i}]: \"${name}\" is not a column on ${ctx.entityName}.`\n          );\n      });\n    }\n  }\n\n  if (intent.pageSize !== undefined && intent.pageSize !== null) {\n    if (\n      !Number.isInteger(intent.pageSize) ||\n      intent.pageSize < 1 ||\n      intent.pageSize > SearchConstants.MAX_PAGE_SIZE\n    ) {\n      errors.push(\n        `pageSize must be an integer between 1 and ${SearchConstants.MAX_PAGE_SIZE}.`\n      );\n    }\n  }\n\n  return errors;\n};\n"],"mappings":"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"}