formatValidationError.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import type { TFunction } from 'i18next';
  2. type IssueLike = { path: PropertyKey[]; message: string };
  3. interface ClientLike {
  4. email?: unknown;
  5. }
  6. /**
  7. * Turns one Zod issue from the inbound-form schema into a human-readable line.
  8. * The schema validates the whole form at once, so a bad client field surfaces
  9. * as `settings.clients.<index>.<field>` — useless on its own when an inbound
  10. * holds hundreds of clients. We resolve that index back to the client's email
  11. * so the operator can find the offending entry. The reason is translated when
  12. * it is a custom message key; Zod defaults like "Invalid input" pass through.
  13. */
  14. export function formatInboundIssue(issue: IssueLike, values: unknown, t: TFunction): string {
  15. const path = Array.isArray(issue?.path) ? issue.path : [];
  16. const reason = t(issue?.message, { defaultValue: issue?.message });
  17. if (path[0] === 'streamSettings' && path[1] === 'tlsSettings' && path[2] === 'certificates') {
  18. return typeof path[3] === 'number'
  19. ? t('pages.inbounds.toasts.invalidCertificate', { index: path[3] + 1, reason })
  20. : reason;
  21. }
  22. if (path[0] === 'settings' && path[1] === 'clients' && typeof path[2] === 'number') {
  23. const index = path[2];
  24. const clients = (values as { settings?: { clients?: ClientLike[] } })?.settings?.clients;
  25. const client = Array.isArray(clients) ? clients[index] : undefined;
  26. const email = typeof client?.email === 'string' && client.email !== '' ? client.email : '';
  27. const who = email ? `"${email}"` : `#${index}`;
  28. const field = path.slice(3).map(String).join('.') || t('clients');
  29. return t('pages.inbounds.toasts.invalidClientField', { client: who, field, reason });
  30. }
  31. const field = path.map(String).join('.') || 'value';
  32. return t('pages.inbounds.toasts.invalidField', { field, reason });
  33. }
  34. /**
  35. * Builds the single-line toast for a failed inbound save: the first issue,
  36. * fully described, plus a "(+N more)" tail when several fields failed.
  37. */
  38. export function formatInboundValidation(
  39. issues: IssueLike[],
  40. values: unknown,
  41. t: TFunction,
  42. ): string {
  43. const first = formatInboundIssue(issues[0], values, t);
  44. if (issues.length <= 1) return first;
  45. return t('pages.inbounds.toasts.moreIssues', { message: first, count: issues.length - 1 });
  46. }