-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Summary
joi v16.0.0 is a massive release with many changes to every corner of the module and its extensions. The entire code base has been rewritten with many new features and core components. Instead of an internal architecture and extension framework, every core type is now using the same extension system with a full extension API available for new or expanded types. Since joi schemas are usually well-defined and well-contained, it is recommended to review them against the updated reference to ensure they still perform the expected validation.
- Upgrade time: high - changes required to most existing schemas
- Complexity: high - requires following a long list of changes and verifying their impact
- Risk: medium - large number of changes but most self-validate and will error on incorrect usage
- Dependencies: high - existing extensions will no longer work as-is
Breaking Changes
- Completely new
describe()manifest format (schema.describe minor issues and feature suggestions #1606, Description changes #1823, string min/max/length description missing encoding #1827, Remove isRegExp from object.rename description #1828, Remove precision flag #1850, Remove byteAligned flag #1851, Remove trim flag #1852, Remove case flag #1853, Merge positive and negative number rules into single sign rule #1864, Replace ref.type description to ref.ref #1891, Change object.rename() from description to { regex } when using from pattern #1898, Replace raw flag with result flag in description #1921, Replace date multiple and timestamp flag with format #1977, Replace strip flag with result flag #1995, Compile valid values into valid+override #2077) - Only check for valid / invalid values after coercion (Check for valid/invalid values after conversion only #1623)
- Remove all arguments flattening (Remove all arguments flattening #1644, Change alternatives to be consistent with other spread arguments #2065)
- Change default date format in error messages to ISO (Customize formatting for dates in error messages of min/max/greater/less (with ISO default) #1762)
- Apply only last min / max / length rules consistently across all types (before: array, after: array, binary, object, and string) (Make certain rules (min/max/length) override themselves revisited #1778, 1825)
- Apply a consistent format for all reference strings in errors (Inconsistent reference error reporting #1822)
- Do not modify options objects in string rules (string methods change provided options #1824)
- Change reference from function to object (Change Ref to class #1830)
- Move
func.ref()toobject.ref()(Move func().ref() to object().ref() #1831) - References used in array items (
items()andordered()) point to their own schema instead of the array they are in (Change reference view in array item rules #1832) - Remove
isJoifrom schema (useJoi.isSchema()instead) (Remove isJoi from schema #1834) - Only execute coerce extensions when
convertistrue(Run extension coerce only if options.convert is true #1835) - Apply
array.single()regardless ofconvert(Apply array.single() regardless of options.convert #1836) - Change object and array errors to use full path instead of nested error messages (Change object and array errors to use path instead of nested errors #1843)
- Remove
Hoek.reach()options fromJoi.ref()(Remove Hoek.reach() options from Ref #1839) - Limit
array.single()to only allow non-array items (Limit array.single() to only non-array items #1840) - Skip basic type check when only allowed valid values fail (Skip type check if valids fail #1846)
- Forbid chaining
when()calls when the first was not onalternativestype (Forbid chaining when() calls when the first was not on alternatives type #1848) - Remove example validation (Remove automatic example validation #1856)
- Remove
any.error()options (Remove self option from error() #1857) - Remove
thisfromany.error()function calls (Remove this from error() function #1858) - Remove
any.createOverrideError()(Remove any.createOverrideError() #1859) - Renamed
options()topreferences()(Bidirectional describe() <-> build() #1867) - Renamed
string.regex()tostring.pattern()(Bidirectional describe() <-> build() #1867) any.error()only supports anErroror a function that returns a singleError(drop support for a function that returns string or array or errors (Remove support for string or array or errors return value from error() function #1869)- Replaced or removed error codes, change config to flat object (Remove support for string or array or errors return value from error() function #1869, Replace alternatives.child with alternatives.match error code #1872, Flatten language object #1878, Remove object.rename.regex.multiple and object.rename.regex.override errors #1887, Rename object.allowUnknown error to object.unknown #1976, Rename string.regex() to string.pattern() #1980, Change error code any.empty to string.empty #2006, Change Infinity errors from any.invalid to number.infinity #2007, Consolidate refs assert's message/code #2016, Change object.assert error context and rule argument name #2026, Consolidate date format errors #2034)
- Remove flags argument from
any.createError(), renamed and changed to$_createError(code, value, local, state, prefs, options)(Remove flags argument from createError() #1874) - Errors paths only include the edge label (Labels only used on path edge #1875)
- Change
object.rename('a', 'b', { override: true })to not delete existing keybifadoes not exist (Change object.rename() override option to not delete existing if nothing match #1886) - The reference returned from
Joi.ref()was changed from a function to object withref.resolve()method (Joi.ref() is not a function #1888) - Move the
escapeHtmlpreference undererrors.escapeHtml(Replace escapeHtml with escapeErrors #1889, Move error related prefs toerrors#1905) - Replace the
{{!var}}unescaped error string notation with{var}(Replace {{!var}} notation with {var} #1890) - Change the format of references in error messages (Change "context:" ref prefix in errors to "ref:global:" #1892)
- Error messages require
#prefix for error local context arguments ({{label}}->{{#label}}) (Change any.notes() to any.note() and require separate arguments #1983) - Remove
isJoifromErrors.Report(Remove isJoi from Errors.Report #1895) - Move
language.wrapArraystoerrors.wrapArrayspreference (Rename messages.wrapArrays with wrapArrays #1901, Move error related prefs toerrors#1905) - Rename
languagepreference tomessages(Renamelanguagepreference tomessages#1902) - Remove implicit label prefix in error messages (Remove implicit label prefix for error messages #1903)
- Remove ability to customize default label with a template in
messages(Remove ability to change template for implicit label prefix in error messages #1904) - Abort early on unknown object keys when
abortEarlyittrue(Unknown object keys ignore abortEarly flag #1911) - Throw error when schema cannot validate any value (valid + invalid = no valid options) (Identify blocked schema (valid + invalid = no valid options) #1912)
- Prevent mixing different versions of joi schema types (Prevent mixing different versions of joi schema types #1913)
- Remove callbacks support (Remove callback support #1938)
- Remove
Joi.validate()andJoi.describe()(call direct on schema instead) (Remove root.validate() and root.describe() #1941) - Rename
object.type()toobject.instance()andany.schemaTypetoany.type(Rename object.type() to object.instance() #1942, Rename any.schemaType to any.type #1943) - Move
Joi.reach()toschema.extract()and throw when reaching into non-objects (Throw when Joi.reach() is reaching into non-object #1945, Replace Joi.reach() with schema.extract() #1949) - Remove
applyFunctionToChildren()(Remove applyFunctionToChildren() #1946) - Remove
requiredKeys(),optionalKeys(), andforbiddenKeys()(usefork()instead) (Remove requiredKeys(), optionalKeys(), and forbiddenKeys() #1951) - Change object peer dependencies (e.g.
object.and()) error path to always use object root (Fix object peer dependencies error path #1960) - Remove
Joi.lazy()type (useJoi.link()instead) (Repalce lazy with link #1968) - Use validated value in references regardless of
raw()(Applyingrawafter successful validation #1972) - Remove
descriptionargument indefault()for functions and require value to not beundefined(Remove description fordefault()function #1975) - Change
only()from an alias ofvalid()to convertingallow()tovalid()(Change only() to flag instead of alias of valid() #1978) - Change
any.example()to append by default with second argumentoptionsfor override (Change any.example() to append by default #1981) - Change
any.tags()toany.tag()and require separate arguments (Change any.tags() to any.tag() and require separate arguments #1982) - Change
any.notes()toany.note()and require separate arguments (Change any.notes() to any.note() and require separate arguments #1983) - Change
Joito no longer be an instance ofJoi.any()but support a some shortcuts (allow(),disallow(),equal(),exist(),forbidden(),invalid(),not(),optional(),options(),prefs(),preferences(),required(),valid(),when()) (Refactor root #1985) - Replace
Joi.bind()withJoi.types()(Refactor root #1985) - Split
validate()to sync-onlyvalidate()and async-onlyasyncValidate()(Split validate() to sync and async versions #1998) - Remove
optionsvalidation invalidate()(useJoi.checkPreferences()to manually validate once) (Remove prefs schema validation #2002) - Completely new
extend()API (New extension API #2010, Expose all internal extend() options externally #2013, Rule arguments validation #2023, Support reverse order coerce #2022, Allow mapping flags to rule methods with a different name #2039) - Rename
Joi.func()toJoi.function()(Rename func to function #2012) - Stop matching object patterns once a match is found (Stop matching object patterns once a match is found #2038)
- Remove
Joi.object()andJoi.array()string coerce (Remove object and array string coerce #2031) - Change
errorin syncvalidate()result toundefinedinstead ofnullwhen there is no error ({ result, error } changed to error undefined instead of null #2036) - Replace
boolean.insensitive()withboolean.sensitive()and not apply it toallow()(Replace boolean.insensitive() with sensitive() and not apply to allow() #2040) - Exclude error stack traces by default (restore with the
errors.stackpreference) (Exclude error stack trace by default #2041) - Cast multi values of same type into a single any with
valid()instead of alternatives type (Cast multi values of into a single type with valid() #2046) - Compile literals into any type with exact literal instead of a string/number/boolean with casting (Compile literals into any type with exact literal #2049)
- Rename
alternatives.when()toalternatives.conditional()(Rename alternatives.when() to alternatives.conditional() #2054) object.assert()ref changed to be relative to own object value, not siblings (object.assert() ref should be relative to parent, not self #2060)- Change
any.when()to retain same type instead of returning aJoi.alternatives()(Support chained when() #2062) - Compile valid values into
Joi.valid(Joi.override, ...value)except forempty()(Compile valid values into valid+override #2077) - No longer use array values from resolved references as optional values but a single literal array value (Fix comparison of array values (literals and references) #2087)
- Always perform deep equal on
valid()andinvalid()(Always perform deep equal on valid() and invalid() #2088) - Always convert
-0to0(Always convert -0 to 0 #2102)
Bug fixes
- Reference keys with
.characters (Support schema keys with '.' (without forcing path reach) #1695) - Treat
'https://'and'http:/'as an invalid urls (Joi.string().uri() accepts "https://" as a valid url #1732, Joi.string().uri() accepts 'http:/' as valid URL #1994) - Do not modify boolean type when convert is disabled and
falsy()ortruthy()rules are present (truthy() / falsy() values apply when convert is false #1736) - Do not delete object keys if rename to and from are the same (Don't delete object key when it equals the rename.to in a rename with regex #1770, contributed by Pedro Paixao)
- Apply only last min / max / length rules consistently across all types (Make certain rules (min/max/length) override themselves revisited #1778, Apply only last min/max/length rules #1825)
- Retain object symbols (fix: copy object including Symbols #1796, contributed by Julian Hundeloh)
- Validate
'.30'as a valid number with coercion (Number validation fails with a valid number (".30") #1806) - Fix inconsistent reference strings in errors (Inconsistent reference error reporting #1822)
- Do not modify options objects in string rules (string methods change provided options #1824)
string.truncate()doesn't account for max with ref (string.truncate() doesn't account for max with ref #1826)- References in array items are not taken in account when processing object keys in the right order (Array items using references to array peers are ignored in order of processing #1833)
- Unique tests are duplicated on
concat()(Unique tests duplicated on concat() #1838, Behavior of .concat() with tests redefinition #1973) - Error when adding an unreachable
when()condition (Error on unreachable when() statement #1849) object.rename()errors when nothing has to be renamed (.rename() causes errors when nothing has to be renamed #1854)number()errors with'0.00000095'value (Joi.number() throws error for "0.00000095" string. #1876, contributed by Raghav Manikandan)- Prevent
object.rename('a', 'b', { override: true })from deleting existing keybifadoes not exist (Change object.rename() override option to not delete existing if nothing match #1886) - Change unknown object key errors to obey
abortEarlyflag (Unknown object keys ignore abortEarly flag #1911) - Identify blocked schema (valid + invalid = no valid options) (Identify blocked schema (valid + invalid = no valid options) #1912)
- Allow combining function type rules with object
keys()(Cannot combine function rules with object keys #1919) - Fix object peer dependencies (e.g.
object.and()) error path from first key to object root (Fix object peer dependencies error path #1960) - Do not leak internal references in
describe()(Deep clone all values returned in describe() #1974) - Merging of two
empty()should allow''if it was explicitly defined (Merging of two empty() should allow '' if it was explicitly defined #2005) string.isoDate()fails onallow()and inconsistent whenconvertpreference isfalse(string.isoDate() fails on allow() and convert:false #2030)- Allow
valid()andinvalid()references to arrays to be used as a literal array value and not array of literal values (Fix comparison of array values (literals and references) #2087) '-0'errors as unsafe number incorrectly ("-0" throw number.unsafe #2091)- Number validation fails with
'1E3'(Number validation fails with "1E3" #2097)
Updated dependencies
- hoek from v6.x to v8.x
New Features
Official browser support
joi now comes with a pre-built minified version for client-side development. The package size has been reduced from over 500K to around 150K (uncompressed). Future releases will allow even smaller distributions by being able to pick just the needed functionality. The browser build does not include TLD email validation, Joi.binary(), the describe/build functionality, or debug/tracing.
Manipulate referenced values in expressions (#667, #1917, #1930, #1935, #2027)
Require b to be equal to a plus one:
Joi.object({
a: Joi.number(),
b: Joi.expression('{a + 1}')
});Require at least one of a, b, or c to be true:
Joi.object({
a: Joi.boolean(),
b: Joi.boolean(),
c: Joi.boolean()
})
.assert(Joi.expression('{a || b || c}'), true, 'at least one key must be true');When annual is true, require to to be less than one year away from from:
Joi.object({
annual: Joi.boolean().required(),
from: Joi.date().required(),
to: Joi.date().required()
.when('annual', { is: true, then: Joi.date().max(Joi.expression('{number(from) + 364 * day}')) })
});Validate domain names (#925)
Match domain names under the .com and .net TLDs:
Joi.string().domain({ tlds: { allow: ['com', 'net'] } });Return typed validation errors (#1244)
Contributed by Aaron J. Lang.
joi will return or throw ValidationError instead of plain Error.
Support multiple languages (#1866)
const messages = {
root: 'thing',
'number.min': '{#label} is not big enough',
english: {
root: 'value',
'number.min': '{#label} too small'
},
latin: {
root: 'valorem',
'number.min': Joi.template('{%label} angustus', { prefix: { local: '%' } })
}
};
const schema = Joi.number().min(10);
schema.validate(1, { messages, errors: { language: 'english' } }).error; // 'value too small'
schema.validate(1, { messages, errors: { language: 'latin' } }).error; // 'valorem angustus'
schema.validate(1, { messages, errors: { language: 'french' } }).error; // 'thing is not big enough'Override rule messages (#1264, #1877)
Proof of concept provided by David Luzar.
Modify the messages of individual rules:
Joi.number()
.min(10).message('way too small')
.max(100).message('way too big');Modify the messages of multiple rules:
Joi.number()
.ruleset
.min(10).max(100)
.message('value must be between 10 and 100');Modify specific languages:
const schema = Joi.number()
.min(10)
.message({
english: {
'number.min': '{#label} too small'
},
latin: {
root: 'valorem',
'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })
}
});
schema.validate(1, { errors: { language: 'english' } }); // { error: 'value too small' }
schema.validate(1, { errors: { language: 'latin' } }); // { error: 'valorem angustus' }object.rename() regex capture groups (#1403)
Rename all keys that contain only digits to start with 'num_':
Joi.object()
.rename(/^(\d+)$/, Joi.expression('num_{#1}'));Specify requirements for object key pattern matches (#1457, #2038)
Require an object to contain a specific number of keys starting with 'custom_' based
on the value of another key count:
Joi.object({
count: Joi.number().required()
})
.pattern(/^custom_\w+$/, Joi.number(), { matches: Joi.array().length(Joi.ref('count')) });Require all keys starting with 'x' to be at least 1 and all keys ending in 'z' to be at least 10:
Joi.object()
.pattern(/^x/, Joi.number().min(1), { fallthrough: true })
.pattern(/z$/, Joi.number().max(10));Require all keys starting with 'x' to be at least 1 and for all other keys ending in 'z' (but not starting with 'x') to be at least 10:
Joi.object()
.pattern(/^x/, Joi.number().min(1))
.pattern(/z$/, Joi.number().max(10));Replace invalid values with a failover value (#1535)
If a number is bigger than 10, set it to 5:
Joi.number().max(10).failover(5);Validate ISO duration string (#1612)
Contributed by Alex Petty.
Validates ISO 8601 duration strings:
Joi.string().isoDuration(); // 'P3Y6M4DT12H30M5S'Advance references (#1569, #1650, #1695, #1702, #1807, #1865, #1979, #1993)
If an array size is 2, all the items must be 2, otherwise 7:
Joi.array()
.when('.length', {
is: 2,
then: Joi.array().items(2),
otherwise: Joi.array().items(7)
});Require key c to equal its uncle a:
Joi.object({
a: Joi.any(),
b: {
c: Joi.ref('...a')
}
});Require key c to equal the absolute value a key (note that the root value changes when this schema is used as a sub-schema):
Joi.object({
a: Joi.any(),
b: {
c: Joi.ref('/a')
}
});Reference keys with . characters:
Joi.object({
'a.b': Joi.any(),
b: Joi.ref('a.b', { separator: false })
});Require the second array item to be equal or greater than the first item:
Joi.array()
.ordered(Joi.number(), Joi.number().min(Joi.ref('0')))Require a to be two times the value of b by manipulating the resolved value of the reference using a function:
Joi.object({
a: Joi.ref('b', { adjust: (value) => 2 * value }),
b: Joi.number()
});Require a to be 10 when b is 5, and 11 when b is 6:
Joi.object({
a: Joi.number(),
b: Joi.ref('a', { map: [[5, 10], [6, 11]] })
});Retain object non-enumerable properties (#1696)
const obj = { a: 100 };
Object.defineProperty(obj, 'test', { value: 42, enumerable: false });
schema.validate(obj, { nonEnumerables: true });Validate URI domain component (#1705, #1965)
Only allow URIs with .com domains and at least 3 segments (e.g. 'www.example.com'):
Joi.string()
.uri({ domain: { minDomainSegments: 3, tlds: { allow: ['com'] } } });Support validation options in assert() and attempt() (#1722)
Contributed by Wes Tyler.
Return all errors in assert message:
Joi.assert(-1, Joi.number().min(2).positive(), { abortEarly: false });Validation value and result caching (#1727)
Proof of concept contributed by Michael McLaughlin.
Cache strings to optimize complex regex lookup for repeated values:
Joi.string().pattern(/^some|complex|regex$/).cache();Support array rules validation when items rule fails (#1730)
Return all errors, including items and unique violations:
Joi.array()
.items(Joi.object({
test: Joi.string(),
hello: Joi.string()
}))
.unique('test')
.preferences({abortEarly: false});Set error language based on value in the validated object (#1737, #1907)
Return errors in the language provided within the value:
Joi.object({
lang: Joi.string().valid('english', 'french'),
x: Joi.number()
})
.preferences({
errors: {
language: Joi.ref('/lang')
}
});Validate URI-safe base64 strings (#1746)
Validate a URI-safe base64 string with optional padding:
Joi.string()
.base64({ urlSafe: true, paddingRequired: false });Customize dates string format in error messages (#1762, 1924)
Set date error string format to date only (no time):
Joi.date()
.less('1-1-1970 UTC')
.preferences({ dateFormat: 'date' });Validate multiple email addresses on a single line (#1812)
Contributed by Dolphin.
Joi.string().email({ multiple: true });Enahnced when() options (#1860, #2078, #2079, #2099)
Switch statement:
Joi.object({
a: Joi.number(),
b: Joi.number()
.when('a', {
switch: [
{ is: 1, then: Joi.number().min(1) },
{ is: 2, then: Joi.number().max(10) }
],
otherwise: Joi.forbidden()
})
});Default is to truthy value validation:
Joi.object({
a: Joi.any(),
b: Joi.number()
.when('a', { then: 5 });Use not for reverse condition:
Joi.object({
a: Joi.number(),
b: Joi.number()
.when('a', { not: 1, then: 5 });Break processing conditions:
When strings begin with 'a' require them to be 'axxx', except for them they are 3 characters long which must always be 'abc':
Joi.string()
.when('.length', { is: 3, then: 'abc', break: true })
.when('.0', { is: 'a', then: 'axxx' });Bidirectional describe() <-> build() (#1867, #1986, #1987)
Generates a description that is 100% representative of the schema and can be converted back into a schema:
const schema = Joi.number().min(10).required();
const desc = schema.describe();
/*
{
type: 'number',
flags: {
presence: 'required'
},
rules: [
{
name: 'min',
args: {
limit: 10
}
}
]
}
*/
const built = Joi.build(desc); // Same as schemaAdd object.schema() type support (#1873)
Require the value to be a joi number schema:
Joi.object().schema('number');Rules sets (#1877)
Change the error message of a group of rules:
Joi.number()
.ruleset
.min(10)
.max(100)
.message('value must be a number between 10 and 100');Warnings (#1877, #1957, #1958)
Return a deprecation warning on unused key y:
(Note that typically, the message definition for deprecate.error will be provided globally.)
const schema = Joi.object({
x: Joi.number(),
y: Joi.number().
.warning('deprecate.error', { reason: 'it is no longer supported' })
.message({ 'deprecate.error': '{#label} is deprecated because {#reason}' });
const { value, error, warning } = schema.validate({ x: 1, y: 2 });
// value -> { x: 1, y: 2 };
// error -> undefined
// warning -> { message: 'y is deprecated because it is no longer supported', details: [...] }Convert a set of rules to generate warnings instead of errors:
Joi.string()
.ruleset
.length(10)
.pattern(/^\d+$/)
.rule({
warn: true,
message: 'really wanted a string of 10 digits but oh well'
});Allow multiple instances of unique rules (#1877, #1881)
Keep both checks for minimum value and error based on the first rule to break:
Joi.number()
.min(10).rule({ keep: true, message: 'value must be much bigger' })
.min(100).message('value still not big enough');Sort array items (#1885)
Sorts the array items in ascending order:
Joi.array()
.items(Joi.number())
.sort({ order: 'ascending' });Verify (but not modify) the array of object is sorted in descending by the n object property:
Joi.array()
.items(Joi.object({ n: Joi.number() }))
.sort({ order: 'descending', by: 'n' })
.strict();Value casting (#1914, #1922, #1925, #1929)
Cast the validated number to a string:
Joi.number().cast('string');Return the validated array as Set:
Joi.array().cast('set');Return the validated object as Map:
Joi.object().cast('map');Return 1 for true and 0 for false:
Joi.boolean().cast('number');Extend schema with constructor arguments (#1932)
Create a new special type that takes a minimum limit in its type constructor:
const custom = Joi.extend({
type: 'special',
base: Joi.string(),
args(schema, limit) {
return schema.min(limit);
}
});
const schema = custom.special(2);Support asynchronious operations (post-validation) (#1937)
Perform a database user id lookup on valid strings:
const lookup = async (id) => {
const user = await db.get('user', id);
if (!user) {
throw new Error('Invalid user id');
}
};
const schema = Joi.string().external(lookup);
await schema.validateAsync('1234abcd');Extract any sub-schema with implicit and explicit identifiers (#1950, #1952)
Extract a deeply nested schema inside array items definition:
const schema = Joi.array()
.items(
Joi.object({
c: Joi.object({
d: Joi.number()
})
})
.id('item')
);
schema.extract('item.c.d');Fork schemas (#1950, #1952)
Change the deeply nested d schema to require a minimum value of 10:
const schema = Joi.object({
a: Joi.number(),
b: Joi.object({
c: Joi.object({
d: Joi.number()
})
})
});
const modified = schema.fork('b.c.d', (schema) => schema.min(10));
// Modified is equal to:
Joi.object({
a: Joi.number(),
b: Joi.object({
c: Joi.object({
d: Joi.number().min(10)
})
})
});Schema alterations (#1954)
Feature designed by Nicolas Morel.
Define a single payload schema but tailor it to handle PUT and POST requests differently:
const payload = Joi.object({
id: Joi.string()
.alter({
post: (schema) => schema.required(),
put: (schema) => schema.forbidden()
}),
name: Joi.string(),
email: Joi.string().email()
});
const postSchema = schema.tailor('post');
const putSchema = schema.tailor('put');Schema links and recursive schemas (#1968, #1979, #1993, #2080, #2084, #2096)
Replaces Joi.lazy() by simply referencing the part of the schema to reuse, including the root.
Validate b using the same rules as a (a string or a number):
Joi.object({
a: [Joi.string(), Joi.number()],
b: Joi.link('a')
});Validate a.b.c.d using the same rules as a.e.f:
Joi.object({
a: Joi.object({
b: Joi.object({
c: Joi.object({
d: Joi.link('#a.e.f')
})
}),
e: Joi.object({
f: [Joi.string(), Joi.number()]
})
})
});Recursively validate persons and their friends and their friends, etc.:
const person = Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
friends: Joi.array()
.items(Joi.link('#person'))
})
.id('person');Validate a family where each member has recursive friends:
const person = Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
friends: Joi.array()
.items(Joi.link('#person'))
})
.id('person');
const family = Joi.object({
parents: Joi.array().items(person).min(1),
siblings: Joi.array().items(person).min(1)
});Validate using a shared schema (that is not used elsewhere in the schema chain):
const shared = Joi.number().id('shared');
const schema = Joi.object({
a: [Joi.string(), Joi.link('#shared')],
b: Joi.link('#type.a')
})
.shared(shared)
.id('type');Validate b.c using the same rules as a (a string or a number):
Joi.object({
a: [Joi.string(), Joi.number()],
b: {
c: Joi.link('...a')
}
});Validate recursive structure { name: 'a', keys: [{ name: 'b', keys: [{ name: 'c', keys: [{ name: 'd' }] }] }] }:
Joi.object({
name: Joi.string().required(),
keys: Joi.array()
.items(Joi.link('...'))
});Validate recursive root structure (root links are simpler but do not work when the schema is then reused inside another schema as the root reference will change to the new external schema):
Joi.object({
name: Joi.string().required(),
keys: Joi.array()
.items(Joi.link('/'))
});Custom synchronious functions (#2032)
Replace 2 with 3 and error on 4:
const method = (value, helpers) => {
if (value === 2) {
return 3;
}
if (value === 4) {
return helpers.error('any.invalid');
}
return value;
};
Joi.number().custom(method, 'custom validation');Include messages in other messages (#2033)
{
'error.code': 'this failed because {msg("error.other")}',
'error.other': 'some other reason'
};Skip error rendering preference (#2035)
Return just the error code and improve performance when the text message is not needed:
Joi.number().min(10).validate(1, { errors: { render: false } }); // error -> 'number.min'Match only one or all alternatives (#2051)
Allow numbers and strings that can be converted to numbers, but keep the original:
Joi.alternatives([
Joi.number(),
Joi.string()
])
.mode('all');Allow numbers and strings but not number strings:
Joi.alternatives([
Joi.number(),
Joi.string()
])
.mode('one');Debug log (#1959)
Return a step-by-step list of internal validation processing for debugging schemas:
const schema = Joi.object({
a: {
x: Joi.string()
.lowercase()
.pattern(/\d/)
.max(20)
.min(10)
},
b: Joi.number()
.min(100),
c: Joi.not('y')
});
const value = { a: { x: '12345678901234567890' }, b: 110, c: 'y' };
const { debug } = schema.validate(value, { debug: true });
// debug -> [
// { type: 'entry', path: [] },
// { type: 'entry', path: ['a'] },
// { type: 'entry', path: ['a', 'x'] },
// { type: 'rule', name: 'case', result: 'pass', path: ['a', 'x'] },
// { type: 'rule', name: 'pattern', result: 'pass', path: ['a', 'x'] },
// { type: 'rule', name: 'max', result: 'pass', path: ['a', 'x'] },
// { type: 'rule', name: 'min', result: 'pass', path: ['a', 'x'] },
// { type: 'entry', path: ['b'] },
// { type: 'rule', name: 'min', result: 'pass', path: ['b'] },
// { type: 'entry', path: ['c'] },
// { type: 'invalid', path: ['c'], value: 'y' }
// ]Allow / disallow lists override (#2076)
Replace the list of valid values:
const schema = Joi.valid(1, 2);
schema.valid(Joi.override, 3); // Only allow 3Clear valid list and only flag:
const schema = Joi.valid(1, 2);
schema.valid(Joi.override); // All values allowedValidate values against members of a referenced array (#2089)
Ensure defaultValue is one of allowedValues:
Joi.object({
allowedValues: Joi.array().items(Joi.string()),
defaultValue: Joi.in('allowedValues')
});
### Validate values against array items and object keys (#2092)
Ensure value is a member of another array:
```js
Joi.object({
allowedValues: Joi.array(),
defaultValue: Joi.in('allowedValues')
});Ensure the value of b is present as a key in a object:
Joi.object({
a: Joi.object(),
b: Joi.in('b')
});Migration Checklist
Due to the size of this release and the sheer amount of changes, both breaking and non-breaking, it would be impossible to generate a single migration guide that will cover every possible use case.
The list below is meant as a general guide to help with common use cases and provide clues as to how to approach upgrading to this release. It is highly recommended to review every single rule being used in your code to ensure it still does what you expect. Most problems will result in a helpful error message, but not all.
Core validation workflow
The basic validation flow has changed in a few way:
- Type coercion (e.g. converting number to string) is only executed if the
convertoption istrue(default value). This has been the case for built-in types but not for extensions. - Previously, if no valid value was found, basic type validation was still performed. This would still result in failed validation but the error might have been different due to basic type validation error.
- When
abortEarlyistrueand an object contains unknown keys, the first key will generate an error instead of iterating through all the unknown keys. This was the expected behavior but might result in different error returned. - When
raw()is used in conjunction with references, the referenced value used was the raw value, not the validated value. This was changed to use the validated (and potentially coerced) value in references. - Mixing different versions of joi modules is no longer allowed.
Checklist:
- Make sure any schema using
raw()with references to the raw key takes into account the validated value instead of the raw value. - If you rely on specific error codes, make sure the changes do not result in a different error code than expected.
Errors and messages
TL;DR - If you do anything with errors other than pass the message along, you don't have to do much. However, if you rely on message format, error codes, or provide custom messages, you will need to revisit as it is unlikely to work as-is.
Error messages use a new template parser:
- References in error messages now have access to the global context as well as the value being validated. To access errors-specific variable (e.g.
label), they must be prefixed with#. For example{{label}}is now{{#label}}). - The
{{!var}}syntax for unescaped variables has been replaced with{var}. - Errors paths only include the edge label. This means the
#labelvariable will be set to the label of the failing schema instead of the full path. - The label prefix is not longer added by default. The
!!prefix is no longer supported and will result in a literal!!included in the error message. To disable labels in message templates that include it, use the newerrors.labelpreference.
Errors content changed throughout by applying:
- A default date format using ISO date string. Use
dateFormatpreference set tostringto retain the previous default. - References string representation changed to:
- global context:
ref:global:{path} - local context:
ref:local:{path} - value:
ref:{path} - root:
ref:root:{path}
- global context:
Other error changes:
Errorobjects no longer capture the call stack by default to improve performance and since validation errors are usually coming from a known local and the call stack is unused. To generate a call stack, use theerrors.stackpreference set totrue.- When nested keys or array items fail validation, the new errors will use the full path to the failing schema instead of nesting a chain of errors. This removes the special nested
reasonlocal error context. - Change object peer dependencies (e.g.
object.and()) error path to always use object root instead of including the failing key in the path. Dependency errors are about the object itself, not the individual keys.
The language preference was replaced by a new messages preference using a new structure:
{
// Default messages
root: 'value',
'number.min': '{#label} too small' // Error codes are simple strings (the comma separator is just a convention)
// Language-specific messages
latin: {
root: 'valorem',
'number.min': '{#label} angustus'
}
}New errors codes:
alternatives.allalternatives.matchalternatives.typesalternatives.oneany.customany.failoverany.refarray.sortarray.sort.mismatchingarray.sort.unsupporteddate.formatnumber.infinityobject.pattern.matchobject.refTypestring.domainstring.emptystring.isoDuration
Changed errors codes:
root- does not support templates, must be a plain string.alternatives.base- changed toalternatives.any.any.allowOnly- changed toany.onlyany.empty- replaced bystring.emptyarray.excludesSingle- replaced byarray.excludesarray.includesSingle- replaced byarray.includesarray.ref- replaced byany.refdate.ref- replaced byany.refdate.isoDate- replaced by 'date.format'date.timestamp.javascript- replaced by 'date.format'date.timestamp.unix- replaced by 'date.format'function.base- replaced byobject.basefunction.ref- replaced byany.refnumber.ref- replaced byany.refobject.allowUnknown- replaced byobject.unknownobject.assert-#refchanged to#subjectobject.rename.multiple- added#patternobject.rename.override- added#patternobject.rename.regex.multiple- replaced byobject.rename.multipleobject.rename.regex.override- replaced byobject.rename.regex.overrideobject.schema- added#typeobject.type- replaced byobject.instancestring.email- added#invalidsstring.ref- replaced byany.refstring.regex.base- replaced bystring.pattern.basestring.regex.name- replaced bystring.pattern.namestring.regex.invert.base- replaced bystring.pattern.invert.basestring.regex.invert.name- replaced bystring.pattern.invert.name
Unsupported error codes:
key- no longer supported.messages.wrapArrays- moved toerrors.wrapArrayspreference.
Schema descriptions
A key goal of joi v16 was to support a fully bidirectional describe and build format. The new format has some overlap with the previous one but the change list is far too to include here. If you use the output of describe() programmatically, please consult the source code for how schemas are described. Full documentation is upcoming.
Please note that the description manifest is not designed to be wire-friendly due to the fact that many methods support functions and symbols which do not have a JSON representation. Converting functions to strings is not practical or secure. If you plan on using the new manifest system to send text schemas across the network, make sure to only use wire-safe features which do not include functions, symbols, or other unsupported primitives.
Extensions
The entire extension system has been replaced with a new framework that is now at the core of the entire type system. Every built-in type is just an extension of the base type. Everything the built-in types can do is now available to extensions. Migrating existing extensions is outside the scope of this guide. Please consult the documentation when it becomes available. However, the best resource is the joi built-in types code base.
Array and object string coercion
Joi.object() and Joi.array() no longer support string coercion. This means that a string value of '[1,2,3]' will no longer be automatically converted to a valid array when convert is true, and similarly for objects. This could be a problem if you support object or array strings in HTTP request query strings.
Use extensions to restore this functionality, for example:
const Bourne = require('@hapi/bourne');
const Joi = require('@hapi/joi');
const custom = Joi.extend([
{
type: 'object',
base: Joi.object(),
coerce: {
from: 'string',
method(value, helpers) {
if (value[0] !== '{' &&
!/^\s*\{/.test(value)) {
return;
}
try {
return { value: Bourne.parse(value) };
}
catch (ignoreErr) { }
}
}
},
{
type: 'array',
base: Joi.array(),
coerce: {
from: 'string',
method(value, helpers) {
if (typeof value !== 'string' ||
value[0] !== '[' && !/^\s*\[/.test(value)) {
return;
}
try {
return { value: Bourne.parse(value) };
}
catch (ignoreErr) { }
}
}
}
]);Schema compilation
When using Joi.compile() explicitly or passing non-schema keys and performing implicit compile (e.g. Joi.object({ a: 'x' })), the schema compilation step will no longer compile literals into a type matching strings, booleans, or numbers. Instead, it will return a Joi.any() type with exact literal set as valid(). This means Joi.object({ a: 5 }) will no longer match { a: '5' }. In addition, compiling an array of literal values will compile into a single any type with valid() instead of alternatives type.
When compiling literal values into valid(), the override flag will be added which will make the value override anything it is being concatenated to (e.g. when used in any.when()). To avoid the default override behavior, pass an explicit Joi.valid() schema in stead of a literal value. For example, 'x' is compiled into Joi.valid(Joi.override, 'x'), but Joi.valid('x') is left unchanged. The only exception is for values passed to the Joi.empty() function which are not set to override.
To keep the previous behavior pass the exact types you want instead of relying on implicit compiling.
API changes
Root module methods
-
Joi.allow()- See
any.allow()changes.
- See
-
Joi.bind()- Replaced with new method
Joi.types().
- Replaced with new method
-
Joi.concat()- No longer supported, use
Joi.any().concat()instead (and consult its own changes).
- No longer supported, use
-
Joi.compile()- When compiling simple types (string, number, boolean, null),
Joi.any()is returned instead of the matching type. This meansJoi.compile(5)will no longer match'5'. To keep the previous behavior pass explicit schemas. See Schema compilation section above.
- When compiling simple types (string, number, boolean, null),
-
Joi.createError()- No longer supported. Use new
any.$_createError()instead.
- No longer supported. Use new
-
Joi.createOverrideError()- No longer supported.
-
Joi.default()- No longer supported, use
Joi.any().default()instead (and consult its own changes).
- No longer supported, use
-
Joi.describe()- No longer supported, use
schema.describe()directly on the compiled schema.
- No longer supported, use
-
Joi.description()- No longer supported, use
Joi.any().description()instead.
- No longer supported, use
-
Joi.disallow()- See
any.disallow()changes.
- See
-
Joi.empty()- No longer supported, use
Joi.any().empty()instead.
- No longer supported, use
-
Joi.equal()- See
any.equal()changes.
- See
-
Joi.error()- No longer supported, use
Joi.any().error()instead (and consult its own changes).
- No longer supported, use
-
Joi.example()- No longer supported, use
Joi.any().example()instead (and consult its own changes).
- No longer supported, use
-
Joi.func()- Renamed to
Joi.function().
- Renamed to
-
Joi.invalid()- See
any.invalid()changes.
- See
-
Joi.label()- No longer supported, use
Joi.any().label()instead.
- No longer supported, use
-
Joi.lazy()- No longer supported, use new
Joi.link()instead.
- No longer supported, use new
-
Joi.meta()- No longer supported, use
Joi.any().meta()instead.
- No longer supported, use
-
Joi.not()- See
any.not()changes.
- See
-
Joi.notes()- No longer supported, use new
Joi.any().note()instead.
- No longer supported, use new
-
Joi.only()- No longer supported, use
Joi.valid()instead (and consult its own changes).
- No longer supported, use
-
Joi.options()- Renamed to
Joi.preferences(). - See
any.options()changes.
- Renamed to
-
Joi.raw()- No longer supported, use
Joi.any().raw()instead.
- No longer supported, use
-
Joi.reach()- No longer supported, use new
schema.extract()directly on the compiled schema.
- No longer supported, use new
-
Joi.ref()- Returns a new reference implementation that is an object instead of function.
- No longer supports
Hoek.reach()options inoptions.
-
Joi.schemaType- No longer supported, use
Joi.any().typeinstead.
- No longer supported, use
-
Joi.strict()- No longer supported, use
Joi.any().strict()instead.
- No longer supported, use
-
Joi.strip()- No longer supported, use
Joi.any().strip()instead.
- No longer supported, use
-
Joi.tags()- No longer supported, use new
Joi.any().tag()instead.
- No longer supported, use new
-
Joi.unit()- No longer supported, use
Joi.any().unit()instead.
- No longer supported, use
-
Joi.valid()- See
any.valid()changes.
- See
-
Joi.validate()- No longer supported. Use
schema.validate()directly on the compiled schema. - See
any.validate()changes.
- No longer supported. Use
-
Joi.when()- See
any.when()changes.
- See
any() methods (applies to all types)
-
any.allow()- No longer accepts array arguments. Must pass each value as a separate argument.
- Values are checked only after type coercion (e.g. convert string to number).
-
any.applyFunctionToChildren()- No longer supported.
-
any.concat()- Unique tests (e.g.
min(),max()) are no longer retained and are replaced by the source concatenated schema.
- Unique tests (e.g.
-
any.createError()- No longer supported. Use new
any.$_createError()instead.
- No longer supported. Use new
-
any.createOverrideError()- No longer supported.
-
any.default()- Remove
descriptionargument. - No longer supports
undefinedas value. - Literal function examples require the new
literaloption set totrue.
- Remove
-
any.disallow()- see
any.invalid()
- see
-
any.equal()- see
any.valid()
- see
-
any.error()- No longer supports
options. - Function called without binding to
this. - Only supports an
Errorinstance or a function that returns a singleErrorinstance. - No longer supports functions that return a string or an array or errors.
- No longer supports
-
any.example()- Only supports passing a single example as the first argument.
- Appends examples by default.
- Use new second argument
optionsfor tooverrideexisting examples. - Examples are no longer validated against the schema.
-
any.invalid()- No longer accepts array arguments. Must pass each value as a separate argument.
- Values are checked only after type coercion (e.g. convert string to number).
- Throws error when all the previously valid values are no longer valid (dead end schema).
- References that resolve into an array are no longer used as possible values to match but a single literal array value. To match against any of the array values, use the new
Joi.in()method. - Object and array values are now compared using the
Hoek.deepEqual()method instead of shallow comparison.
-
any.isJoi- No longer supported. Use
Joi.isSchema()instead.
- No longer supported. Use
-
any.not()- see
any.invalid()
- see
-
any.notes()- Renamed to
any.note(). - Requires each note to be passed as a separate argument.
- Renamed to
-
any.only()- No longer used as an alias of
valid()and must be replaced withany.valid()(and consult its own changes). - New usage converts any
any.allow()rules to behave as ifany.valid()was used.
- No longer used as an alias of
-
any.options()- Renamed to
preferences(). - See list of
optionschanges listed underany.validate().
- Renamed to
-
Joi.schemaType- No longer supported, use
any.typeinstead.
- No longer supported, use
-
any.tags()- Renamed to
any.tag(). - Requires each tag to be passed as a separate argument.
- Renamed to
-
any.valid()- No longer accepts array arguments. Must pass each value as a separate argument.
- Values are checked only after type coercion (e.g. convert string to number).
- References that resolve into an array are no longer used as possible values to match but a single literal array value. To match against any of the array values, use the new
Joi.in()method. - Object and array values are now compared using the
Hoek.deepEqual()method instead of shallow comparison.
-
any.validate()- Remove callback support.
- No longer returns a then-able object, only
{ value, error, warning }. For Promises support, use newany.asyncValidate(). - Result
errorchanged toundefinedfromnull. - The
optionsobject is no longer validated automatically. UseJoi.checkPreferences()to manually validate once. options.languageno longer supported. Use newoptions.messages.- Move
options.escapeHtmltooptions.errors.escapeHtml. - Move
options.language.wrapArraystoerrors.wrapArrays.
-
any.when()- No longer returns
Joi.alternatives()type. The same type is retained and the condition is applied during validation time to generate a custom schema based on the combination of the multiplewhen()statements defined. - Past behavior ignored chained
when()conditions once the first one matched. This is no longer the case and all thewhen()statements are processed. If you compare the same reference in chainedwhen()s, keep in mind that it will match both the value and anyotherwisestatements which is probably not what you want. Use the newswitchstatement to only match a single schema. - Conflicting
when()types (e.g. string type rules applied to number type base) will throw an error during validation so make sure to review your schema for potential conflicts. is,then,otherwiseliteral values are compiled into override schemas ('x'is compiled intoJoi.valid(Joi.override, 'x')). This means they will override any base schema the rule is applied to. To add a literal value, use the explicitJoi.valid('x')format.
- No longer returns
alternatives() methods
-
alternatives()- Supports a single argument which must be an array or multiple schemas as separate arguments.
-
alternatives.try()- No longer accepts array arguments. Must pass each value as a separate argument.
-
alternatives.when()- Split off
any.when()with a different implementation. Usealternatives.conditional()instead to keep existing behavior. - Will throw is the provided configuration will result in unreachable conditions.
- Split off
array() methods
-
array()- No longer support string coercion.
-
array.items()- No longer accepts array arguments. Must pass each value as a separate argument.
- References used in schemas point to their own schema instead of the array they are in. Prefix keys with
...to reference the array itself.
-
array.ordered()- No longer accepts array arguments. Must pass each value as a separate argument.
- References used in schemas point to their own schema instead of the array they are in. Prefix keys with
...to reference the array itself.
-
array.single()- Applied to the value regardless of
convert. - Only allowed on arrays with non-array items.
- Applied to the value regardless of
binary() methods
-
binary.min()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
binary.max()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
binary.length()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
boolean() methods
-
boolean.insensitive()- No longer supported. Use the opposite new
boolean.sensitive()instead.
- No longer supported. Use the opposite new
-
boolean.allow()- String values are compared in case-sensitive manner.
-
boolean.equal()- String values are compared in case-sensitive manner.
-
boolean.falsy()- No longer accepts array arguments. Must pass each value as a separate argument.
-
boolean.invalid()- String values are compared in case-sensitive manner.
-
boolean.not()- String values are compared in case-sensitive manner.
-
boolean.truthy()- No longer accepts array arguments. Must pass each value as a separate argument.
-
boolean.valid()- String values are compared in case-sensitive manner.
date methods
-
date.min()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
date.max()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
date.greater()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
date.less()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
function methods
-
function.arity()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
function.class()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
function.maxArity()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
function.minArity()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
function.ref()- Move to
object.ref()
- Move to
number() methods
-
number.greater()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.integer()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.less()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.max()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.min()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.negative()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.port()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.positive()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
number.precision()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
object() methods
-
object()- No longer support string coercion.
-
object.and()- No longer accepts array arguments. Must pass each value as a separate argument.
-
object.assert()- The subject reference is no longer relative to the object keys (siblings). Instead it is relative to the object being validated itself. Just add
.as a prefix to the subject reference (e.g. change'a.b'to.a.b). No change on references resolved inside the assertion schema condition.
- The subject reference is no longer relative to the object keys (siblings). Instead it is relative to the object being validated itself. Just add
-
object.forbiddenKeys()- No longer supported. Use new
any.fork()instead.
- No longer supported. Use new
-
object.length()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
object.max()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
object.min()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
object.nand()- No longer accepts array arguments. Must pass each value as a separate argument.
-
object.optionalKeys()- No longer supported. Use new
any.fork()instead.
- No longer supported. Use new
-
object.or()- No longer accepts array arguments. Must pass each value as a separate argument.
-
object.pattern()- Stops matching patterns once a match is found. Use the new
fallthroughoption to keep previous behavior.
- Stops matching patterns once a match is found. Use the new
-
object.rename()rename('a', 'b', { override: true })will no longer delete existing keybifadoes not exist.
-
object.requiredKeys()- No longer supported. Use new
any.fork()instead.
- No longer supported. Use new
-
object.schema()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
object.type()- Renamed to
object.instance() - Only the last invocation is kept which may result in different error message due to the order the rules are applied.
- Renamed to
-
object.xor()- No longer accepts array arguments. Must pass each value as a separate argument.
ref() methods
Joi.ref()was changed from a function to object withref.resolve()method.Joi.ref()no longer supportsHoek.reach()options.
string() methods
-
string.alphanum()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.base64()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.creditCard()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.dataUri()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.email()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.guid()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.hex()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.hostname()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.ip()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.isoDate()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.length()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.lowercase()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.max()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.min()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.normalize()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.regex()- Renamed to
string.pattern().
- Renamed to
-
string.trim()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.uppercase()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.token()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.uri()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.
-
string.uuid()- Only the last invocation is kept which may result in different error message due to the order the rules are applied.