Skip to content

数据验证

运行时验证是 farrow-schema 的核心功能之一,让你的应用在运行时也能保持类型安全。

🛡️ 基础验证

typescript
import { Validator } from 'farrow-schema/validator'

class User extends ObjectType {
  name = String
  age = Number
}

// 验证数据
const result = Validator.validate(User, {
  name: 'Alice',
  age: 25
})

// 使用 result.kind 进行类型收窄
if (result.kind === 'Ok') {
  console.log('验证成功:', result.value)
  // result.value 的类型是 { name: string, age: number }
} else {
  console.log('验证失败:', result.value.message)
  console.log('错误位置:', result.value.path?.join('.'))
}

验证选项

严格模式 vs 宽松模式

typescript
// 严格模式(默认):不进行类型转换
const strictResult = Validator.validate(User, {
  name: 'Bob',
  age: '30'  // ❌ 字符串在严格模式下会失败
})

// 宽松模式:尝试类型转换
const lenientResult = Validator.validate(User, {
  name: 'Bob',
  age: '30'  // ✅ 字符串会被转换为数字
}, { strict: false })

类型转换规则(宽松模式 strict: false):

  • "25"25
  • "true"true
  • "2024-01-01"Date

创建专用验证器

在 API 中使用时,推荐创建专用验证器:

typescript
import { createSchemaValidator } from 'farrow-schema/validator'

// 创建验证器(默认严格模式)
const validateUser = createSchemaValidator(User)

// 或者显式指定宽松模式
const validateUserLenient = createSchemaValidator(User, { strict: false })

// 在 API 中使用
app.post('/users', (req, res) => {
  const result = validateUser(req.body)

  if (result.kind === 'Err') {
    return res.status(400).json({
      error: result.value.message,
      path: result.value.path
    })
  }

  // result.value 已通过类型检查
  const user = await createUser(result.value)
  res.json(user)
})

自定义验证器

使用 ValidatorType

typescript
import { ValidatorType } from 'farrow-schema/validator'

// 邮箱验证器
class EmailType extends ValidatorType<string> {
  validate(input: unknown) {
    const result = Validator.validate(String, input)
    if (result.kind === 'Err') return result

    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    if (!emailRegex.test(result.value)) {
      return this.Err('邮箱格式不正确')
    }

    return this.Ok(result.value)
  }
}

// 使用
class User extends ObjectType {
  name = String
  email = EmailType  // 使用自定义验证器
}

参数化验证器

typescript
// 字符串长度验证器
const StringLength = (min: number, max: number) => {
  return class StringLength extends ValidatorType<string> {
    validate(input: unknown) {
      const result = Validator.validate(String, input)
      if (result.kind === 'Err') return result

      if (result.value.length < min || result.value.length > max) {
        return this.Err(`长度必须在 ${min}-${max} 个字符之间`)
      }

      return this.Ok(result.value)
    }
  }
}

class Article extends ObjectType {
  title = StringLength(5, 100)
  content = StringLength(50, 5000)
}

正则表达式验证器

typescript
import { RegExp } from 'farrow-schema/validator'

class User extends ObjectType {
  username = RegExp(/^[a-zA-Z0-9_]{3,16}$/)
  phone = RegExp(/^1[3-9]\d{9}$/)
}

错误处理

理解验证结果

typescript
const result = Validator.validate(User, data)

// 推荐:使用 kind 字段进行类型收窄
if (result.kind === 'Err') {
  console.log('Error:', result.value.message)  // 错误消息
  console.log('Path:', result.value.path?.join('.'))  // 错误路径,如 "user.address.zipCode"
  return
}

// result.value 类型安全
console.log(result.value)

// 也可以使用 isOk/isErr(向后兼容)
if (result.isErr) {
  // 处理错误
}

实战示例:完整错误处理

typescript
function validateAndCreate(data: unknown): Result<User, string> {
  const result = Validator.validate(CreateUserInput, data)

  // 利用 kind 进行类型收窄
  if (result.kind === 'Err') {
    return Err(`Validation failed: ${result.value.message}`)
  }

  // 业务逻辑验证
  if (result.value.age < 18) {
    return Err('User must be at least 18 years old')
  }

  return Ok(createUser(result.value))
}

// 统一处理
const result = validateAndCreate(req.body)
if (result.kind === 'Err') {
  return res.status(400).json({ error: result.value })
}
res.json(result.value)

这是一个第三方 Farrow 文档站 | 用 ❤️ 和 TypeScript 构建