Skip to content

Data Validation

Runtime validation is one of the core features of farrow-schema, keeping your application type-safe at runtime.

🛡️ Basic Validation

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

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

// Validate data
const result = Validator.validate(User, {
  name: 'Alice',
  age: 25
})

// Use result.kind for type narrowing
if (result.kind === 'Ok') {
  console.log('Validation successful:', result.value)
  // result.value type is { name: string, age: number }
} else {
  console.log('Validation failed:', result.value.message)
  console.log('Error location:', result.value.path?.join('.'))
}

Validation Options

Strict Mode vs Lenient Mode

typescript
// Strict mode (default): No type conversion
const strictResult = Validator.validate(User, {
  name: 'Bob',
  age: '30'  // ❌ String fails in strict mode
})

// Lenient mode: Attempt type conversion
const lenientResult = Validator.validate(User, {
  name: 'Bob',
  age: '30'  // ✅ String will be converted to number
}, { strict: false })

Type conversion rules (lenient mode strict: false):

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

Create Dedicated Validators

When using in APIs, it's recommended to create dedicated validators:

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

// Create validator (strict mode by default)
const validateUser = createSchemaValidator(User)

// Or explicitly specify lenient mode
const validateUserLenient = createSchemaValidator(User, { strict: false })

// Use in 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 has passed type checking
  const user = await createUser(result.value)
  res.json(user)
})

Custom Validators

Using ValidatorType

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

// Email 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('Invalid email format')
    }

    return this.Ok(result.value)
  }
}

// Use
class User extends ObjectType {
  name = String
  email = EmailType  // Use custom validator
}

Parameterized Validators

typescript
// String length validator
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(`Length must be between ${min}-${max} characters`)
      }

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

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

Regular Expression Validators

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}$/)
}

Error Handling

Understanding Validation Results

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

// Recommended: Use kind field for type narrowing
if (result.kind === 'Err') {
  console.log('Error:', result.value.message)  // Error message
  console.log('Path:', result.value.path?.join('.'))  // Error path, e.g., "user.address.zipCode"
  return
}

// result.value is type-safe
console.log(result.value)

Practical Example: Complete Error Handling

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

  // Use kind for type narrowing
  if (result.kind === 'Err') {
    return Err(`Validation failed: ${result.value.message}`)
  }

  // Business logic validation
  if (result.value.age < 18) {
    return Err('User must be at least 18 years old')
  }

  return Ok(createUser(result.value))
}

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

This is a third-party Farrow documentation site | Built with ❤️ and TypeScript