🎯 Custom Error Messages

Provide user-friendly, context-specific validation feedback

New in v2.2.x

πŸ“ Syntax

Basic Format

"type --> Your custom error message"

The --> operator separates the type definition from your custom error message.

Key Points:

  • Spaces around --> are optional
  • Works with all type definitions, constraints, and modifiers
  • TypeScript inference correctly strips the message
  • Minimal performance impact
All these formats work:
// With spaces
"string --> Please provide your name"

// No space before
"string--> Please provide your name"

// No spaces at all
"string-->Please provide your name"

πŸš€ Basic Usage

Simple Type Validation

import { Interface } from "fortify-schema";

const schema = Interface({
  name: "string --> Please provide your name",
  age: "number --> Age must be a valid number",
  email: "email --> Please enter a valid email address",
});

const result = schema.safeParse({
  name: 123, // Wrong type
});

console.log(result.errors[0].message);
// Output: "Validation failed: Please provide your name in field \"name\""

With Constraints

const ProductSchema = Interface({
  price: "number(0.01,) --> Price must be greater than 0",
  quantity: "int(1,1000) --> Quantity must be between 1 and 1000",
  name: "string(3,100) --> Product name must be 3-100 characters",
});

With Union Types

const StatusSchema = Interface({
  status: "pending|approved|rejected --> Status must be pending, approved, or rejected",
  role: "admin|user|guest --> Invalid role selected",
});

πŸ”₯ Advanced Examples

Required Fields

Combine with ! modifier

email: "email! --> Email is required"

Optional Fields

Combine with ? modifier

phone: "string? --> Phone must be valid if provided"

Array Validation

Works with arrays too

tags: "string[] --> Must be an array of strings"

Complex Constraints

Multiple constraints

username: "string(3,20)! --> Username must be 3-20 chars"

Nested Objects

const ProfileSchema = Interface({
  user: {
    name: "string --> Name is required",
    email: "email --> Please provide a valid email",
    settings: {
      theme: "light|dark --> Theme must be light or dark",
      notifications: "boolean --> Must be enabled or disabled",
    },
  },
});

✨ Best Practices

1. Be Clear and Actionable

Bad
name: "string --> Invalid"
Good
name: "string --> Please provide your full name"

2. Include Constraint Details

Bad
age: "number(18,65) --> Invalid age"
Good
age: "number(18,65) --> Age must be between 18 and 65"

3. Use Consistent Tone

const schema = Interface({
  email: "email --> Please enter a valid email address",
  phone: "string --> Please provide your phone number",
  address: "string --> Please enter your full address",
});

4. Keep Messages Concise

πŸ’‘ Tip: Aim for messages under 100 characters. Users should understand the issue at a glance.

5. Consider Internationalization

// Use error codes for multi-language support
const schema = Interface({
  email: "email --> ERR_INVALID_EMAIL",
});

// Map codes to localized messages
const messages = {
  en: { ERR_INVALID_EMAIL: "Please enter a valid email" },
  es: { ERR_INVALID_EMAIL: "Ingrese un correo vΓ‘lido" },
  fr: { ERR_INVALID_EMAIL: "Entrez un e-mail valide" },
};

🎯 Type Inference

TypeScript correctly infers types even with custom error messages:

const schema = Interface({
  status: "pending|approved|rejected --> Invalid status",
  count: "number --> Count must be a number",
});

// TypeScript knows:
// status: "pending" | "approved" | "rejected"
// count: number

const data = schema.parse({
  status: "pending", // βœ… TypeScript accepts this
  // status: "invalid", // ❌ TypeScript error: not in union
  count: 42,
});
πŸ’‘ Pro Tip: The custom error message is completely transparent to TypeScript's type system. You get full type safety without any compromises!

πŸ”— Combining with Other Features

With Conditional Validation

const schema = Interface({
  accountType: "free|premium --> Account type must be free or premium",
  maxProjects: "when accountType=free *? int(1,3) --> Free: 1-3 projects : int(1,100) --> Premium: 1-100 projects",
});

With Record Types

const ConfigSchema = Interface({
  settings: "record --> Settings must be key-value pairs",
  metadata: "record --> Metadata must be an object",
});

Mixed Required and Optional

const FormSchema = Interface({
  // Required with custom message
  email: "email! --> Email is required",
  
  // Optional with custom message
  phone: "string? --> Phone is optional but must be valid",
  
  // Required with constraints
  password: "string(8,)! --> Password must be at least 8 characters",
});

❓ FAQ

Can I use --> in the error message itself?

Yes! Only the first --> is used as the separator:

name: "string --> Use format: FirstName --> LastName"
// Type: string
// Message: "Use format: FirstName --> LastName"

What if I don't provide a custom message?

The default error message is used:

name: "string" // Default: "Expected String, but received ..."

Can I use emojis?

Absolutely! Emojis work perfectly:

email: "email --> πŸ“§ Please provide a valid email address",
age: "number(18,) --> πŸ”ž You must be 18 or older"

Does this affect performance?

Minimal impact. The message is extracted once during schema creation. Validation performance is nearly identical.

Which validators support custom messages?

Feature Supported
Basic types (string, number, boolean) βœ… Yes
Format types (email, url, uuid) βœ… Yes
Constraints (min, max, length) βœ… Yes
Union types βœ… Yes
Arrays βœ… Yes
Required fields (!) βœ… Yes
Optional fields (?) βœ… Yes
Nested objects βœ… Yes