=for HTML Tests CPAN version =head1 NAME Validator::LIVR - Lightweight validator supporting Language Independent Validation Rules Specification (LIVR) =head1 SYNOPSIS use Validator::LIVR; my $validator = Validator::LIVR->new({ name => 'required', email => [ 'required', 'email' ], gender => { one_of => [ 'male', 'female' ] }, phone => { max_length => 10 }, password => [ 'required', { min_length => 10 } ], password2 => { equal_to_field => 'password' }, }); if ( my $valid_data = $validator->validate($user_data) ) { save_user($valid_data); } else { my $errors = $validator->get_errors(); # { email => 'WRONG_EMAIL', password => 'TOO_SHORT' } } =head1 DESCRIPTION LIVR rules are plain data: a hash that maps field names to rules. They are easy to read, and because the same rules work in L, one rule set can check a form in the browser with JavaScript and again on the server with Perl. This module implements L and passes the test suite of the specification. It also has the rules of the LIVR 2.5 draft, see L, and passes its test suite. =over 4 =item * B that you can keep in JSON and share with other services. =item * B, not just the first one. =item * B A field without rules, such as an unexpected C, never reaches your code. =item * B: objects, lists, lists of objects and objects of several types. =item * B, such as C or C. Turn them into messages in any language you need. =item * B with modifiers, such as C, C and C. =item * B, as aliases of existing rules or as Perl code. =item * B: only modules that come with Perl. =back Use it for user input, configs, API requests and responses, or any other data that has to have a known shape. =head1 A QUICK TOUR =head2 Writing rules A rule set maps each field to its rules. A rule is written in one of these ways: { name => 'required', # a rule without arguments bio => { max_length => 1000 }, # a rule with one argument nick => { length_between => [ 3, 20 ] }, # a rule with several arguments email => [ 'required', 'email' ], # several rules } Several rules of a field are applied in order, and the field stops at its first error. A hash is always one rule, so two rules with arguments are two hashes: C<< [ { min_length => 3 }, { max_length => 20 } ] >>. Most rules accept empty values: C and the empty string. So a field is optional until you add C: { age => 'positive_integer', # can be missing id => [ 'required', 'positive_integer' ], # must be present } =head2 Validating nested data Metarules describe the structure of the data, and errors have the same shape as the data: my $validator = Validator::LIVR->new({ order_id => [ 'required', 'positive_integer' ], address => [ 'required', { nested_object => { country => [ 'required', { one_of => [ 'Ukraine', 'USA' ] } ], city => 'required', zip => 'positive_integer', } } ], products => [ 'required', 'not_empty_list', { list_of_objects => { product_id => [ 'required', 'positive_integer' ], quantity => [ 'required', { min_number => 1 } ], } } ], tags => { list_of => [ 'string', { max_length => 10 } ] }, }); For an order with an unknown country, a second product with a quantity of 0 and a second tag that is too long, C returns: { address => { country => 'NOT_ALLOWED_VALUE' }, products => [ undef, { quantity => 'TOO_LOW' } ], tags => [ undef, 'TOO_LONG' ], } =head2 Cleaning data Modifiers change the value for the next rules and for the output: my $validator = Validator::LIVR->new({ email => [ 'trim', 'required', 'email', 'to_lc' ], phone => { leave_only => '0123456789' }, role => [ { default => 'user' }, { one_of => [ 'user', 'admin' ] } ], }); $validator->validate({ email => ' John@Example.COM ', phone => '+38 (050) 123-45-67' }); # { email => 'john@example.com', phone => '380501234567', role => 'user' } To trim every string of the input before validation, turn on auto trim for one validator with C<< Validator::LIVR->new( $rules, 1 ) >>, or for all new validators with C<< Validator::LIVR->default_auto_trim(1) >>. =head2 Sharing rules with other languages Rules are plain data, so they can live in a JSON file: { "email": [ "required", "email" ], "password": [ "required", { "min_length": 10 } ] } use JSON::PP qw(decode_json); my $validator = Validator::LIVR->new( decode_json($json_rules) ); The same file works with L in JavaScript, and with the L for PHP, Python, Go, Java, Ruby, Erlang, Lua, Raku and Swift. You can try rules in the L. =head2 Adding your own rules The simplest way is an alias: a new name for a combination of rules, with its own error code. Aliases are a part of the specification, so other implementations understand them too. Validator::LIVR->register_aliased_default_rule({ name => 'adult_age', rules => [ 'positive_integer', { min_number => 18 } ], error => 'WRONG_AGE', }); my $validator = Validator::LIVR->new({ age => [ 'required', 'adult_age' ] }); For anything else, write the rule in Perl, see L. =head1 RULES The built-in rules, grouped as in the specification. Each group links to its page in the specification, which has the details and edge cases. A rule that expects a string or a number returns C for a hash or a list. A rule that expects a hash or a list returns C for anything else. =head2 Common rules L =over 4 =item C The field is present and not empty: not C and not C<''>. Error: C. =item C The field can be missing, but when it is there, it is not C<''>. Error: C. =item C The field is a list with at least one item. Errors: C, C. =item C The field is a hash with any content. Error: C. =back =head2 String rules L =over 4 =item C Any string or number. Error: C. =item C<< { eq => $value } >> The value is equal to C<$value>, compared as strings: C<2> equals C<"2">, and a boolean such as C equals C<"true">. As in Validator::LIVR 2.0, true also equals C<"1"> and false C<"0">. The output is C<$value>. Error: C. =item C<< { one_of => [ @values ] } >> The value is equal to one of C<@values>, compared as in C. As in Validator::LIVR 2.0, a boolean value also equals C<1> or C<"1"> (true) and C<0> or C<"0"> (false). The output is that one of C<@values>. Error: C. =item C<< { min_length => $length } >> The value has at least C<$length> characters. Error: C. =item C<< { max_length => $length } >> The value has at most C<$length> characters. Error: C. =item C<< { length_equal => $length } >> The value has exactly C<$length> characters. Errors: C, C. =item C<< { length_between => [ $min, $max ] } >> The value has from C<$min> to C<$max> characters. Errors: C, C. =item C<< { like => $regex } >>, C<< { like => [ $regex, 'i' ] } >> The value matches the regular expression. The C flag makes it case insensitive. Error: C. The rule is experimental in the specification. The syntax of regular expressions differs between languages, so stick to the common features when you share rules. =back =head2 Numeric rules L =over 4 =item C An integer, such as C<-5> or C<42>. Error: C. =item C An integer greater than zero. Error: C. =item C A number, such as C<-1.5> or C<42>. Error: C. =item C A number greater than zero. Error: C. =item C<< { min_number => $min } >> A number that is not less than C<$min>. Errors: C, C. =item C<< { max_number => $max } >> A number that is not greater than C<$max>. Errors: C, C. =item C<< { number_between => [ $min, $max ] } >> A number from C<$min> to C<$max>. Errors: C, C, C. =back =head2 Special rules L =over 4 =item C An email address, such as C, without a name, comments or spaces around it. The domain has a TLD or is an IP address in brackets. Error: C. =item C An C or C URL, such as C. The host is a domain name, such as C or C, or an IPv4 address. Domain names in other languages work in decoded strings, not in UTF-8 bytes. Error: C. =item C, C<< { iso_date => { min => $date, max => $date } } >> A date as C that exists in the calendar: C<2024-02-29> is valid, C<2025-02-29> is not. Error: C. LIVR 2.5: the bounds C and C, both optional and inclusive, are dates or C, C and C: the current date in UTC, from the system time (see L). An argument that is not a hash dies; LIVR 2.0 ignored it. A boolean gives C. Errors: C, C, C, C. =item C<< { equal_to_field => $field } >> The value is equal to the value of another field, such as a password confirmation. Error: C. =back =head2 Metarules L Metarules validate nested data with their own rule sets. Their errors are nested too: a hash of errors for an object, and a list for a list, with C for the valid items. =over 4 =item C<< { nested_object => \%rules } >> A hash validated with C<\%rules>. =item C<< { list_of => \@rules } >>, C<< { list_of => $rule } >> A list, each item validated with the rules. =item C<< { list_of_objects => \%rules } >> A list of hashes, each validated with C<\%rules>. =item C<< { variable_object => [ $selector, { $type => \%rules, ... } ] } >> A hash validated with the rules for the value of its C<$selector> field. The error is C when the hash has no rules for that value. payment => [ 'required', { variable_object => [ type => { card => { type => 'required', card_number => [ 'required', { length_equal => 16 } ], }, iban => { type => 'required', iban => 'required', }, } ] } ], =item C<< { list_of_different_objects => [ $selector, { $type => \%rules, ... } ] } >> A list of hashes, each validated as in C. =item C<< { or => [ $rules1, $rules2, ... ] } >> The value passes at least one of the rule sets. Each rule set is a rule or a list of rules. When all of them fail, the error is the error of the last one. id => { or => [ 'email', 'positive_integer' ] }, =back The rules of C and of aliases see the fields next to the field, so C works in them. The items of C have no fields next to them. =head2 Modifiers L Modifiers change the value and never fail. =over 4 =item C Removes the spaces at the start and at the end. =item C Converts to lower case. =item C Converts to upper case. =item C<< { remove => $chars } >> Removes each of the characters in C<$chars>, so C<< { remove => ' -()' } >> turns C<(050) 123-45-67> into C<0501234567>. =item C<< { leave_only => $chars } >> Removes all characters except the ones in C<$chars>. =item C<< { default => $value } >> Sets C<$value> when the field is missing or empty. =back =head2 LIVR 2.5 rules L The rules of the LIVR 2.5 draft. They are loaded on first use, so they do not slow down C. They check their arguments when the validator is built: L dies on a wrong argument, such as C<< { boolean => 1 } >> or C<< { digest => 'MD5' } >>, or on C or an empty list in place of rules in C, C and C (use C for no rules), and on C or a list in their lists of rules, such as C<< [ 'required', [] ] >>. Arguments after the ones a rule takes are ignored, and a number and a string with that number are the same argument. C is an argument too, and no LIVR 2.5 rule takes it: C<< { boolean => undef } >> dies, like C<< { boolean => [undef] } >>. The LIVR 2.0 rules and custom rules get C as an argument, as before, and C<< { iso_date => undef } >> stays C, as LIVR 2.0 ignored its arguments. They compare and check the string form of a value: a boolean, such as C, is C or C. Format and checksum rules return their own error code for a boolean, and C for a hash or a list. =over 4 =item C C, C, C<1>, C<0>, C<"1">, C<"0">, C<"true"> or C<"false">. The output is C or C. Errors: C, C. =item C<< { not_one_of => [ @values ] } >> The value is none of C<@values>. Errors: C, C. =item C<< { max_decimals => $count } >> A number with at most C<$count> digits after the decimal point; trailing zeros do not count. The output is a number. Errors: C, C, C. =item C ASCII digits, the leading zeros are kept: the output is a string. Errors: C, C. =item C<< { starts_with => [ @strings ] } >>, C<< { ends_with => [ @strings ] } >>, C<< { contains => [ @strings ] } >> The value starts with, ends with or contains one of C<@strings>. Errors: C, C. =item C, C<< { iso_datetime => { min => $bound, max => $bound } } >> A date and a time with an offset, such as C<2026-10-29T10:00+02:00>. The output is the time in UTC, C<2026-10-29T08:00:00.000Z>. A bound is a datetime, a date (its start as C, its end as C) or C, C and C. Errors: C, C, C, C. =item C, C<< { iso_time => { min => $time, max => $time } } >> C, C or C. With C greater than C the range goes over midnight. Errors: C, C, C, C. =item C<< { required_if => { $reference => $value, ... } } >> The field is required when all referenced fields have the given values, or one of the values of a list. A reference is a field name or a path, such as C
. Error: C. =item C<< { when => [ $reference, $condition, $rules_if_true, $rules_if_false ] } >>, C<< { when => [ $reference, { $value => $rules, ... } ] } >> Validates the field with rules that depend on another field: on whether it passes the C<$condition> rules, or on its value. vat_number => { when => [ 'type', { eq => 'company' }, [ 'required', 'string' ], { eq => '' } ] }, Errors: C and the errors of the rules. =item C<< { values => \@rules } >>, C<< { keys => \@rules } >> A hash with every value or every key validated with the rules. A wrong key gets C. Errors: C, C and the errors of the rules. =item C Any value, the hashes and lists too, stays in the output unchanged. =item C<< { list_length => $length } >>, C<< { list_length => [ $min, $max ] } >>, C<< { list_length => { min => $min, max => $max } } >> A list with that number of items. Errors: C, C, C. =item C, C<< { unique => [ @keys ] } >> A list without duplicates, or of hashes without duplicate values of C<@keys>. Errors: C, C. =item C<< { split => $separator } >> A modifier: splits a string into a list, C<"1,2,3"> into C<["1", "2", "3"]>. A number or a boolean is split in its string form, a list stays as it is. =item Formats C (or C<< { uuid => [ 'v4', 'v7' ] } >>), C (or C<< { ip => 'v4' } >>, C<< { ip => 'v6' } >>), C (the same), C, C (or C<< { base64 => 'url' } >>), C, C<< { digest => [ 'md5', 'sha256' ] } >> (also C, C, C), C, C, C (or C<< { mac => 64 } >>), C, C and C. Errors: C, C, C, C, C, C, C, C, C, C, C, C, C, C. =item Checksums C, C and C. The value is checked as it is: remove spaces before, with C<< { remove => ' -' } >>. Errors: C, C, C, C. =back =head2 Testing rules with the current date The bounds C, C and C read the system time with C