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