Changelog
v2.5
Released on 2026-11-07; the url fix was added on 2026-11-08. The specification is in drafts/v2.5.md, and the conformance table shows which implementations pass the 2.5 test suite.
LIVR 2.5 only adds rules. Every LIVR 2.0 schema gives the same result in a 2.5 implementation, except for the fixes below: they change only schemas that never worked as written.
- Existing rules keep their behavior, error codes and test cases. One existing rule,
iso_date, gets a new argument form; its 2.0 forms are unchanged. - The syntax of schemas and the validator API do not change. Tests fix the current time for the date bounds (
today,yesterday,tomorrow) ofiso_dateandiso_datetimewith the usual tools of the language. - Fix: rules that read other fields (
equal_to_fieldand custom rules) saw a made-up object inside an alias, anorbranch and an item oflist_ofin LIVR 2.0 ({ value: x },{ field: x },{ field: item }in the JavaScript implementation), so a comparison with a sibling field never worked there. 2.5 defines it through the containing object: in an alias or anorbranch the rules see the siblings of the field, in an item oflist_ofthey see no fields. A schema that relied on the made-up object gives a different result. - Fix:
iso_dateignored any argument in 2.0, so{ iso_date: 'today' }or{ iso_date: 'datetime' }silently checked nothing of what they say. In 2.5 an argument that is not an options object is a schema error, and a boolean value givesWRONG_DATEin every implementation (some gaveFORMAT_ERROR). - Fix:
iso_dateuses the proleptic Gregorian calendar, as almost every implementation already did:1582-10-10is valid and1500-02-29is not (Ruby used the Julian calendar before October 1582). - Fix:
urlaccepted a dot after the host (https://www.google.), host labels longer than 63 characters and ports above 65535, and rejected one-digit ports (https://example.com:8). In 2.5 a dot after the host givesWRONG_URL, like inhostname, labels have at most 63 characters and ports are 0–65535. The rest of theurlgrammar does not change. - Fix: an unknown rule is a schema error in every implementation. Swift ignored it in 2.0 and olifer gave a field error, so a typo in a rule name checked nothing.
- A clarification of
like:^at the start and$at the end of a pattern match only at the start and the end of the value, so{ like: '^[0-9]{5}$' }rejects"12345\n". - A "LIVR 2.5 compatible" implementation supports all the new rules. There are no optional modules.
- The new rules follow the conventions of the 2.5 rules: empty values, the string form of values, comparison, numbers, field references, arguments, grammars and booleans in the output.
- The new error codes are listed in Error codes.
New rules
- Common rules:
boolean,required_if,any. - String rules:
not_one_of,digits,starts_with,ends_with,contains. - Numeric rules:
max_decimals. - Special rules:
iso_datetime,iso_time. - Metarules:
when,values,keys. - List rules, a new group:
list_length,unique. - Format rules, a new group:
uuid,ip,cidr,hostname,base64,hex,digest,mongo_id,hex_color,mac,e164,semver,jwt. - Checksum rules, a new group:
luhn,credit_card,iban. - Modifiers:
split.
Extended iso_date
iso_date takes an options object with min and max. A bound is a date or one of 'today', 'yesterday' and 'tomorrow', always in UTC; bounds give DATE_TOO_EARLY and DATE_TOO_LATE. The 2.0 forms 'iso_date' and { iso_date: [] } do not change; any other argument that is not an options object is a schema error. Dates with a time have their own rule, iso_datetime, which returns the instant in UTC as YYYY-MM-DDTHH:MM:SS.sssZ.
Reserved rule names
These rule names are now reserved: boolean, not_one_of, max_decimals, digits, starts_with, ends_with, contains, iso_datetime, iso_time, required_if, when, keys, values, any, list_length, unique, split, uuid, ip, cidr, hostname, base64, hex, digest, mongo_id, hex_color, mac, e164, semver, jwt, luhn, credit_card, iban.
A rule that the user registers with one of these names replaces the standard rule. So schemas that use boolean, uuid or required_if from js-livr-extra-rules keep working while the package is registered.
Migration from js-livr-extra-rules
| extra-rules | LIVR 2.5 | What changes for the user |
|---|---|---|
boolean | boolean | "true" and "false" also pass |
credit_card | credit_card | 12–19 digits instead of 14–16 |
list_length | list_length | Nothing; the min and max options are added |
list_items_unique | unique | New name. INCOMPARABLE_ITEMS becomes FORMAT_ERROR. Keys for lists of objects |
required_if | required_if | Values are compared as strings (== before); a falsy value (0, false) no longer throws; a list of values means "any of" |
iso_date with min, max | iso_date with min, max | DATE_TOO_LOW and DATE_TOO_HIGH become DATE_TOO_EARLY and DATE_TOO_LATE; current becomes today (current is a schema error); today, yesterday and tomorrow are in UTC, not in the server time zone; datetimes are rejected instead of truncated to a date |
iso_date with format: 'datetime' | iso_datetime | New rule; a date without a time is rejected; bounds give DATETIME_TOO_EARLY and DATETIME_TOO_LATE; current becomes today |
uuid (NOT_UUID), { uuid: 'v4' } | uuid (WRONG_UUID), { uuid: 'v4' } | The default is any version (v4 before); the version and the variant are checked |
ipv4 (NOT_IP) | { ip: 'v4' } (WRONG_IP) | New name and code |
base64 (MALFORMED_BASE64) | base64 (WRONG_BASE64) | New code; 'relaxed' is not in 2.5: keep the package registered or write an alias with like |
md5 (NOT_MD5) | { digest: 'md5' } (WRONG_DIGEST) | New name and code |
mongo_id (NOT_ID) | mongo_id (WRONG_MONGO_ID) | New code |
is | ['required', { eq: ... }] | Not in the specification |
instance_of, has_methods | — | JavaScript only, they stay in the package |
While js-livr-extra-rules is registered, its rules replace the standard ones and nothing changes.
Test suite
The 2.5 cases are a separate test suite in test_suite_2.5/, next to test_suite/ and with the same layout (positive, negative, aliases_positive, aliases_negative) and files. It is a separate directory, because implementations copy test_suite/ into their repositories and some of their runners read every directory in it.
- A case may have an
options.json,{ "now": "2026-10-18T12:00:00Z" }, the current time fortoday,yesterdayandtomorrow. The harness fixes the clock the way the tests of its implementation do; a harness that cannot fix it skips such cases. schema_errors/holds schemas that must not build:rules.json, andaliases.jsonwhen the error is in an alias. A case passes when building the validator (with the aliases registered) fails, however the implementation reports it.- Checks that need to tell
{}from[](values,keys,list_lengthanduniqueon an empty object or list) are in their own cases,positive/andnegative/NN-object_or_list. PHP and Lua decode both to the same value, so the conformance page does not count these cases for them, as in 2.0. - A number in
output.jsonis written the way it comes in the input (10.0gives10.0,"5.000"gives5.000), and objects in the cases have no keys"0","1", …, because PHP and Lua cannot tell such an object from a list. - The conformance page shows a separate 2.5 result for every implementation.
Not in 2.5
- Changes of existing rules and of the engine: error params, string length in code points, the number grammar, the
emailgrammar and the rest of theurlgrammar (Unicode and punycode hosts, IPv6, user info), I-Regexp forlike, parent and root references, root rules, unknown field options fornested_object, meta keys ($error), recursive aliases. not_null,default_if_missingandpresent: the meaning ofnullfor partial updates is decided in 3.0.alpha,alphanumericandnormalize: they need Unicode tables that some languages do not have.not_like: it depends on thelikedialect that 3.0 replaces.absent: it is the same as{ eq: '' }(empty values pass, anything else fails), only the error code would differ. Use an alias witherrorfor a specific code.multiple_of: money is covered bymax_decimals; packs and time slots are rare, and exact decimal arithmetic is a lot of work in every language.tuple: postponed to 3.0, where its argument form (positions as arguments or{ items: [...] }) is decided. For a list of fixed length, uselist_ofwithlist_length.parse_json: postponed. A safe parser needs a nesting limit that every language can enforce without crashing, and that limit is decided in 3.0.- Rare rules or rules that other rules cover (
split,when):list_contains,iso_duration,isbn,gtin,ulid,max_byte_length,collapse_spaces,to_list,required_with,required_without. Forulid, see the alias recipe. country_code,currency_codeandlanguage_code: useone_ofwith a list from a library.
v2.0
- Switched to semver. The new release version is 2.0.
- Unified approach to types handling.
- "Base rules" renamed to "Common rules".
- "Filter rules" renamed to "Modifiers". They do not validate anything, they just modify data.
- "Helper rules" renamed to "Metarules", as these rules are for describing other rules.
- Added common rule
any_object. - Added string rule
string. - Added string rule
eq. - Added metarule
variable_object. - Added metarule
or. - Added modifier
default. - Added more edge cases to the test suite.
- Marked the
likerule as experimental.
v0.4
- Added special rules
urlandiso_date. - Added filter rules
removeandleave_only. - Added support for the
iflag to thelikerule. - Introduced a new syntax for the
one_ofandlist_ofrules, see below. - Rules aliasing, with custom errors.
v0.3
v0.2
- Added the
not_empty_listrule with test cases.
Syntax changes for one_of and list_of rules
The old syntax { one_of: [['val1', 'val2']] } was hard to remember for many people. The idea was that the list of allowed values is passed as a single array argument: { one_of: [['val1', 'val2']] } becomes one_of(['val1', 'val2']). But that is not obvious for users.
So a new syntax was introduced: { one_of: ['val1', 'val2'] } becomes one_of('val1', 'val2'). The problem is that you do not know how many arguments will be passed to one_of, and both syntaxes must be supported for backward compatibility (the test suite covers both). It was decided that the one_of and list_of rules handle both syntaxes by themselves.
Documentation TODO
- Describe the internals with a detailed step-by-step example.
- Write a developers guide.