Validate: allow array schemas + defer known_keys hash creation

Doesn't allow multiple 'func' options yet, needs work.
This commit is contained in:
Yorhel 2025-03-14 06:57:59 +01:00
parent 64a105e013
commit 1363e11269
2 changed files with 33 additions and 16 deletions

View file

@ -76,7 +76,7 @@ our %default_validations = (
uint => { _reg $re_uint }, # implies num
min => sub($min) { +{ num => 1, func => sub { $_[0] >= $min ? 1 : { expected => $min, got => $_[0] } } } },
max => sub($max) { +{ num => 1, func => sub { $_[0] <= $max ? 1 : { expected => $max, got => $_[0] } } } },
range => sub { +{ min => $_[0][0], max => $_[0][1] } },
range => sub { [ min => $_[0][0], max => $_[0][1] ] },
ascii => { _reg qr/^[\x20-\x7E]*$/ },
sl => { _reg qr/^[^\t\r\n]+$/ },
@ -99,10 +99,12 @@ our %default_validations = (
#
sub _compile($schema, $validations, $rec) {
my(%top, @val);
my @keys = keys $schema->{keys}->%* if $schema->{keys};
for my($name, $val) (%$schema) {
for my($name, $val) (ref $schema eq 'ARRAY' ? @$schema : %$schema) {
if ($builtin{$name}) {
confess "Invalid value for 'type': $val" if $name eq 'type' && !$type_vals{$val};
confess "Invalid value for 'missing': $val" if $name eq 'missing' && !$missing_vals{$val};
confess "Invalid value for 'unknown': $val" if $name eq 'unknown' && !$unknown_vals{$val};
$top{$name} = $schema->{$name};
next;
}
@ -117,6 +119,8 @@ sub _compile($schema, $validations, $rec) {
push @val, $v;
}
my @keys = keys $top{keys}->%* if $top{keys};
for my ($n,$t) (qw/keys hash unknown hash values array sort array unique array/) {
next if !exists $top{$n};
confess "Incompatible types, the schema specifies '$top{type}' but the '$n' validation implies '$t'" if $top{type} && $top{type} ne $t;
@ -132,8 +136,8 @@ sub _compile($schema, $validations, $rec) {
exists $t->{$_} and !exists $top{$_} and $top{$_} = delete $t->{$_}
for qw/default onerror trim type scalar unknown missing sort unique/;
push @keys, keys %{ delete $t->{known_keys} };
push @keys, keys %{ $t->{keys} } if $t->{keys};
push @keys, delete($t->{known_keys})->@* if $t->{known_keys};
push @keys, keys $t->{keys}->%* if $t->{keys};
}
# Compile sub-schemas
@ -141,24 +145,20 @@ sub _compile($schema, $validations, $rec) {
$top{values} = __PACKAGE__->compile($top{values}, $validations) if $top{values};
$top{validations} = \@val;
$top{known_keys} = { map +($_,1), @keys };
$top{known_keys} = \@keys;
\%top;
}
sub compile($pkg, $schema, $validations={}) {
return $schema if $schema isa __PACKAGE__;
my $c = _compile $schema, $validations, 64;
$c->{type} //= 'scalar';
$c->{missing} //= 'create';
$c->{trim} //= 1 if $c->{type} eq 'scalar';
$c->{unknown} //= 'remove' if $c->{type} eq 'hash';
confess "Invalid value for 'type': $c->{type}" if !$type_vals{$c->{type}};
confess "Invalid value for 'missing': $c->{missing}" if !$missing_vals{$c->{missing}};
confess "Invalid value for 'unknown': $c->{unknown}" if exists $c->{unknown} && !$unknown_vals{$c->{unknown}};
$c->{known_keys} = { map +($_,1), $c->{known_keys}->@* } if $c->{known_keys};
delete $c->{default} if ref $c->{default} eq 'SCALAR' && ${$c->{default}} eq 'required';
@ -420,10 +420,11 @@ validation. These are documented in L</SCHEMA DEFINITION> below.
=head1 SCHEMA DEFINITION
A schema is a hashref, each key is the name of a built-in option or of a
validation to be performed. None of the options or validations are required,
but some built-ins have default values. This means that the empty schema C<{}>
is actually equivalent to:
A schema is an arrayref or hashref, where each key is the name of a built-in
option or of a validation to be performed and the values are the arguments to
those validations. None of the options or validations are required, but some
built-ins have default values. This means that the empty schema C<{}> is
actually equivalent to:
{ type => 'scalar',
trim => 1,
@ -431,6 +432,19 @@ is actually equivalent to:
missing => 'create',
}
Built-in options are always validated in a fixed order, but the order in which
standard and custom validations are performed is random when the schema is
given as a hashref. This is rarely a problem, but it can in some cases affect
the returned error message or whether a later validation will receive data
normalized by a previous validation. An arrayref can be used to enforce a
validation order:
[ enum => [1, 2, 'a'], int => 1 ]
Or to use the same validation multiple times:
[ regex => qr/^a/, regex => qr/z$/ ]
=head2 Built-in options
=over