CreateTableSheet.schema.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { z } from 'zod'
  2. import { NEW_NAMESPACE_MARKER } from './CreateTableSheet.constants'
  3. const getValidRegex = (type: 'namespace' | 'table') =>
  4. type === 'namespace'
  5. ? /^(?!aws)[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/
  6. : /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/
  7. const getErrorRegex = (type: 'namespace' | 'table') =>
  8. type === 'namespace'
  9. ? /^(?:(?<starts_with_reserved>aws.*)|(?<invalid_start>[^a-z0-9].*)|(?<invalid_char>.*[^a-z0-9_].*)|(?<invalid_end>.*[^a-z0-9]))$/
  10. : /^(?:(?<invalid_start>[^a-z0-9].*)|(?<invalid_char>.*[^a-z0-9_].*)|(?<invalid_end>.*[^a-z0-9]))$/
  11. const validateName = ({ name, type }: { name: string; type: 'namespace' | 'table' }) => {
  12. const validRe = getValidRegex(type)
  13. if (validRe.test(name)) return undefined
  14. const errorRe = getErrorRegex(type)
  15. const match = name.match(errorRe)?.groups || {}
  16. if (match.starts_with_reserved) return "Namespace must not start with 'aws'"
  17. if (match.invalid_start) return 'Name must begin with a lowercase letter or number'
  18. if (match.invalid_end) return 'Name must end with a lowercase letter or number'
  19. if (match.invalid_char) return 'Name may only contain lowercase letters, numbers, and underscores'
  20. return 'Invalid name'
  21. }
  22. export const createFormSchema = () =>
  23. z
  24. .object({
  25. namespace: z.string().min(1, 'Please select a namespace'),
  26. newNamespace: z.string().max(255, 'Name must be within 255 characters').optional(),
  27. name: z
  28. .string()
  29. .min(1, 'Provide a name for your table')
  30. .max(255, 'Name must be within 255 characters'),
  31. columns: z
  32. .object({
  33. name: z.string().min(1, 'Provide a name for your column'),
  34. type: z.string().min(1, 'Select a type for your column'),
  35. // For decimal type
  36. precision: z.number().optional(),
  37. scale: z.number().int().optional(),
  38. // For fixed type
  39. length: z.number().int().optional(),
  40. })
  41. .array()
  42. .default([]),
  43. })
  44. .superRefine((data, ctx) => {
  45. if (data.namespace === NEW_NAMESPACE_MARKER) {
  46. if (data.newNamespace) {
  47. const newNamespaceError = validateName({
  48. name: data.newNamespace,
  49. type: 'namespace',
  50. })
  51. if (newNamespaceError) {
  52. ctx.addIssue({
  53. code: z.ZodIssueCode.custom,
  54. message: newNamespaceError,
  55. path: ['newNamespace'],
  56. })
  57. }
  58. } else {
  59. ctx.addIssue({
  60. code: z.ZodIssueCode.custom,
  61. message: 'Provide a name for your new namespace',
  62. path: ['newNamespace'],
  63. })
  64. }
  65. }
  66. data.columns.forEach((column, index) => {
  67. if (column.type === 'decimal') {
  68. if (column.precision == null) {
  69. ctx.addIssue({
  70. code: z.ZodIssueCode.custom,
  71. message: 'Required',
  72. path: [`columns.${index}.precision`],
  73. })
  74. }
  75. if (column.scale == null) {
  76. ctx.addIssue({
  77. code: z.ZodIssueCode.custom,
  78. message: 'Required',
  79. path: [`columns.${index}.scale`],
  80. })
  81. }
  82. }
  83. if (column.type === 'fixed') {
  84. if (column.length == null) {
  85. ctx.addIssue({
  86. code: z.ZodIssueCode.custom,
  87. message: 'Required',
  88. path: [`columns.${index}.length`],
  89. })
  90. }
  91. }
  92. })
  93. if (data.name) {
  94. const newTableError = validateName({ name: data.name, type: 'table' })
  95. if (newTableError) {
  96. ctx.addIssue({
  97. code: z.ZodIssueCode.custom,
  98. message: newTableError,
  99. path: ['name'],
  100. })
  101. }
  102. }
  103. })