revert 495fcbc, added test case for issue#7

This commit is contained in:
2023-03-08 14:29:20 +01:00
parent 495fcbcb5f
commit 513039dd53
3 changed files with 313 additions and 289 deletions

View File

@@ -1,3 +1,7 @@
0.12
o revert commit 495fcbc, see #7: breaks backwards
compatibility.
0.11 0.11
o typos o typos

View File

@@ -21,7 +21,7 @@ use File::stat;
use Data::Validate qw(:math is_printable); use Data::Validate qw(:math is_printable);
use Data::Validate::IP qw(is_ipv4 is_ipv6); use Data::Validate::IP qw(is_ipv4 is_ipv6);
our $VERSION = 0.11; our $VERSION = 0.12;
use vars qw(@ISA); use vars qw(@ISA);
@@ -195,14 +195,7 @@ sub _traverse {
my ($self, $reference, $hash, @tree) = @_; my ($self, $reference, $hash, @tree) = @_;
foreach my $key (keys %{$reference}) { foreach my $key (keys %{$reference}) {
my $reference_ref = ref $reference->{$key}; if (ref($reference->{$key}) eq 'ARRAY') {
my $hash_ref = ref $hash->{$key};
if ($reference_ref =~ /^ARRAY|HASH$/ && $reference_ref ne $hash_ref)
{
my $err = sprintf("Different structure types %s vs %s", $reference_ref, $hash_ref);
push @{$self->{errors}}, sprintf(q{%s at '%s'}, $err, join(' => ', @tree));
}
elsif ($reference_ref eq 'ARRAY') {
# just use the 1st one, more elements in array are expected to be the same # just use the 1st one, more elements in array are expected to be the same
foreach my $item (@{$hash->{$key}}) { foreach my $item (@{$hash->{$key}}) {
if (ref($item) eq q(HASH)) { if (ref($item) eq q(HASH)) {
@@ -219,10 +212,10 @@ sub _traverse {
} }
} }
} }
elsif ($reference_ref eq 'HASH') { elsif (ref($reference->{$key}) eq 'HASH') {
$self->_traverse($reference->{$key}, $hash->{$key}, @tree, $key); $self->_traverse($reference->{$key}, $hash->{$key}, @tree, $key);
} }
elsif ($reference_ref eq '') { elsif (ref($reference->{$key}) eq '') {
$self->_debug("Checking $key at " . join(', ', @tree)); $self->_debug("Checking $key at " . join(', ', @tree));
if (my $err = $self->_check_type($key, $reference, $hash)) { if (my $err = $self->_check_type($key, $reference, $hash)) {
push @{$self->{errors}}, sprintf(q{%s at '%s'}, $err, join(' => ', @tree)); push @{$self->{errors}}, sprintf(q{%s at '%s'}, $err, join(' => ', @tree));

583
t/run.t
View File

@@ -6,36 +6,36 @@ use Encode qw{ encode };
require_ok( 'Data::Validate::Struct' ); require_ok( 'Data::Validate::Struct' );
my $ref = { my $ref = {
'b1' => { 'b1' => {
'b2' => { 'b2' => {
'b3' => { 'b3' => {
'item' => 'int' 'item' => 'int'
} }
} }
}, },
'item' => [ 'number' ], 'item' => [ 'number' ],
'v1' => 'int', 'v1' => 'int',
'v2' => 'number', 'v2' => 'number',
'v3' => 'word', 'v3' => 'word',
'v4' => 'line', 'v4' => 'line',
'v5' => 'text', 'v5' => 'text',
'v6' => 'hostname', 'v6' => 'hostname',
'v8' => 'user', 'v8' => 'user',
'v10' => 'port', 'v10' => 'port',
'v11' => 'uri', 'v11' => 'uri',
'v12' => 'cidrv4', 'v12' => 'cidrv4',
'v13' => 'ipv4', 'v13' => 'ipv4',
'v14' => 'path', 'v14' => 'path',
'v15' => 'fileexists', 'v15' => 'fileexists',
'v16' => 'quoted', 'v16' => 'quoted',
'v171' => 'regex', 'v171' => 'regex',
'v172' => 'regex', 'v172' => 'regex',
'v18' => 'novars', 'v18' => 'novars',
'v19' => 'ipv6', 'v19' => 'ipv6',
'v20' => 'ipv6', 'v20' => 'ipv6',
'v21' => 'ipv6', 'v21' => 'ipv6',
'v22' => 'ipv6', 'v22' => 'ipv6',
'v23' => 'ipv6', 'v23' => 'ipv6',
'v24' => 'ipv6', 'v24' => 'ipv6',
'v25' => 'ipv6', 'v25' => 'ipv6',
'v26' => 'cidrv6', 'v26' => 'cidrv6',
@@ -48,54 +48,55 @@ my $ref = {
'AoA' => [ [ 'int' ] ], 'AoA' => [ [ 'int' ] ],
'AoH' => [ 'AoH' => [
{ fullname => 'text', user => 'word', uid => 'int' } {
], fullname => 'text', user => 'word', uid => 'int' }
],
'HoH' => { 'HoH' => {
father => { fullname => 'text', user => 'word' }, father => { fullname => 'text', user => 'word' },
son => { fullname => 'text', user => 'word' }, son => { fullname => 'text', user => 'word' },
daughter => { fullname => 'text', user => 'word' }, daughter => { fullname => 'text', user => 'word' },
}, },
'r1' => 'range(80-90)', 'r1' => 'range(80-90)',
}; };
my $cfg = { my $cfg = {
'b1' => { 'b1' => {
'b2' => { 'b2' => {
'b3' => { 'b3' => {
'item' => '100' 'item' => '100'
} }
} }
}, },
'item' => [ 'item' => [
'10', '10',
'20', '20',
'30' '30'
], ],
'v1' => '123', 'v1' => '123',
'v2' => '19.03', 'v2' => '19.03',
'v3' => 'Johannes', 'v3' => 'Johannes',
'v4' => 'this is a line of text', 'v4' => 'this is a line of text',
'v5' => 'This is a text block 'v5' => 'This is a text block
This is a text block', This is a text block',
'v6' => 'search.cpan.org', 'v6' => 'search.cpan.org',
'v8' => 'root', 'v8' => 'root',
'v10' => '22', 'v10' => '22',
'v11' => 'http://search.cpan.org/~tlinden/?ignore&not=1', 'v11' => 'http://search.cpan.org/~tlinden/?ignore&not=1',
'v12' => '192.168.1.101/18', 'v12' => '192.168.1.101/18',
'v13' => '10.0.0.193', 'v13' => '10.0.0.193',
'v14' => '/etc/ssh/sshd.conf', 'v14' => '/etc/ssh/sshd.conf',
'v15' => 'MANIFEST', 'v15' => 'MANIFEST',
'v16' => '\' this is a quoted string \'', 'v16' => '\' this is a quoted string \'',
'v171' => qr([0-9]+), 'v171' => qr([0-9]+),
'v172' => 'qr([0-9]+)', 'v172' => 'qr([0-9]+)',
'v18' => 'Doesnt contain any variables', 'v18' => 'Doesnt contain any variables',
'v19' => '3ffe:1900:4545:3:200:f8ff:fe21:67cf', 'v19' => '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
'v20' => 'fe80:0:0:0:200:f8ff:fe21:67cf', 'v20' => 'fe80:0:0:0:200:f8ff:fe21:67cf',
'v21' => 'fe80::200:f8ff:fe21:67cf', 'v21' => 'fe80::200:f8ff:fe21:67cf',
'v22' => 'ff02:0:0:0:0:0:0:1', 'v22' => 'ff02:0:0:0:0:0:0:1',
'v23' => 'ff02::1', 'v23' => 'ff02::1',
'v24' => '::ffff:192.0.2.128', 'v24' => '::ffff:192.0.2.128',
'v25' => '::', 'v25' => '::',
'v26' => '2001:db8:dead:beef::b00c/64', 'v26' => '2001:db8:dead:beef::b00c/64',
@@ -104,228 +105,234 @@ my $cfg = {
'v28' => '$ten', 'v28' => '$ten',
'AoA' => [ 'AoA' => [
[ qw{ 10 11 12 13 } ], [ qw{ 10 11 12 13 } ],
[ qw{ 20 21 22 23 } ], [ qw{ 20 21 22 23 } ],
[ qw{ 30 31 32 33 } ], [ qw{ 30 31 32 33 } ],
], ],
'AoH' => [ 'AoH' => [
{ fullname => 'Homer Simpson', user => 'homer', uid => 100 }, {
{ fullname => 'Bart Simpson', user => 'bart', uid => 101 }, fullname => 'Homer Simpson', user => 'homer', uid => 100 },
{ fullname => 'Lisa Simpson', user => 'lisa', uid => 102 }, {
], fullname => 'Bart Simpson', user => 'bart', uid => 101 },
{
fullname => 'Lisa Simpson', user => 'lisa', uid => 102 },
],
'HoH' => { 'HoH' => {
father => { fullname => 'Homer Simpson', user => 'homer' }, father => { fullname => 'Homer Simpson', user => 'homer' },
son => { fullname => 'Bart Simpson', user => 'bart' }, son => { fullname => 'Bart Simpson', user => 'bart' },
daughter => { fullname => 'Lisa Simpson', user => 'lisa' }, daughter => { fullname => 'Lisa Simpson', user => 'lisa' },
}, },
'r1' => 85, 'r1' => 85,
}; };
my $v = new_ok('Data::Validate::Struct', [ $ref ]); my $v = new_ok('Data::Validate::Struct', [ $ref ]);
ok ($v->validate($cfg), "validate a reference against a OK config"); ok ($v->validate($cfg), "validate a reference against a OK config");
# check failure matching # check failure matching
my @failure = ( my @failure = (
{ {
cfg => q(acht), cfg => q(acht),
type => q(int), type => q(int),
descr => 'int', descr => 'int',
errors => 1, errors => 1,
}, },
{ {
cfg => q(27^8), cfg => q(27^8),
type => q(number), type => q(number),
descr => 'number', descr => 'number',
errors => 1, errors => 1,
}, },
{ {
cfg => q(two words), cfg => q(two words),
type => q(word), type => q(word),
descr => 'word', descr => 'word',
errors => 1, errors => 1,
}, },
{ {
cfg => qq(<<EOF\nzeile1\nzeile2\nzeile3\nEOF\n), cfg => qq(<<EOF\nzeile1\nzeile2\nzeile3\nEOF\n),
type => q(line), type => q(line),
descr => 'line', descr => 'line',
errors => 1, errors => 1,
}, },
{ {
cfg => q(ätz), cfg => q(ätz),
type => q(hostname), type => q(hostname),
descr => 'hostname', descr => 'hostname',
errors => 1, errors => 1,
}, },
{ {
cfg => q(gibtsnet123456790.intern), cfg => q(gibtsnet123456790.intern),
type => q(resolvablehost), type => q(resolvablehost),
descr => 'resolvablehost', descr => 'resolvablehost',
errors => 1, errors => 1,
}, },
{ {
cfg => q(äüö), cfg => q(äüö),
type => q(user), type => q(user),
descr => 'user', descr => 'user',
errors => 1, errors => 1,
}, },
{ {
cfg => q(äüö), cfg => q(äüö),
type => q(group), type => q(group),
descr => 'group', descr => 'group',
errors => 1, errors => 1,
}, },
{ {
cfg => q(234234444), cfg => q(234234444),
type => q(port), type => q(port),
descr => 'port', descr => 'port',
errors => 1, errors => 1,
}, },
{ {
cfg => q(unknown:/unsinnüäö), cfg => q(unknown:/unsinnüäö),
type => q(uri), type => q(uri),
descr => 'uri', descr => 'uri',
errors => 1, errors => 1,
}, },
{ {
cfg => q(1.1.1.1/33), cfg => q(1.1.1.1/33),
type => q(cidrv4), type => q(cidrv4),
descr => 'cidrv4', descr => 'cidrv4',
errors => 1, errors => 1,
}, },
{ {
cfg => q(300.1.1.1), cfg => q(300.1.1.1),
type => q(ipv4), type => q(ipv4),
descr => 'ipv4', descr => 'ipv4',
errors => 1, errors => 1,
}, },
{ {
cfg => q(üäö), cfg => q(üäö),
type => q(fileexists), type => q(fileexists),
descr => 'fileexists', descr => 'fileexists',
errors => 1, errors => 1,
}, },
{ {
cfg => q(not quoted), cfg => q(not quoted),
type => q(quoted), type => q(quoted),
descr => 'quoted', descr => 'quoted',
errors => 1, errors => 1,
}, },
{ {
cfg => q(no regex), cfg => q(no regex),
type => q(regex), type => q(regex),
descr => 'regex', descr => 'regex',
errors => 1, errors => 1,
}, },
{ {
cfg => q($contains some $vars), cfg => q($contains some $vars),
type => q(novars), type => q(novars),
descr => 'novars', descr => 'novars',
errors => 1, errors => 1,
}, },
{ {
cfg => q(2001:db8::dead::beef), cfg => q(2001:db8::dead::beef),
type => q(ipv6), type => q(ipv6),
descr => 'ipv6', descr => 'ipv6',
errors => 1, errors => 1,
}, },
{ {
cfg => q(2001:db8:dead:beef::1/129), cfg => q(2001:db8:dead:beef::1/129),
type => q(cidrv6), type => q(cidrv6),
descr => 'cidrv6', descr => 'cidrv6',
errors => 1, errors => 1,
}, },
{ {
cfg => [ cfg => [
[ qw{ 10 11 12 13 } ], [ qw{ 10 11 12 13 } ],
[ qw{ 'twenty' 21 22 23 } ], [ qw{ 'twenty' 21 22 23 } ],
[ qw{ 30 31 32.0 33 } ], [ qw{ 30 31 32.0 33 } ],
], ],
type => [ [ 'int' ] ], type => [ [ 'int' ] ],
descr => 'array of arrays', descr => 'array of arrays',
errors => 2, errors => 2,
}, },
{ {
cfg => [ cfg => [
{ fullname => 'Homer Simpson', user => 'homer', uid => 100 }, {
{ fullname => 'Bart Simpson', user => ':bart', uid => 101 }, fullname => 'Homer Simpson', user => 'homer', uid => 100 },
{ fullname => 'Lisa Simpson', user => 'lisa', uid => '102' }, {
], fullname => 'Bart Simpson', user => ':bart', uid => 101 },
{
fullname => 'Lisa Simpson', user => 'lisa', uid => '102' },
],
type => [ type => [
{ fullname => 'text', user => 'word', uid => 'int' } {
], fullname => 'text', user => 'word', uid => 'int' }
],
descr => 'array of hashes', descr => 'array of hashes',
errors => 1, errors => 1,
}, },
{ {
cfg => { cfg => {
father => { fullname => 'Homer Simpson', user => 'homer', uid => 100 }, father => { fullname => 'Homer Simpson', user => 'homer', uid => 100 },
son => { fullname => 'Bart Simpson', user => 'bart', uid => 'one hundred one' }, son => { fullname => 'Bart Simpson', user => 'bart', uid => 'one hundred one' },
daughter => { fullname => 'Lisa Simpson', user => 'lisa:', uid => 'one hundred two' }, daughter => { fullname => 'Lisa Simpson', user => 'lisa:', uid => 'one hundred two' },
}, },
type => { type => {
father => { fullname => 'text', user => 'word', uid => 'int' }, father => { fullname => 'text', user => 'word', uid => 'int' },
son => { fullname => 'text', user => 'word', uid => 'int' }, son => { fullname => 'text', user => 'word', uid => 'int' },
daughter => { fullname => 'text', user => 'word', uid => 'int' }, daughter => { fullname => 'text', user => 'word', uid => 'int' },
}, },
descr => 'hash of hashes', descr => 'hash of hashes',
errors => 3, errors => 3,
}, },
{ {
cfg => { cfg => {
name => 'Foo Bar', name => 'Foo Bar',
age => 42, age => 42,
}, },
type => { type => {
name => 'text', name => 'text',
age => 'int', age => 'int',
address => 'text', address => 'text',
}, },
descr => 'Missing required field', descr => 'Missing required field',
errors => 1, errors => 1,
}, },
{ {
cfg => 100, cfg => 100,
type => 'range(200-1000)', type => 'range(200-1000)',
descr => 'value outside dynamic range', descr => 'value outside dynamic range',
errors => 1, errors => 1,
}, },
); );
foreach my $test (@failure) { foreach my $test (@failure) {
my $ref = { v => $test->{type} }; my $ref = { v => $test->{type} };
@@ -334,13 +341,12 @@ foreach my $test (@failure) {
#$v->debug(); #$v->debug();
my $result = $v->validate($cfg); my $result = $v->validate($cfg);
my $descr = encode('UTF-8', my $descr = encode('UTF-8',
exists $test->{descr} ? $test->{descr} : $test->{cfg} exists $test->{descr} ? $test->{descr} : $test->{cfg}
); );
my $errors = exists $test->{errors} ? $test->{errors} : 1; my $errors = exists $test->{errors} ? $test->{errors} : 1;
unless ($result) { unless ($result) {
is @{$v->errors}, $errors, "Caught failure for '$descr'"; is @{$v->errors}, $errors, "Caught failure for '$descr'";
} } else {
else {
fail("Couldn't catch invalid '$test->{descr}'"); fail("Couldn't catch invalid '$test->{descr}'");
} }
} }
@@ -349,45 +355,45 @@ foreach my $test (@failure) {
# clean old object # clean old object
undef $v; undef $v;
$v = Data::Validate::Struct->new({ $v = Data::Validate::Struct->new({
h1 => { h2 => { item => 'int' } } h1 => { h2 => { item => 'int' } }
}); });
ok !$v->validate({ ok !$v->validate({
h1 => { h2 => { item => 'qux' } } h1 => { h2 => { item => 'qux' } }
}), 'item is not an h1 => h2 => int'; }), 'item is not an h1 => h2 => int';
is $v->errstr, q{'qux' doesn't match 'int' at 'h1 => h2'}, 'correct error trace'; is $v->errstr, q{'qux' doesn't match 'int' at 'h1 => h2'}, 'correct error trace';
# adding custom type # adding custom type
my $ref3 = { my $ref3 = {
v1 => 'address', v1 => 'address',
v2 => 'list', v2 => 'list',
v3 => 'noob', v3 => 'noob',
v4 => 'nonoob', v4 => 'nonoob',
}; };
my $cfg3 = { my $cfg3 = {
v1 => 'Marblestreet 15', v1 => 'Marblestreet 15',
v2 => 'a1, b2, b3', v2 => 'a1, b2, b3',
v3 => 42, v3 => 42,
v4 => 43, v4 => 43,
}; };
my $v3 = new Data::Validate::Struct($ref3); my $v3 = new Data::Validate::Struct($ref3);
# add via hash # add via hash
note('added via hash'); note('added via hash');
my %h = ( my %h = (
address => qr(^\w+\s\s*\d+$) address => qr(^\w+\s\s*\d+$)
); );
$v3->type(%h); $v3->type(%h);
# add via hash ref # add via hash ref
note('added via hash ref'); note('added via hash ref');
$v3->type({ list => $v3->type({ list =>
sub { sub {
my $list = $_[0]; my $list = $_[0];
my @list = split /\s*,\s*/, $list; my @list = split /\s*,\s*/, $list;
return scalar @list > 1; return scalar @list > 1;
} }
}); });
# add via key => value # add via key => value
note('added via key => val'); note('added via key => val');
@@ -401,9 +407,30 @@ my $v4 = Data::Validate::Struct->new({age => 'int'});
ok(!$v4->validate({age => 'eight'}), "cache check first run, error"); ok(!$v4->validate({age => 'eight'}), "cache check first run, error");
ok($v4->validate({age => 8}), "cache check second run, no error"); ok($v4->validate({age => 8}), "cache check second run, no error");
# different references # optional array, see:
my $v5 = Data::Validate::Struct->new({ foo => [{bar => 'int'}]}); # https://github.com/TLINDEN/Data-Validate-Struct/issues/7
ok(!$v5->validate({foo=>{bar=>10}})); my $ref4 = {
routers => [ {
stubs => [ {
network => 'ipv4',
}, {} ],
}, {}, ],
};
my $test4 = {
'routers' => [
{
'stubs' => [
{
'network' => '172.31.199.0',
}
],
'router' => '172.31.199.2', # optional, ignored by validate
},
{ # optional as well
'router' => '172.30.5.5',
},
],
};
my $v4 = Data::Validate::Struct->new($ref4);
ok($v4->validate($test4), "check optional " . $Data::Validate::Struct::VERSION);
done_testing(); done_testing();