Browse Source

fix: use Zod v4 compatible schema definition without .omit() on refined schemas

Zod v4 does not support .omit() on schemas containing .refine(). Restructured
the shared schema to define server and client schemas independently while
sharing field definitions to avoid duplication.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat/registration-page
npmrun 2 weeks ago
parent
commit
882e40baa1
  1. 80
      packages/shared/__tests__/auth-schema.test.ts
  2. 39
      packages/shared/auth-schema.ts

80
packages/shared/__tests__/auth-schema.test.ts

@ -0,0 +1,80 @@
import { describe, it, expect } from 'vitest'
import { registerSchema, clientRegisterSchema } from '../auth-schema'
describe('registerSchema', () => {
const validBody = {
username: 'testuser',
password: 'password123',
confirmPassword: 'password123',
captchaToken: 'token-abc',
captchaText: 'abcde',
}
it('passes with valid input', () => {
expect(registerSchema.safeParse(validBody).success).toBe(true)
})
it('fails on short username', () => {
const r = registerSchema.safeParse({ ...validBody, username: 'ab' })
expect(r.success).toBe(false)
if (!r.success) {
expect(r.error.issues[0].path).toContain('username')
}
})
it('fails on short password', () => {
const r = registerSchema.safeParse({ ...validBody, password: '1234567' })
expect(r.success).toBe(false)
if (!r.success) {
expect(r.error.issues[0].path).toContain('password')
}
})
it('fails on password mismatch', () => {
const r = registerSchema.safeParse({
...validBody,
confirmPassword: 'different',
})
expect(r.success).toBe(false)
if (!r.success) {
expect(r.error.issues[0].path).toContain('confirmPassword')
}
})
it('fails on empty captchaText', () => {
const r = registerSchema.safeParse({ ...validBody, captchaText: '' })
expect(r.success).toBe(false)
if (!r.success) {
expect(r.error.issues[0].path).toContain('captchaText')
}
})
it('fails on empty captchaToken', () => {
const r = registerSchema.safeParse({ ...validBody, captchaToken: '' })
expect(r.success).toBe(false)
if (!r.success) {
expect(r.error.issues[0].path).toContain('captchaToken')
}
})
})
describe('clientRegisterSchema', () => {
it('still requires captchaText field', () => {
const r = clientRegisterSchema.safeParse({
username: 'testuser',
password: 'password123',
confirmPassword: 'password123',
})
expect(r.success).toBe(false)
})
it('validates without captchaToken field', () => {
const r = clientRegisterSchema.safeParse({
username: 'testuser',
password: 'password123',
confirmPassword: 'password123',
captchaText: 'abcde',
})
expect(r.success).toBe(true)
})
})

39
packages/shared/auth-schema.ts

@ -1,23 +1,38 @@
import { z } from 'zod' import { z } from 'zod'
const usernameField = z.string().trim().min(3, '用户名至少需要3个字符').max(30, '用户名最多30个字符')
const passwordField = z.string().min(8, '密码至少需要8个字符')
const confirmPasswordField = z.string()
const captchaTextField = z.string().min(1, '验证码不能为空')
const captchaTokenField = z.string().min(1, '验证码令牌不能为空')
const passwordRefine = (data: { password: string; confirmPassword: string }) =>
data.password === data.confirmPassword
export const registerSchema = z export const registerSchema = z
.object({ .object({
username: z username: usernameField,
.string() password: passwordField,
.trim() confirmPassword: confirmPasswordField,
.min(3, '用户名至少需要3个字符') captchaToken: captchaTokenField,
.max(30, '用户名最多30个字符'), captchaText: captchaTextField,
password: z.string().min(8, '密码至少需要8个字符'),
confirmPassword: z.string(),
captchaToken: z.string().min(1, '验证码令牌不能为空'),
captchaText: z.string().min(1, '验证码不能为空'),
}) })
.refine((data) => data.password === data.confirmPassword, { .refine(passwordRefine, {
message: '两次输入的密码不一致', message: '两次输入的密码不一致',
path: ['confirmPassword'], path: ['confirmPassword'],
}) })
/** Client-side schema: omits captchaToken (managed by page shell) */ /** Client-side schema: excludes captchaToken (managed by page shell) */
export const clientRegisterSchema = registerSchema.omit({ captchaToken: true }) export const clientRegisterSchema = z
.object({
username: usernameField,
password: passwordField,
confirmPassword: confirmPasswordField,
captchaText: captchaTextField,
})
.refine(passwordRefine, {
message: '两次输入的密码不一致',
path: ['confirmPassword'],
})
export type RegisterInput = z.infer<typeof clientRegisterSchema> export type RegisterInput = z.infer<typeof clientRegisterSchema>

Loading…
Cancel
Save