@jetio/validator Format Validation
Overview
Format validation in @jetio/validator allows you to validate string values (and other types) against predefined or custom patterns. The validator supports:
- Built-in formats (email, URL, UUID, date-time, etc.)
- Custom formats with multiple definition styles
- Type-specific formats (apply only to certain data types)
- Synchronous and asynchronous validation
- Flexible format modes (full, fast, or disabled)
Configuration
Instance-Level Configuration
Format behavior is configured when creating a @jetio/validator instance:
import { JetValidator } from "@jetio/validator";
const jetValidator = new JetValidator({
formatMode: "full", // 'full', 'fast', or false
formats: [],
validateFormats: true, // Enable/disable format validation
async: false, // Enable async validation globally
});Configuration Options
formatMode
Controls which built-in format validators are loaded:
'full'(default): Loads all inbuilt extensive format validators which comprises of functions and complex regex'fast': Loads only regex-based format validators for better performancefalse: Disables format validation entirely; no inbuilt format validators are loaded, doesn’t affect custom formats
// Full Built-in format loaded
const jetValidator = new JetValidator({ formats: "full" });
// Fast Built-in loaded (regex-only)
const jetValidator = new JetValidator({ formats: "fast" });
// No Built-in format loaded (custom formats needed)
const jetValidator = new JetValidator({ formats: false });validateFormats
Controls whether formats are actually validated:
true(default): Format validation is enabledfalse: Format validation is disabled (formats can still be loaded but won’t be checked)
// Load formats but don't validate them
const jetValidator = new JetValidator({
formats: "full",
validateFormats: false,
});Use case: Useful during development when you want formats available for reference but want to skip validation for performance.
async
Controls whether validation functions support async format validators:
false(default): Only synchronous format validators are supportedtrue: Async format validators are supported; all validators must be awaited
const jetValidator = new JetValidator({ async: true });Per-Compilation Configuration
You can override instance-level settings when compiling a schema:
const jetValidator = new JetValidator({
formatMode: "full",
validateFormats: true,
async: false,
});
// Override for this specific schema
const validate = jetValidator.compile(schema, {
validateFormats: false, // Skip format validation for this schema
async: true, // Enable async for this schema
});Note: Compilation config accepts the same options as the constructor and takes precedence over instance-level settings, except formatMode since instance are loaded immediately after config, it cant be overriden.
Format Types
Built-in Formats
JetValidator includes standard JSON Schema formats:
Full Format Set (formats: 'full')
String Formats:
email- Email addresses (RFC 5322)uri- Uniform Resource Identifiersuri-reference- URI referencesuri-template- URI templatesurl- URLs (subset of URI)uuid- UUIDs (RFC 4122)hostname- Internet hostnames (RFC 1123)ipv4- IPv4 addressesipv6- IPv6 addressesjson-pointer- JSON Pointer (RFC 6901)relative-json-pointer- Relative JSON Pointerregex- Regular expressions
Date/Time Formats:
date-time- ISO 8601 date-timedate- ISO 8601 full-datetime- ISO 8601 full-timeduration- ISO 8601 duration
Fast Format Set (formats: 'fast')
Simpler versions of the formats listed above, for faster validation
Adding Custom Formats
JetValidator provides multiple ways to define custom format validators, each suited for different use cases.
1. Regular Expression
The simplest form - a regex pattern to match against:
jetValidator.addFormat("username", /^[a-zA-Z0-9_]{3,16}$/);
const schema = {
type: "string",
format: "username",
};
const validate = jetValidator.compile(schema);
console.log(validate("john_doe")); // true
console.log(validate("ab")); // falseUse when: You have a simple pattern to match against strings.
2. Validation Function
A function that receives the value and returns a boolean:
jetValidator.addFormat("even-length", (value: string) => {
return value.length % 2 === 0;
});
const schema = {
type: "string",
format: "even-length",
};
const validate = jetValidator.compile(schema);
console.log(validate("test")); // true (length 4)
console.log(validate("hello")); // false (length 5)Function signature:
(value: any) => boolean;Use when: You need simple conditional logic beyond regex.
3. Format Object with Validation
An object with a validate property that can be a regex or function:
// With regex
jetValidator.addFormat("hex-color", {
validate: /^#[0-9A-Fa-f]{6}$/,
});
// With function
jetValidator.addFormat("positive", {
validate: (value: number) => value > 0,
});
const schema = {
format: "positive",
};Use when: You want to use the object format for consistency or to add additional properties like type.
4. Validation with Custom Error Messages
Functions can throw errors to provide specific error messages:
jetValidator.addFormat("password", {
validate: (value: string) => {
if (value.length < 8) {
throw new Error("Password must be at least 8 characters");
}
if (!/[A-Z]/.test(value)) {
throw new Error("Password must contain an uppercase letter");
}
if (!/[0-9]/.test(value)) {
throw new Error("Password must contain a number");
}
if (!/[!@#$%^&*]/.test(value)) {
throw new Error("Password must contain a special character");
}
return true;
},
});Return values:
true- Validation passesfalse- Validation fails with default error messagethrow Error(...)- Validation fails with custom error message
Use when: You need specific, user-friendly error messages for different validation failures.
Type Constraints
By default, format validation applies to all data types. You can restrict formats to specific types using the type property.
Single Type
jetValidator.addFormat("positive", {
type: "number",
validate: (value: number) => value > 0,
});
const schema = { format: "positive" };
const validate = jetValidator.compile(schema);
console.log(validate(5)); // true
console.log(validate(-5)); // true
console.log(validate("hello")); // true - wrong type, format skippedAvailable types:
'string''number''integer''boolean''array''object''null'
Multiple Types
Formats can apply to multiple types using an array:
jetValidator.addFormat("non-empty", {
type: ["string", "array"],
validate: (value: string | any[]) => value.length > 0,
});
const schema = { format: "non-empty" };
const validate = jetValidator.compile(schema);
console.log(validate("hello")); // true
console.log(validate("")); // true
console.log(validate([1, 2])); // true
console.log(validate([])); // true
console.log(validate(42)); // true - wrong type, format skippedWhy Use Type Constraints?
- Performance: Validation only runs on appropriate types
- Type Safety: Format validators receive correctly typed values
- Clearer Intent: Makes schema more self-documenting
- Prevents Errors: Avoids runtime errors from type mismatches
Async Format Validation
JetValidator supports asynchronous format validation for use cases like database lookups, API calls, or other I/O operations.
Defining Async Formats
Mark a format as async by setting the async property to true:
jetValidator.addFormat("unique-email", {
type: "string",
async: true,
validate: async (value: string) => {
// Simulate database check
const exists = await checkEmailInDatabase(value);
return !exists;
},
});Using Async Formats
Method 1: Global Async Configuration
Enable async validation for all schemas compiled by the instance:
const jetValidator = new JetValidator({ async: true });
const schema = {
type: "string",
format: "unique-email",
};
const validate = jetValidator.compile(schema);
// Must await validation
const result = await validate("new@example.com");
console.log(result); // trueMethod 2: Per-Schema Async Configuration
Enable async validation for a specific schema:
const jetValidator = new JetValidator({ async: false });
const schema = {
type: "string",
format: "unique-email",
};
// Enable async for this compilation only
const validate = jetValidator.compile(schema, { async: true });
// Must await validation
const result = await validate("new@example.com");Method 3: Override Instance Setting
Override the instance-level async setting:
const jetValidator = new JetValidator({ async: true });
const syncSchema = {
type: "string",
format: "email", // Sync format
};
// Override instance setting for sync validation
const validate = jetValidator.compile(syncSchema, { async: false });
// No await needed
const result = validate("test@example.com");Async Error Handling
Async validators support the same error handling as sync validators:
jetValidator.addFormat("valid-api-key", {
type: "string",
async: true,
validate: async (value: string) => {
try {
const response = await fetch(`/api/validate?key=${value}`);
const data = await response.json();
if (!data.valid) {
throw new Error(data.reason || "Invalid API key");
}
return true;
} catch (error) {
throw new Error(`API validation failed: ${error.message}`);
}
},
});Mixed Sync/Async Formats
You can use both sync and async formats in the same instance:
const jetValidator = new JetValidator();
// Sync format
jetValidator.addFormat("email", /^[^\s@]+@[^\s@]+\.[^\s@]+$/);
// Async format
jetValidator.addFormat("unique-email", {
type: "string",
async: true,
validate: async (value: string) => await isUnique(value),
});
// Schema with sync format - no async needed
const emailSchema = {
type: "string",
format: "email",
};
const validateEmail = jetValidator.compile(emailSchema);
validateEmail("test@example.com"); // Sync validation
// Schema with async format - requires async
const uniqueSchema = {
type: "string",
format: "unique-email",
};
const validateUnique = jetValidator.compile(uniqueSchema, { async: true });
await validateUnique("test@example.com"); // Async validationPerformance Considerations
Async validation has trade-offs:
✅ Pros:
- Enables complex validation requiring I/O
- Non-blocking for Node.js applications
- Can perform database/API checks
❌ Cons:
- Slower than synchronous validation
- Requires
awaiton all validators - Cannot be used in synchronous contexts
Best practices:
- Only use async when necessary (database checks, API calls)
- Use sync formats for simple validation (regex, calculations)
- Consider caching async validation results
- Set
async: falseby default, enable per-schema as needed
Format Management
Adding Formats
jetValidator.addFormat(name: string, definition: FormatDefinition): voidAdds a custom format validator.
Throws: Error if format already exists.
jetValidator.addFormat("slug", /^[a-z0-9-]+$/);Removing Formats
jetValidator.removeFormat(name: string): voidRemoves a format validator.
Throws: Error if format doesn’t exist.
jetValidator.removeFormat("slug");Checking Format Registration
jetValidator.isFormatRegistered(name: string): booleanReturns true if the format is registered.
if (jetValidator.isFormatRegistered("email")) {
console.log("Email format is available");
}Getting Format Definition
jetValidator.getFormat(name: string): FormatDefinition | undefinedRetrieves the format definition.
const emailFormat = jetValidator.getFormat("email");Listing Registered Formats
jetValidator.getRegisteredFormats(): string[]Returns array of all registered format names.
const formats = jetValidator.getRegisteredFormats();
console.log(formats); // ['email', 'url', 'uuid', 'username', ...]Getting All Formats
jetValidator.getAllFormats(): Record<string, FormatDefinition>Returns a copy of all format definitions.
const allFormats = jetValidator.getAllFormats();Testing Formats Directly
Test Format
jetValidator.testFormat(value: string, format: string): boolean | Promise<boolean>Tests a value against a format validator.
const isValid = jetValidator.testFormat("test@example.com", "email");
console.log(isValid); // true
// For async formats
const isUnique = await jetValidator.testFormat(
"new@example.com",
"unique-email",
);Validate Format
jetValidator.validateFormat(value: string, format: string): ValidationResultValidates a value and returns a detailed result.
const result = jetValidator.validateFormat("invalid-email", "email");
console.log(result);
// { valid: false, errors: { format: "Failed to validate format 'email'" } }Advanced Usage
Combining Type and Format Validation
const schema = {
type: "string",
format: "email",
minLength: 5,
maxLength: 100,
};
const validate = jetValidator.compile(schema);Type is checked first, then format. If type validation fails, format validation is skipped.
Format with Schema Composition
jetValidator.addFormat("strong-password", {
type: "string",
validate: (value: string) => {
return (
value.length >= 12 &&
/[A-Z]/.test(value) &&
/[a-z]/.test(value) &&
/[0-9]/.test(value) &&
/[!@#$%^&*]/.test(value)
);
},
});
const schema = {
type: "object",
properties: {
username: {
type: "string",
format: "username",
},
password: {
type: "string",
format: "strong-password",
},
email: {
type: "string",
format: "email",
},
},
required: ["username", "password", "email"],
};Conditional Format Validation
const schema = {
type: "object",
properties: {
contactType: { type: "string", enum: ["email", "phone"] },
},
if: {
properties: { contactType: { const: "email" } },
},
then: {
properties: {
contact: { type: "string", format: "email" },
},
},
else: {
properties: {
contact: { type: "string", format: "phone" },
},
},
};Reusing Format Logic
const isValidLength = (min: number, max: number) => (value: string) =>
value.length >= min && value.length <= max;
jetValidator.addFormat("short-text", {
type: "string",
validate: isValidLength(1, 50),
});
jetValidator.addFormat("medium-text", {
type: "string",
validate: isValidLength(1, 200),
});
jetValidator.addFormat("long-text", {
type: "string",
validate: isValidLength(1, 1000),
});Custom Formats with Dependencies
import validator from "validator";
jetValidator.addFormat("credit-card", {
type: "string",
validate: (value: string) => validator.isCreditCard(value),
});
jetValidator.addFormat("isbn", {
type: "string",
validate: (value: string) => validator.isISBN(value),
});Format Validation with Side Effects
const auditLog: string[] = [];
jetValidator.addFormat("audited-email", {
type: "string",
validate: (value: string) => {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
auditLog.push(`Email validation: ${value} - ${isValid ? "PASS" : "FAIL"}`);
return isValid;
},
});Note: Side effects should be used carefully as validators may be called multiple times.
API Reference
FormatDefinition Type
type FormatDefinition =
| RegExp
| ((value: any) => boolean)
| {
validate: RegExp | ((value: any) => boolean | Promise<boolean>);
type?: string | string[];
async?: boolean;
};Configuration Types
interface ValidatorOptions {
formats?: "full" | "fast" | false;
validateFormats?: boolean;
async?: boolean;
// ... other options
}
interface CompileConfig {
validateFormats?: boolean;
async?: boolean;
// ... other overrides (same as ValidatorOptions)
}Methods
addFormat(name, definition)
addFormat(name: string, definition: FormatDefinition): voidremoveFormat(name)
removeFormat(name: string): voidgetFormat(name)
getFormat(name: string): FormatDefinition | undefinedisFormatRegistered(name)
isFormatRegistered(name: string): booleangetRegisteredFormats()
getRegisteredFormats(): string[]getAllFormats()
getAllFormats(): Record<string, FormatDefinition>testFormat(value, format)
testFormat(value: string, format: string): boolean | Promise<boolean>validateFormat(value, format)
validateFormat(value: string, format: string): ValidationResultcompile(schema, config)
compile(schema: object | boolean, config?: CompileConfig): FunctionExamples
Example 1: User Registration
const jetValidator = new JetValidator({ formats: "full" });
jetValidator.addFormat("username", {
type: "string",
validate: (value: string) => {
if (value.length < 3) {
throw new Error("Username must be at least 3 characters");
}
if (value.length > 20) {
throw new Error("Username must be at most 20 characters");
}
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
throw new Error(
"Username can only contain letters, numbers, and underscores",
);
}
return true;
},
});
jetValidator.addFormat("strong-password", {
type: "string",
validate: (value: string) => {
const checks = [
{ test: value.length >= 8, error: "at least 8 characters" },
{ test: /[A-Z]/.test(value), error: "an uppercase letter" },
{ test: /[a-z]/.test(value), error: "a lowercase letter" },
{ test: /[0-9]/.test(value), error: "a number" },
{ test: /[!@#$%^&*]/.test(value), error: "a special character" },
];
const failed = checks.find((check) => !check.test);
if (failed) {
throw new Error(`Password must contain ${failed.error}`);
}
return true;
},
});
const userSchema = {
type: "object",
properties: {
username: { type: "string", format: "username" },
email: { type: "string", format: "email" },
password: { type: "string", format: "strong-password" },
},
required: ["username", "email", "password"],
};
const validate = jetValidator.compile(userSchema);Example 2: Async Email Uniqueness Check
const jetValidator = new JetValidator();
jetValidator.addFormat("unique-email", {
type: "string",
async: true,
validate: async (value: string) => {
// Check format first
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
throw new Error("Invalid email format");
}
// Check uniqueness in database
const user = await db.users.findOne({ email: value });
if (user) {
throw new Error("Email is already registered");
}
return true;
},
});
const schema = {
type: "object",
properties: {
email: { type: "string", format: "unique-email" },
},
};
const validate = jetValidator.compile(schema, { async: true });
try {
const result = await validate({ email: "new@example.com" });
console.log("Registration successful");
} catch (error) {
console.error("Validation failed:", error);
}Example 3: Multi-Type Format
jetValidator.addFormat("positive", {
type: ["number", "integer"],
validate: (value: number) => {
if (value <= 0) {
throw new Error("Value must be positive");
}
return true;
},
});
const productSchema = {
type: "object",
properties: {
price: { type: "number", format: "positive" },
quantity: { type: "integer", format: "positive" },
},
};Example 4: Dynamic Format Configuration
// Development: Skip format validation
const devValidator = new JetValidator({
formats: "full",
validateFormats: false,
});
// Production: Full validation
const prodValidator = new JetValidator({
formats: "full",
validateFormats: true,
});
// Testing: Sync only for speed
const testValidator = new JetValidator({
formats: "fast",
async: false,
});Best Practices
- Use type constraints to ensure format validators receive correct types
- Throw descriptive errors instead of returning false for better UX
- Enable async only when needed to maintain performance
- Use ‘fast’ formats in production if full validation isn’t required
- Cache async validation results when possible
- Keep format validators pure (avoid side effects)
- Document custom formats with clear error messages
- Test formats independently using
testFormat()before schema compilation
Troubleshooting
Format not validating
Check:
- Is
validateFormatsset totrue? - Is the format registered? Use
isFormatRegistered() - Is the data type correct for type-constrained formats?
Async format not working
Check:
- Is
async: trueset on the format definition? - Is
async: truepassed tocompile()or set in constructor? - Are you awaiting the validation result?
Format exists error
Problem: addFormat() throws “format already exists”
Solution: Remove the format first:
jetValidator.removeFormat("email");
jetValidator.addFormat("email", /custom-pattern/);Performance Metrics
Format validation overhead:
- Regex formats: ~0.01ms per validation
- Function formats: ~0.01ms per validation
- Async formats: 10-1000ms per validation (depends on I/O)
Recommendations:
- Use
formats: 'fast'for high-throughput applications - Batch async validations when possible
- Consider caching async validation results