Skip to content

Commit f36cbb1

Browse files
committed
fix: optional type and distinguish error
1 parent 4e69970 commit f36cbb1

3 files changed

Lines changed: 66 additions & 17 deletions

File tree

packages/core/src/collection.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,12 @@ export function getAllCollectionEndpoints<
762762
]
763763
}
764764
case ApiDefaultMethod.FIND_MANY: {
765-
const response = fieldsToZodObject(fields)
765+
const body = fieldsToZodObject(fields)
766+
const response = z.object({
767+
data: z.array(body),
768+
total: z.number(),
769+
page: z.number(),
770+
})
766771

767772
const schema = {
768773
path: `/api/${collection.slug}/${method}`,

packages/core/src/field.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type TableRelationalConfig,
88
} from 'drizzle-orm'
99
import type { Simplify } from 'type-fest'
10-
import type { ZodObject } from 'zod'
10+
import type { ZodObject, ZodOptional } from 'zod'
1111
import z from 'zod'
1212

1313
import {
@@ -444,21 +444,30 @@ export class FieldBuilder<
444444
}
445445
}
446446

447+
type CastOptionalFieldToZodSchema<
448+
TField extends Field<any>,
449+
TSchema extends z.ZodTypeAny,
450+
> = TField['_'] extends { source: 'column' }
451+
? TField['_']['column']['notNull'] extends true
452+
? ZodOptional<TSchema>
453+
: TSchema
454+
: TSchema
455+
447456
type FieldToZodScheama<TField extends Field<any>> =
448457
TField extends FieldColumnStringCollectionOptions<any>[keyof FieldColumnStringCollectionOptions<any>]
449-
? z.ZodString
458+
? CastOptionalFieldToZodSchema<TField, z.ZodString>
450459
: TField extends FieldColumnStringArrayCollectionOptions<any>[keyof FieldColumnStringArrayCollectionOptions<any>]
451-
? z.ZodArray<z.ZodString>
460+
? CastOptionalFieldToZodSchema<TField, z.ZodArray<z.ZodString>>
452461
: TField extends FieldColumnNumberCollectionOptions<any>[keyof FieldColumnNumberCollectionOptions<any>]
453-
? z.ZodNumber
462+
? CastOptionalFieldToZodSchema<TField, z.ZodNumber>
454463
: TField extends FieldColumnNumberArrayCollectionOptions<any>[keyof FieldColumnNumberArrayCollectionOptions<any>]
455-
? z.ZodArray<z.ZodNumber>
464+
? CastOptionalFieldToZodSchema<TField, z.ZodArray<z.ZodNumber>>
456465
: TField extends FieldColumnBooleanCollectionOptions[keyof FieldColumnBooleanCollectionOptions]
457-
? z.ZodBoolean
466+
? CastOptionalFieldToZodSchema<TField, z.ZodBoolean>
458467
: TField extends FieldColumnBooleanArrayCollectionOptions[keyof FieldColumnBooleanArrayCollectionOptions]
459-
? z.ZodArray<z.ZodBoolean>
468+
? CastOptionalFieldToZodSchema<TField, z.ZodArray<z.ZodBoolean>>
460469
: TField extends FieldColumnDateCollectionOptions[keyof FieldColumnDateCollectionOptions]
461-
? z.ZodDate
470+
? CastOptionalFieldToZodSchema<TField, z.ZodDate>
462471
: never
463472
// TODO: Relation input
464473
// TODO: Optioanl and default values
@@ -473,26 +482,47 @@ export function fieldToZodScheama<TField extends Field<any>>(
473482
case 'selectText':
474483
case 'time':
475484
case 'media':
485+
if (!field._.column.notNull) {
486+
return z.string().optional() as FieldToZodScheama<TField>
487+
}
476488
return z.string() as FieldToZodScheama<TField>
477489
// string[] input
478490
case 'comboboxText':
491+
if (!field._.column.notNull) {
492+
return z.array(z.string()) as FieldToZodScheama<TField>
493+
}
479494
return z.array(z.string()) as FieldToZodScheama<TField>
480495
// number input
481496
case 'number':
482497
case 'selectNumber':
498+
if (!field._.column.notNull) {
499+
return z.number().optional() as FieldToZodScheama<TField>
500+
}
483501
return z.number() as FieldToZodScheama<TField>
484502
// number[] input
485503
case 'comboboxNumber':
504+
if (!field._.column.notNull) {
505+
return z.array(z.number()).optional() as FieldToZodScheama<TField>
506+
}
486507
return z.array(z.number()) as FieldToZodScheama<TField>
487508
// boolean input
488509
case 'checkbox':
489510
case 'switch':
511+
if (!field._.column.notNull) {
512+
return z.boolean().optional() as FieldToZodScheama<TField>
513+
}
490514
return z.boolean() as FieldToZodScheama<TField>
491515
// boolean[] input
492516
case 'comboboxBoolean':
517+
if (!field._.column.notNull) {
518+
return z.array(z.boolean()).optional() as FieldToZodScheama<TField>
519+
}
493520
return z.array(z.boolean()) as FieldToZodScheama<TField>
494521
// date input
495522
case 'date':
523+
if (!field._.column.notNull) {
524+
return z.date().optional() as FieldToZodScheama<TField>
525+
}
496526
return z.date() as FieldToZodScheama<TField>
497527
// TODO: relation input
498528
case 'connect':
@@ -515,6 +545,7 @@ export function fieldsToZodObject<TFields extends Fields<any>>(
515545
): FieldsToZodObject<TFields> {
516546
const zodObject = Object.entries(fields).reduce(
517547
(acc, [key, field]) => {
548+
console.log(key, field)
518549
acc[key] = fieldToZodScheama(field)
519550
return acc
520551
},

packages/core/src/utils.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,32 +121,45 @@ export async function validateRequestBody<
121121
TApiRouteSchema extends ApiRouteSchema = any,
122122
TContext extends Record<string, unknown> = Record<string, unknown>,
123123
>(schema: TApiRouteSchema, payload: ApiRouteHandlerPayloadWithContext<TApiRouteSchema, TContext>) {
124-
const zodErrors: ZodError[] = []
124+
let zodErrors: Partial<Record<'query' | 'pathParams' | 'headers' | 'body', ZodError>> | undefined
125+
125126
if (schema.query) {
126127
const err = await schema.query.safeParseAsync((payload as any).query)
127128
if (!err.success) {
128-
zodErrors.push(err.error)
129+
zodErrors = {
130+
...zodErrors,
131+
query: err.error,
132+
}
129133
}
130134
}
131135

132136
if (schema.pathParams) {
133137
const err = await schema.pathParams.safeParseAsync((payload as any).pathParams)
134138
if (!err.success) {
135-
zodErrors.push(err.error)
139+
zodErrors = {
140+
...zodErrors,
141+
pathParams: err.error,
142+
}
136143
}
137144
}
138145

139146
if (schema.headers) {
140147
const err = await schema.headers.safeParseAsync(payload.headers)
141148
if (!err.success) {
142-
zodErrors.push(err.error)
149+
zodErrors = {
150+
...zodErrors,
151+
headers: err.error,
152+
}
143153
}
144154
}
145155

146156
if (schema.method !== 'GET' && schema.body) {
147157
const err = await schema.body.safeParseAsync((payload as any).body)
148158
if (!err.success) {
149-
zodErrors.push(err.error)
159+
zodErrors = {
160+
...zodErrors,
161+
body: err.error,
162+
}
150163
}
151164
}
152165

@@ -177,12 +190,12 @@ export function withValidator<
177190
): (payload: ApiRouteHandlerPayloadWithContext<TApiRouteSchema, TContext>) => MaybePromise<any> {
178191
return async (payload: ApiRouteHandlerPayloadWithContext<TApiRouteSchema, TContext>) => {
179192
const zodErrors = await validateRequestBody(schema, payload)
180-
if (zodErrors.length > 0) {
193+
if (zodErrors) {
181194
return {
182195
status: 400,
183196
body: {
184197
error: 'Validation failed',
185-
details: zodErrors.map((e) => e.message),
198+
details: zodErrors,
186199
},
187200
}
188201
}
@@ -195,7 +208,7 @@ export function withValidator<
195208
status: 500,
196209
body: {
197210
error: 'Response validation failed',
198-
details: validationError.errors.map((e) => e.message),
211+
details: validationError.issues,
199212
},
200213
}
201214
}

0 commit comments

Comments
 (0)