Chained Condition API - ConditionalBuilder
Overview
ConditionalBuilder Provides a smooth chained conditional judgment API, similar to JavaScript’s if-else statement, which is used to dynamically adjust validation rules based on actual data during validation.
Key semantics: When you use the "return on failure" mode such as .message() / .assert() / .check(), the conditional function should be written as failure condition, because the condition will be judged as failure if it returns true.
Core features
- ✅ Chain Call - Fluent API, similar to JavaScript if-else
- ✅ Runtime execution - judge based on actual data during validation
- ✅ Multiple condition combination - supports and/or logical combination
- ✅ 🆕 Independent Message - v1.1.1+ Each.and()/.or() can have independent error message
- ✅ else optional - If you don’t write else, it will not be validated.
- ✅ Simplified Design - message automatically throws errors, no need to throwError()
- ✅ Fully Compatible - Does not affect existing APIs
🆕 v1.1.1 new features
Independent message support - .and()/.or() can be called later .message()
Each condition can have its own error message
Starting from v1.1.1, it is supported to call .message() after .and() and .or() to set an independent error message to make the error prompt more precise.
Basic usage
import { s } from 'schema-dsl/pure';
// ✅ v1.1.1+ new feature: independent message for each condition
s.if(d => !d)
.message('ACCOUNT_NOT_FOUND')
.and(d => d.tradable_credits < amount)
.message('INSUFFICIENT_TRADABLE_CREDITS')
.assert(account);
// How it works:
// - first condition is true → return 'ACCOUNT_NOT_FOUND'
// - second condition is true → return 'INSUFFICIENT_TRADABLE_CREDITS'
// - all conditions are false → validation successful
Multiple.and() conditions
//Multiple layers of validation, with clear error messages for each layer
s.if(d => !d)
.message('ACCOUNT_NOT_FOUND')
.and(d => d.status !== 'active')
.message('ACCOUNT_INACTIVE')
.and(d => d.tradable_credits < amount)
.message('INSUFFICIENT_TRADABLE_CREDITS')
.assert(account);
// Check in sequence, the first true condition returns its message
.or() conditionally independent messages
// OR conditions also support independent messages
s.if(d => d.age < 18)
.message('Underage users cannot register')
.or(d => d.isBlocked)
.message('Account has been banned')
.assert(data);
// If any condition is true, it fails and the corresponding message is returned.
chain check mode
v1.1.1 introduced Chain Check Mode, which is automatically enabled when the following conditions are met:
- Use
.message() mode (not .then()/.else())
- The root condition is
.message()
- There are
.and() conditions
- No
.or() condition
Chain check mode features:
- Check each condition in turn
- The first condition
true fails and its message is returned
- Validation passes when all conditions are
false
- This mode is a sequential failure-check chain, not an
A && B boolean expression
Example comparison:
// ✅ Enable chain checking (pure AND scenario)
s.if(d => !d).message('A').and(d => d < 100).message('B')
// ❌ Not enabled (has.or(), uses traditional AND/OR logic)
s.if(d => !d).message('A').and(d => d < 100).or(d => d > 200).message('B')
// ❌ Not enabled (use.then()/.else(), not message mode)
s.if(d => d.age >= 18).and(d => d.role === 'admin').then('email!')
backward compatibility
100% backward compatible, does not affect existing code:
// ✅Original usage continues to work
s.if(d => d.age >= 18).and(d => d.role === 'admin').message('does not meet the conditions')
// ✅ It’s okay not to call.message() after.and()
s.if(d =>!d).message('Overall error').and(d => d < 100).assert(50)
// → Use global message 'Global error'
Practical application scenarios
Scenario 1: Account Validation
function validateAccount(account, amount) {
s.if(d => !d)
.message('ACCOUNT_NOT_FOUND')
.and(d => d.status !== 'active')
.message('ACCOUNT_INACTIVE')
.and(d => d.balance < amount)
.message('INSUFFICIENT_BALANCE')
.assert(account);
}
// Each failure point has a clear error message
Scenario 2: User permission validation
function validateUserPermission(user) {
s.if(d => d.role !== 'admin')
.message('NO_ADMIN_PERMISSION')
.and(d => !d.isVerified)
.message('USER_NOT_VERIFIED')
.and(d => d.isBanned)
.message('USER_BANNED')
.assert(user);
}
Scenario 3: Order status check
function validateOrder(order) {
s.if(d => d.status !== 'paid')
.message('ORDER_NOT_PAID')
.and(d => !d.payment)
.message('PAYMENT_INFO_MISSING')
.and(d => !d.shippingAddress)
.message('SHIPPING_ADDRESS_MISSING')
.assert(order);
}
Differences from existing methods
s.if() provides two usage methods, automatically selected according to the parameter type:
Method 1 (field conditions): static judgment based on field values
// Example: Select different validation rules based on isVip field value
s.if('isVip', 'number:0-50', 'number:0-10')
Method 2 (function condition): dynamic judgment based on complete data
// Example: Dynamic selection based on the combination logic of multiple fields
s.if((data) => data.age >= 18 && data.role === 'admin')
.then('email!')
.else('email')
In addition, s.match() is suitable for multi-value mapping scenarios:
// Example: Map different validation rules based on type field value
s.match('type', {
email: 'email!',
phone: 'string:11!',
_default: 'string'
})
quick start
Basic usage
import { s, validate } from 'schema-dsl/pure';
// Method 1: Traditional method (requires validate function)
const schema1 = s({
age: 'number!',
status: s.if((data) => data.age < 18)
.message('Underage users cannot register')
});
validate(schema1, { age: 16, status: 'active' });
// => { valid: false, errors: [{ message: 'Underage users cannot register' }], data: { age: 16, status: 'active' } }
// ✅ Method 2: Shortcut (one line of code validation)
const result = s.if((data) => data.age < 18)
.message('Underage users cannot register')
.validate({ age: 16 });
// => { valid: false, errors: [{ message: 'Underage users cannot register' }], data: { age: 16 } }
// ✅ Method 3:.check() quick judgment
const isValid = s.if((data) => data.age < 18)
.message('Underage users cannot register')
.check({ age: 16 });
// => false
// 2. Condition + then/else (dynamic Schema)
const result = s.if((data) => data.userType === 'admin')
.then('email!') // Required by administrator
.else('email') // Optional for ordinary users
.validate({ userType: 'admin', email: 'admin@example.com' });
// 3. else optional
const result = s.if((data) => data.userType === 'vip')
.then('enum:gold|silver|bronze!')
// Do not write else, non-vip users will not be validated
.validate({ userType: 'user' });
// 4. Reuse validator
const ageValidator = s.if(d => d.age < 18).message('Underage users cannot register');
const r1 = ageValidator.validate({ age: 16 }); // failed
const r2 = ageValidator.validate({ age: 20 }); // Pass
Multiple condition combinations
// 1. AND condition
const result = s.if((data) => data.age >= 18)
.and((data) => data.userType === 'admin')
.message('Only adult administrators can operate')
.validate({ age: 20, userType: 'user' });
// 2. OR condition
const result = s.if((data) => data.age < 18)
.or((data) => data.status === 'blocked')
.message('Registration not allowed')
.validate({ age: 16, status: 'active' });
// 3. Complex combination
const result = s.if((data) => data.age >= 18)
.and((data) => data.userType === 'admin')
.or((data) => data.status === 'vip')
.then('email!')
.else('email')
.validate(data);
elseIf branch
const validator = s.if((data) => data.userType === 'admin')
.then('array<string>!')
.elseIf((data) => data.userType === 'vip')
.then('array<string>')
.elseIf((data) => data.userType === 'user')
.then('array')
.else(null); // Visitors do not verify
const r1 = validator.validate({ userType: 'admin', permissions: ['read', 'write'] });
const r2 = validator.validate({ userType: 'vip' });
const r3 = validator.validate({ userType: 'guest' });
API reference
s.if(condition)
Create a chained conditional builder.
Parameters:
condition {Function} - conditional function, receiving complete data object
- Parameter:
(data: any) => boolean
- Return:
boolean - true means the condition is met
Returns: ConditionalBuilder - builder instance
Example:
s.if((data) => data.age >= 18)
s.if((data) => data.userType === 'admin')
s.if((data) => data.status === 'active' && data.validated)
.and(condition)
Add an AND condition (combine with the previous condition).
v1.1.1+ Support calling .message() after .and() to set independent error messages
Parameters:
condition {Function} - conditional function
Return: this - supports chain calls
Basic example:
// Traditional usage: all conditions share one message
s.if((data) => data.age >= 18)
.and((data) => data.userType === 'admin')
.message('Does not meet conditions')
v1.1.1+ Standalone Message:
// ✅ Each condition has its own error message
s.if((data) => !data)
.message('Account does not exist')
.and((data) => data.balance < 100)
.message('Insufficient balance')
.assert(account);
// How it works:
// - The first condition is true → return 'account does not exist'
// - The second condition is true → return 'Insufficient balance'
// - all conditions are false → validation successful
Multiple.and() conditions:
// Supports multiple.and() conditions, each with independent messages
s.if(d => !d)
.message('NOT_FOUND')
.and(d => d.status !== 'active')
.message('INACTIVE')
.and(d => d.balance < 100)
.message('INSUFFICIENT')
.assert(account);
// Check in sequence, the first true condition returns its message
logic:
- Traditional mode:
(condition1 AND condition2) - all conditions must be true to fail
- Chain check mode (v1.1.1+): Check in sequence, the first one that is true fails
Chain check mode trigger conditions:
- Use
.message() mode
- The root condition is
.message()
- There are
.and() conditions
- No
.or() condition
.or(condition)
Add an OR condition (combine with the previous condition).
v1.1.1+ Support calling .message() after .or() to set independent error messages
Parameters:
condition {Function} - conditional function
Return: this - supports chain calls
Basic example:
// Traditional usage: all conditions share one message
s.if((data) => data.age < 18)
.or((data) => data.status === 'blocked')
.message('Registration not allowed')
v1.1.1+ Standalone Message:
// ✅ Each OR condition has its own error message
s.if(d => d.age < 18)
.message('Underage users cannot register')
.or(d => d.isBlocked)
.message('Account has been banned')
.assert(data);
// How it works:
// - The first condition is true → return 'Underage users cannot register'
// - The second condition is true → returns 'Account has been banned'
// - all conditions are false → validation successful
Logic: (condition1 OR condition2) - Fails if any condition is true
Notice:
- Chained check mode will not be enabled if
.or() condition is present
- Use traditional AND/OR combinational logic
.build()
Output the current ConditionalBuilder into a schema object that can be directly passed to Validator / validate() for use.
.build() is an alias of .toSchema(), suitable for use when you want to get the final schema explicitly.
import { s, validate } from 'schema-dsl/pure';
const conditionalSchema = s.if(data => data.age >= 18)
.then('email!')
.else('email')
.build();
const result = validate(conditionalSchema, 'user@example.com');
console.log(result.valid);
.elseIf(condition)
Add else-if branch.
Parameters:
condition {Function} - conditional function
Return: this - supports chain calls
Example:
s.if((data) => data.userType === 'admin')
.then('email!')
.elseIf((data) => data.userType === 'vip')
.then('email')
.else(null)
Note: Must be called after .if()
.message(msg)
Set error message (supports multi-language keys).
v1.1.1+ Support setting independent messages for .and() and .or() conditions
Parameters:
msg {string} - error message or multilingual key
Return: this - supports chain calls
Behavior: This error is automatically thrown when the condition is true (no need for .throwError())
Basic example:
s.if((data) => data.age < 18)
.message('Underage users cannot register')
//Support multi-language key
s.if((data) => data.age < 18)
.message('error.underage')
v1.1.1+ Set independent message for.and():
// ✅ Each condition has its own error message
s.if((data) => !data)
.message('Account does not exist')
.and((data) => data.balance < 100)
.message('Insufficient balance')
.assert(account);
v1.1.1+ Set independent message for.or():
// ✅ OR conditions also support independent messages
s.if(d => d.age < 18)
.message('underage')
.or(d => d.isBlocked)
.message('Banned')
.assert(data);
Chain check mode description (v1.1.1+):
Chained check mode is automatically enabled when the following conditions are met:
- Use
.message() mode (not .then()/.else())
- The root condition is
.message()
- There are
.and() conditions
- No
.or() condition
// ✅ Enable chain checking (pure AND scenario)
s.if(d => !d).message('A').and(d => d < 100).message('B')
// ❌ Not enabled (with.or())
s.if(d => !d).message('A').and(d => d < 100).or(d => d > 200).message('B')
// ❌ Not enabled (use.then()/.else())
s.if(d => d.age >= 18).and(d => d.role === 'admin').then('email!')
.then(schema)
Set the Schema when the conditions are met.
Parameters:
schema {string|DslBuilder|JSONSchema} - DSL string or Schema object
Return: this - supports chain calls
Example:
// DSL string
s.if((data) => data.userType === 'admin')
.then('email!')
// DslBuilder instance
s.if((data) => data.userType === 'admin')
.then(s('email!').label('Administrator's email'))
// JSON Schema object
s.if((data) => data.userType === 'admin')
.then({ type: 'string', format: 'email' })
.else(schema)
Set the default Schema (when all conditions are not met).
Parameters:
schema {string|DslBuilder|JSONSchema|null} - DSL string, Schema object, or null
Return: this - supports chain calls
Feature: Optional, if you don’t write else, it won’t be validated.
Example:
//Explicitly specify else
s.if((data) => data.userType === 'admin')
.then('email!')
.else('email')
// else is null (explicitly skip validation)
s.if((data) => data.userType === 'admin')
.then('email!')
.else(null)
// Don't write else (implicitly skip validation)
s.if((data) => data.userType === 'admin')
.then('email!')
.validate(data, options)
Quick validation method - returns complete validation results.
Parameters:
data {*} - Data to be validated (any type)
options {Object} - Authentication options (optional)
locale {string} - locale (e.g. 'zh-CN', 'en-US')
messages {Object} - Custom error message
Return: Object - Validation result { valid, errors, data }
Feature: One line of code to complete validation, no external validate() function required
Example:
// One line of code validation
const result = s.if(d => d.age < 18)
.message('Underage users cannot register')
.validate({ age: 16 });
// => { valid: false, errors: [...], data }
//Reuse validator
const ageValidator = s.if(d => d.age < 18).message('underage');
const r1 = ageValidator.validate({ age: 16 }); // false
const r2 = ageValidator.validate({ age: 20 }); // true
//Support validation options
const result = s.if(d => d.age < 18)
.message('conditional.underAge')
.validate({ age: 16 }, { locale: 'zh-CN' });
// Validate non-object types
const result = s.if(d => d.includes('@'))
.then('email!')
.validate('test@example.com');
.validateAsync(data, options)
Asynchronous validation method - automatically throws an exception on failure.
Parameters:
data {*} - Data to be validated
options {Object} - Authentication options (optional)
Return: Promise<*> - If the validation passes, the data will be returned, if it fails, an exception will be thrown.
Throws: ValidationError - Validation failure throws exception
Features: Suitable for async/await scenarios, automatically throwing errors on failure
Example:
// Asynchronous validation, automatically throwing an error if it fails
try {
const data = await s.if(d => d.age < 18)
.message('Underage users cannot register')
.validateAsync({ age: 16 });
} catch (error) {
console.log(error.message); // "Underage users cannot register"
console.log(error.errors); // Detailed error information
}
// Express middleware
app.post('/register', async (req, res, next) => {
try {
await s.if(d => d.age < 18)
.message('Underage users cannot register')
.validateAsync(req.body);
// Validation passed, continue processing...
const user = await createUser(req.body);
res.json(user);
} catch (error) {
next(error);
}
});
//Reuse validator
const ageValidator = s.if(d => d.age < 18).message('underage');
try {
await ageValidator.validateAsync({ age: 16 });
} catch (error) {
// Handle errors
}
.assert(data, options)
Assertion method - synchronous validation, if it fails, an error will be thrown directly.
Parameters:
data {*} - Data to be validated
options {Object} - Authentication options (optional)
Return: * - Return data after validation
Throws: ValidationError - Throws ValidationError directly when validation fails
Feature: Synchronous version of assertion validation, suitable for fast failure scenarios
Example:
// Assertion validation, if it fails, an error will be thrown directly
try {
s.if(d => d.age < 18)
.message('Underage users cannot register')
.assert({ age: 16 });
} catch (error) {
console.log(error.message); // "Underage users cannot register"
}
// Quick assertion in function
function registerUser(userData) {
// Assertion validation
s.if(d => d.age < 18)
.message('Underage users cannot register')
.assert(userData);
s.if(d => !d.email)
.message('Mailbox cannot be empty')
.assert(userData);
// Validation passed, continue processing...
return createUser(userData);
}
// chained assertions
function validateAndCreate(data) {
s.if(d => d.age < 18).message('underage').assert(data);
s.if(d =>!d.email).message('Email required').assert(data);
s.if(d =>!d.username).message('Username required').assert(data);
return createUser(data);
}
.check(data)
Quick check method - returns only boolean.
Parameters:
data {*} - Data to be validated
Return: boolean - Validation passed
Features: More concise than .validate(), suitable for scenarios where only true and false needs to be determined
Example:
//Quick judgment
const isValid = s.if(d => d.age < 18)
.message('underage')
.check({ age: 16 });
// => false
// Assert scenario
if (!validator.check(userData)) {
console.log('Validation failed');
}
// Loop validation
const users = [{ age: 16 }, { age: 20 }, { age: 17 }];
const adults = users.filter(u =>
!s.if(d => d.age < 18).message('underage').check(u)
);
Usage scenarios
Scenario 1: User registration - quick validation
Use the .validate() method to quickly verify user registration data.
//Create a reusable validator
const validators = {
age: s.if(d => d.age < 18).message('Underage users cannot register'),
email: s.if(d => d.userType === 'admin')
.message('The administrator must provide an email address')
};
// Quick validation (one line of code)
function registerUser(userData) {
//Verify age
const ageResult = validators.age.validate(userData);
if (!ageResult.valid) {
return { error: ageResult.errors[0].message };
}
//Verify email
const emailResult = validators.email.validate(userData);
if (!emailResult.valid) {
return { error: emailResult.errors[0].message };
}
return { success: true };
}
// use
registerUser({ username: 'test', age: 16 });
// => { error: 'Underage users cannot register' }
Scenario 2: Batch data validation - using.check()
Use the .check() method to quickly filter qualified data.
const users = [
{ name: 'Zhang San', age: 16 },
{ name: 'Li Si', age: 20 },
{ name: 'Wang Wu', age: 17 },
{ name: 'Zhao Liu', age: 25 }
];
//Create validator
const canRegister = s.if(d => d.age < 18)
.message('underage');
// ✅ Use.check() to filter
const validUsers = users.filter(u => !canRegister.check(u));
// => [{ name: 'Li Si', age: 20 }, { name: 'Zhao Liu', age: 25 }]
// ✅ Use.check() statistics
const minorCount = users.filter(u => canRegister.check(u)).length;
console.log(`Underage users: ${minorCount}`);
// => "Underage users: 2 people"
// Front-end form validation
const formValidators = {
username: s.if(d => d.length < 3)
.message('Username must be at least 3 characters'),
password: s.if(d => d.length < 8)
.message('Password must be at least 8 characters')
};
// Real-time validation (when inputting)
function onUsernameChange(value) {
const isValid = formValidators.username.check(value);
if (!isValid) {
showError('Username must be at least 3 characters');
} else {
clearError();
}
}
// Submit validation
function onSubmit(formData) {
const usernameResult = formValidators.username.validate(formData.username);
const passwordResult = formValidators.password.validate(formData.password);
if (!usernameResult.valid) {
return alert(usernameResult.errors[0].message);
}
if (!passwordResult.valid) {
return alert(passwordResult.errors[0].message);
}
// Submit form...
}
Scenario 4: User permission check
// Permission validator
const hasPermission = s.if(d => d.role === 'admin')
.or(d => d.role === 'moderator')
.message('Insufficient permissions');
// middleware
function checkPermission(req, res, next) {
if (!hasPermission.check(req.user)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
}
// routing
app.delete('/users/:id', checkPermission, deleteUser);
Scenario 5: Validate different fields based on age and user type (contrast with traditional methods)
// Traditional way (requires validate function)
const schema = s({
username: 'string:3-32!',
age: 'number:1-120!',
userType: 'enum:admin|vip|user!',
// Minors are prohibited from registering
ageCheck: s.if((data) => data.age < 18)
.message('Underage users cannot register'),
//The administrator must have an email address
email: s.if((data) => data.userType === 'admin')
.then('email!')
.else('email'),
// VIP users must have a mobile phone number
phone: s.if((data) => data.userType === 'vip')
.then('string:11!')
.else(null),
// Administrators and VIPs can set nicknames
nickname: s.if((data) => data.userType === 'admin')
.or((data) => data.userType === 'vip')
.then('string:2-20')
.else(null)
});
// test
validate(schema, {
username: 'admin1',
age: 25,
userType: 'admin',
email: 'admin@example.com'
});
// => { valid: true }
Scenario 2: Product release
Validate different fields based on product type.
const schema = s({
title: 'string:1-100!',
price: 'number:0-!',
type: 'enum:physical|digital|service!',
// Physical products require weight and dimensions
weight: s.if((data) => data.type === 'physical')
.then('number:0-!')
.else(null),
dimensions: s.if((data) => data.type === 'physical')
.then('string!')
.else(null),
// Digital products require a download link
downloadUrl: s.if((data) => data.type === 'digital')
.then('url!')
.else(null),
// Service class requires service duration
duration: s.if((data) => data.type === 'service')
.then('number:1-!')
.else(null)
});
// physical goods
validate(schema, {
title: 'Laptop',
price: 5999,
type: 'physical',
weight: 1.5,
dimensions: '30x20x2cm'
});
// => { valid: true }
//Digital goods
validate(schema, {
title: 'e-book',
price: 29.9,
type: 'digital',
downloadUrl: 'https://example.com/download'
});
// => { valid: true }
Scenario 3: Permission control
Control access based on user role and status.
const schema = s({
userId: 'string!',
role: 'enum:admin|moderator|user!',
status: 'enum:active|suspended|banned!',
// Banned users are prohibited from operating
accessCheck: s.if((data) => data.status === 'banned')
.message('Your account has been banned'),
// Pause user can only view
operationType: s.if((data) => data.status === 'suspended')
.then('enum:view!')
.else('enum:view|edit|delete!'),
// Administrators can access all resources
resourceIds: s.if((data) => data.role === 'admin')
.then('array<string>') // optional
.else('array<string>!') // required
});
best practices
1. Keep conditional functions simple
❌ Not recommended:
s.if((data) => {
const user = getUserFromDB(data.userId); // Synchronous database query
return user.level > 5;
})
✅ Recommended:
s.if((data) => data.userLevel > 5)
Reason: Conditional functions should only read data objects and should not have side effects or perform time-consuming operations.
2. Use meaningful field names
❌ Not recommended:
const schema = s({
field1: 'string!',
check1: s.if((data) => data.field1 === 'admin')
.message('Error')
});
✅ Recommended:
const schema = s({
userType: 'string!',
ageVerification: s.if((data) => data.age < 18)
.message('Underage users cannot register')
});
3. Fair use else
When conditions are not met and different validation rules are required, use .else():
s.if((data) => data.userType === 'admin')
.then('email!')
.else('email') // Different validation rules
When the conditions are not met, validation is not required and .else() is omitted:
s.if((data) => data.userType === 'vip')
.then('string:6!')
// Do not write else, non-vip users will not be validated
4. Use the internal logic of the function first for multiple condition combinations
Simple conditions can be composed directly inside functions:
// ✅ Recommended (concise)
s.if((data) => data.age >= 18 && data.userType === 'admin')
.then('email!')
// ⚠️ Available but a little cumbersome
s.if((data) => data.age >= 18)
.and((data) => data.userType === 'admin')
.then('email!')
Use .and() / .or() when complex logic or maintainability is required:
// ✅ Recommended (high readability)
s.if((data) => data.age >= 18)
.and((data) => data.userType === 'admin')
.and((data) => data.validated)
.or((data) => data.isSuperUser)
.then('email!')
5. Error messages are clear and unambiguous
❌ Not recommended:
s.if((data) => data.age < 18)
.message('Error')
✅ Recommended:
s.if((data) => data.age < 18)
.message('Underage users cannot register')
✅ BETTER (Supports multiple languages):
s.if((data) => data.age < 18)
.message('error.user.underage')
FAQ
Q1: When will the conditional function be executed?
A: Executed when calling validate(), not when defining Schema.
const schema = s({
email: s.if((data) => data.userType === 'admin')
.then('email!') // ← will not be executed here
});
validate(schema, data); // ← conditional function is executed here
Q2: What data can the conditional function access?
A: Can access the complete data object.
const schema = s({
age: 'number!',
userType: 'string!',
status: 'string!',
email: s.if((data) => {
// All fields can be accessed
return data.age >= 18 && data.userType === 'admin' && data.status === 'active';
}).then('email!')
});
Q3: How to deal with conditional functions throwing errors?
A: If the conditional function throws an error, it will be captured and regarded as the condition is not met.
const schema = s({
obj: 'object!',
result: s.if((data) => data.obj.nested.value > 10)
.then('string!')
.else(null)
});
// data.obj.nested does not exist, access will throw an error
validate(schema, { obj: {} });
// => { valid: true } If the condition is not met, execute else(null)
Suggestion: Do defensive checks in conditional functions:
s.if((data) => data.obj?.nested?.value > 10)
.then('string!')
Q4: Can s.if() be nested?
A: Yes, nesting is supported.
const schema = s({
userType: 'string!',
age: 'number!',
email: s.if((data) => data.userType === 'admin')
.then(
s.if((data) => data.age >= 18)
.then('email!')
.else('email')
)
.else('email')
});
Q5: How to use it with the existing s.match() method?
A: Can be mixed, choose the most suitable method.
const schema = s({
// Static value mapping - use match
userType: 'enum:admin|vip|user!',
level: s.match('userType', {
admin: 'enum:high!',
vip: 'enum:medium!',
user: 'enum:low!'
}),
// Dynamic conditional judgment - use if
email: s.if((data) => data.userType === 'admin' && data.level === 'high')
.then('email!')
.else('email')
});
Selection Suggestions:
- Simple Value Mapping → Use
s.match()
- Complex conditional logic → Use
s.if()
Q6: Are non-object types (strings, arrays, numbers, etc.) supported?
A: Fully support! Any type of value can be validated directly.
//Example 1: Directly verify the string
const stringSchema = s.if((data) => typeof data === 'string' && data.includes('@'))
.then('email!')
.else('string:1-50');
validate(stringSchema, 'test@example.com'); // ✅ as email validation
validate(stringSchema, 'just a text'); // ✅ Validate as a normal string
//Example 2: Directly verify the array
const arraySchema = s.if((data) => Array.isArray(data) && data.length > 5)
.message('Array can have up to 5 elements');
validate(arraySchema, [1, 2, 3]); // ✅ Passed
validate(arraySchema, [1, 2, 3, 4, 5, 6]); // ❌ failed
// Example 3: Verify numbers directly
const numberSchema = s.if((data) => typeof data === 'number' && data < 0)
.message('Negative numbers are not allowed');
validate(numberSchema, 10); // ✅ Passed
validate(numberSchema, -5); // ❌ failed
//Example 4: Automatically identify the type (email or mobile phone number)
const contactSchema = s.if((data) => typeof data === 'string' && data.includes('@'))
.then('email!')
.else('string:11!');
validate(contactSchema, 'user@example.com'); // ✅ as email validation
validate(contactSchema, '13800138000'); // ✅ Verify as mobile phone number
Full Example: See test/unit/conditional-non-object.test.ts
A: Excellent performance, conditional function execution is very fast.
- The conditional function is a pure JavaScript function and executes quickly
- Traverse the condition chain only once and stop when the first matching condition is found.
- Support cache optimization (WeakMap)
Performance Tips:
- Avoid performing time-consuming operations (database queries, API calls) in conditional functions
- Put the most common conditions first (if instead of elseIf)
Change log
v1.1.1 (2026-01-05)
- ✅ Added
ConditionalBuilder category
- ✅ Added
s.if() chain condition API
- ✅Supports and/or multiple condition combinations
- ✅ Support elseIf multiple branches
- ✅ message automatically throws errors (no need to throwError)
- ✅ else optional (if you don’t write it, it won’t be validated)
- ❌ Remove invalid old conditional method type definitions
Corresponding sample file
Example entry: conditional-api.ts
Description: Also overrides .check() / .assert() in failed predicate mode, as well as field name version s.if(field, then, else) and s.match() mapping.