diff --git a/Changelog b/Changelog index ae9d1fd..1e676b5 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,7 @@ +0.12 + o revert commit 495fcbc, see #7: breaks backwards + compatibility. + 0.11 o typos diff --git a/Struct.pm b/Struct.pm index 23938f1..951391f 100644 --- a/Struct.pm +++ b/Struct.pm @@ -21,7 +21,7 @@ use File::stat; use Data::Validate qw(:math is_printable); use Data::Validate::IP qw(is_ipv4 is_ipv6); -our $VERSION = 0.11; +our $VERSION = 0.12; use vars qw(@ISA); @@ -195,14 +195,7 @@ sub _traverse { my ($self, $reference, $hash, @tree) = @_; foreach my $key (keys %{$reference}) { - my $reference_ref = ref $reference->{$key}; - 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') { + if (ref($reference->{$key}) eq 'ARRAY') { # just use the 1st one, more elements in array are expected to be the same foreach my $item (@{$hash->{$key}}) { 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); } - elsif ($reference_ref eq '') { + elsif (ref($reference->{$key}) eq '') { $self->_debug("Checking $key at " . join(', ', @tree)); if (my $err = $self->_check_type($key, $reference, $hash)) { push @{$self->{errors}}, sprintf(q{%s at '%s'}, $err, join(' => ', @tree)); diff --git a/t/run.t b/t/run.t index d297654..697ee34 100644 --- a/t/run.t +++ b/t/run.t @@ -6,36 +6,36 @@ use Encode qw{ encode }; require_ok( 'Data::Validate::Struct' ); my $ref = { - 'b1' => { + 'b1' => { 'b2' => { - 'b3' => { - 'item' => 'int' - } + 'b3' => { + 'item' => 'int' + } } - }, - 'item' => [ 'number' ], - 'v1' => 'int', - 'v2' => 'number', - 'v3' => 'word', - 'v4' => 'line', - 'v5' => 'text', - 'v6' => 'hostname', - 'v8' => 'user', - 'v10' => 'port', - 'v11' => 'uri', - 'v12' => 'cidrv4', - 'v13' => 'ipv4', - 'v14' => 'path', - 'v15' => 'fileexists', - 'v16' => 'quoted', - 'v171' => 'regex', - 'v172' => 'regex', - 'v18' => 'novars', - 'v19' => 'ipv6', - 'v20' => 'ipv6', - 'v21' => 'ipv6', - 'v22' => 'ipv6', - 'v23' => 'ipv6', + }, + 'item' => [ 'number' ], + 'v1' => 'int', + 'v2' => 'number', + 'v3' => 'word', + 'v4' => 'line', + 'v5' => 'text', + 'v6' => 'hostname', + 'v8' => 'user', + 'v10' => 'port', + 'v11' => 'uri', + 'v12' => 'cidrv4', + 'v13' => 'ipv4', + 'v14' => 'path', + 'v15' => 'fileexists', + 'v16' => 'quoted', + 'v171' => 'regex', + 'v172' => 'regex', + 'v18' => 'novars', + 'v19' => 'ipv6', + 'v20' => 'ipv6', + 'v21' => 'ipv6', + 'v22' => 'ipv6', + 'v23' => 'ipv6', 'v24' => 'ipv6', 'v25' => 'ipv6', 'v26' => 'cidrv6', @@ -48,54 +48,55 @@ my $ref = { 'AoA' => [ [ 'int' ] ], 'AoH' => [ - { fullname => 'text', user => 'word', uid => 'int' } - ], + { + fullname => 'text', user => 'word', uid => 'int' } + ], 'HoH' => { - father => { fullname => 'text', user => 'word' }, - son => { fullname => 'text', user => 'word' }, - daughter => { fullname => 'text', user => 'word' }, - }, + father => { fullname => 'text', user => 'word' }, + son => { fullname => 'text', user => 'word' }, + daughter => { fullname => 'text', user => 'word' }, + }, - 'r1' => 'range(80-90)', - }; + 'r1' => 'range(80-90)', + }; my $cfg = { - 'b1' => { - 'b2' => { + 'b1' => { + 'b2' => { 'b3' => { - 'item' => '100' + 'item' => '100' } - } - }, - 'item' => [ - '10', - '20', - '30' - ], - 'v1' => '123', - 'v2' => '19.03', - 'v3' => 'Johannes', - 'v4' => 'this is a line of text', - 'v5' => 'This is a text block + } + }, + 'item' => [ + '10', + '20', + '30' + ], + 'v1' => '123', + 'v2' => '19.03', + 'v3' => 'Johannes', + 'v4' => 'this is a line of text', + 'v5' => 'This is a text block This is a text block', - 'v6' => 'search.cpan.org', - 'v8' => 'root', - 'v10' => '22', - 'v11' => 'http://search.cpan.org/~tlinden/?ignore¬=1', - 'v12' => '192.168.1.101/18', - 'v13' => '10.0.0.193', - 'v14' => '/etc/ssh/sshd.conf', - 'v15' => 'MANIFEST', - 'v16' => '\' this is a quoted string \'', - 'v171' => qr([0-9]+), - 'v172' => 'qr([0-9]+)', - 'v18' => 'Doesnt contain any variables', - 'v19' => '3ffe:1900:4545:3:200:f8ff:fe21:67cf', - 'v20' => 'fe80:0:0:0:200:f8ff:fe21:67cf', - 'v21' => 'fe80::200:f8ff:fe21:67cf', - 'v22' => 'ff02:0:0:0:0:0:0:1', - 'v23' => 'ff02::1', + 'v6' => 'search.cpan.org', + 'v8' => 'root', + 'v10' => '22', + 'v11' => 'http://search.cpan.org/~tlinden/?ignore¬=1', + 'v12' => '192.168.1.101/18', + 'v13' => '10.0.0.193', + 'v14' => '/etc/ssh/sshd.conf', + 'v15' => 'MANIFEST', + 'v16' => '\' this is a quoted string \'', + 'v171' => qr([0-9]+), + 'v172' => 'qr([0-9]+)', + 'v18' => 'Doesnt contain any variables', + 'v19' => '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + 'v20' => 'fe80:0:0:0:200:f8ff:fe21:67cf', + 'v21' => 'fe80::200:f8ff:fe21:67cf', + 'v22' => 'ff02:0:0:0:0:0:0:1', + 'v23' => 'ff02::1', 'v24' => '::ffff:192.0.2.128', 'v25' => '::', 'v26' => '2001:db8:dead:beef::b00c/64', @@ -104,228 +105,234 @@ my $cfg = { 'v28' => '$ten', 'AoA' => [ - [ qw{ 10 11 12 13 } ], - [ qw{ 20 21 22 23 } ], - [ qw{ 30 31 32 33 } ], - ], + [ qw{ 10 11 12 13 } ], + [ qw{ 20 21 22 23 } ], + [ qw{ 30 31 32 33 } ], + ], 'AoH' => [ - { fullname => 'Homer Simpson', user => 'homer', uid => 100 }, - { fullname => 'Bart Simpson', user => 'bart', uid => 101 }, - { fullname => 'Lisa Simpson', user => 'lisa', uid => 102 }, - ], + { + fullname => 'Homer Simpson', user => 'homer', uid => 100 }, + { + fullname => 'Bart Simpson', user => 'bart', uid => 101 }, + { + fullname => 'Lisa Simpson', user => 'lisa', uid => 102 }, + ], 'HoH' => { - father => { fullname => 'Homer Simpson', user => 'homer' }, - son => { fullname => 'Bart Simpson', user => 'bart' }, - daughter => { fullname => 'Lisa Simpson', user => 'lisa' }, - }, + father => { fullname => 'Homer Simpson', user => 'homer' }, + son => { fullname => 'Bart Simpson', user => 'bart' }, + daughter => { fullname => 'Lisa Simpson', user => 'lisa' }, + }, - 'r1' => 85, - }; + 'r1' => 85, + }; my $v = new_ok('Data::Validate::Struct', [ $ref ]); ok ($v->validate($cfg), "validate a reference against a OK config"); - # check failure matching my @failure = ( - { - cfg => q(acht), - type => q(int), - descr => 'int', - errors => 1, - }, + { + cfg => q(acht), + type => q(int), + descr => 'int', + errors => 1, + }, - { - cfg => q(27^8), - type => q(number), - descr => 'number', - errors => 1, - }, + { + cfg => q(27^8), + type => q(number), + descr => 'number', + errors => 1, + }, - { - cfg => q(two words), - type => q(word), - descr => 'word', - errors => 1, - }, + { + cfg => q(two words), + type => q(word), + descr => 'word', + errors => 1, + }, - { - cfg => qq(< q(line), - descr => 'line', - errors => 1, - }, + { + cfg => qq(< q(line), + descr => 'line', + errors => 1, + }, - { - cfg => q(ätz), - type => q(hostname), - descr => 'hostname', - errors => 1, - }, + { + cfg => q(ätz), + type => q(hostname), + descr => 'hostname', + errors => 1, + }, - { - cfg => q(gibtsnet123456790.intern), - type => q(resolvablehost), - descr => 'resolvablehost', - errors => 1, - }, + { + cfg => q(gibtsnet123456790.intern), + type => q(resolvablehost), + descr => 'resolvablehost', + errors => 1, + }, - { - cfg => q(äüö), - type => q(user), - descr => 'user', - errors => 1, - }, + { + cfg => q(äüö), + type => q(user), + descr => 'user', + errors => 1, + }, - { - cfg => q(äüö), - type => q(group), - descr => 'group', - errors => 1, - }, + { + cfg => q(äüö), + type => q(group), + descr => 'group', + errors => 1, + }, - { - cfg => q(234234444), - type => q(port), - descr => 'port', - errors => 1, - }, + { + cfg => q(234234444), + type => q(port), + descr => 'port', + errors => 1, + }, - { - cfg => q(unknown:/unsinnüäö), - type => q(uri), - descr => 'uri', - errors => 1, - }, + { + cfg => q(unknown:/unsinnüäö), + type => q(uri), + descr => 'uri', + errors => 1, + }, - { - cfg => q(1.1.1.1/33), - type => q(cidrv4), - descr => 'cidrv4', - errors => 1, - }, + { + cfg => q(1.1.1.1/33), + type => q(cidrv4), + descr => 'cidrv4', + errors => 1, + }, - { - cfg => q(300.1.1.1), - type => q(ipv4), - descr => 'ipv4', - errors => 1, - }, + { + cfg => q(300.1.1.1), + type => q(ipv4), + descr => 'ipv4', + errors => 1, + }, - { - cfg => q(üäö), - type => q(fileexists), - descr => 'fileexists', - errors => 1, - }, + { + cfg => q(üäö), + type => q(fileexists), + descr => 'fileexists', + errors => 1, + }, - { - cfg => q(not quoted), - type => q(quoted), - descr => 'quoted', - errors => 1, - }, + { + cfg => q(not quoted), + type => q(quoted), + descr => 'quoted', + errors => 1, + }, - { - cfg => q(no regex), - type => q(regex), - descr => 'regex', - errors => 1, - }, + { + cfg => q(no regex), + type => q(regex), + descr => 'regex', + errors => 1, + }, - { - cfg => q($contains some $vars), - type => q(novars), - descr => 'novars', - errors => 1, - }, + { + cfg => q($contains some $vars), + type => q(novars), + descr => 'novars', + errors => 1, + }, - { - cfg => q(2001:db8::dead::beef), - type => q(ipv6), - descr => 'ipv6', - errors => 1, - }, + { + cfg => q(2001:db8::dead::beef), + type => q(ipv6), + descr => 'ipv6', + errors => 1, + }, - { - cfg => q(2001:db8:dead:beef::1/129), - type => q(cidrv6), - descr => 'cidrv6', - errors => 1, - }, + { + cfg => q(2001:db8:dead:beef::1/129), + type => q(cidrv6), + descr => 'cidrv6', + errors => 1, + }, - { - cfg => [ - [ qw{ 10 11 12 13 } ], - [ qw{ 'twenty' 21 22 23 } ], - [ qw{ 30 31 32.0 33 } ], - ], + { + cfg => [ + [ qw{ 10 11 12 13 } ], + [ qw{ 'twenty' 21 22 23 } ], + [ qw{ 30 31 32.0 33 } ], + ], - type => [ [ 'int' ] ], + type => [ [ 'int' ] ], - descr => 'array of arrays', - errors => 2, - }, + descr => 'array of arrays', + errors => 2, + }, - { - cfg => [ - { fullname => 'Homer Simpson', user => 'homer', uid => 100 }, - { fullname => 'Bart Simpson', user => ':bart', uid => 101 }, - { fullname => 'Lisa Simpson', user => 'lisa', uid => '102' }, - ], + { + cfg => [ + { + fullname => 'Homer Simpson', user => 'homer', uid => 100 }, + { + fullname => 'Bart Simpson', user => ':bart', uid => 101 }, + { + fullname => 'Lisa Simpson', user => 'lisa', uid => '102' }, + ], - type => [ - { fullname => 'text', user => 'word', uid => 'int' } - ], + type => [ + { + fullname => 'text', user => 'word', uid => 'int' } + ], - descr => 'array of hashes', - errors => 1, - }, + descr => 'array of hashes', + errors => 1, + }, - { - cfg => { - father => { fullname => 'Homer Simpson', user => 'homer', uid => 100 }, - son => { fullname => 'Bart Simpson', user => 'bart', uid => 'one hundred one' }, - daughter => { fullname => 'Lisa Simpson', user => 'lisa:', uid => 'one hundred two' }, - }, + { + cfg => { + father => { fullname => 'Homer Simpson', user => 'homer', uid => 100 }, + son => { fullname => 'Bart Simpson', user => 'bart', uid => 'one hundred one' }, + daughter => { fullname => 'Lisa Simpson', user => 'lisa:', uid => 'one hundred two' }, + }, - type => { - father => { fullname => 'text', user => 'word', uid => 'int' }, - son => { fullname => 'text', user => 'word', uid => 'int' }, - daughter => { fullname => 'text', user => 'word', uid => 'int' }, - }, + type => { + father => { fullname => 'text', user => 'word', uid => 'int' }, + son => { fullname => 'text', user => 'word', uid => 'int' }, + daughter => { fullname => 'text', user => 'word', uid => 'int' }, + }, - descr => 'hash of hashes', - errors => 3, - }, + descr => 'hash of hashes', + errors => 3, + }, - { - cfg => { - name => 'Foo Bar', - age => 42, - }, + { + cfg => { + name => 'Foo Bar', + age => 42, + }, - type => { - name => 'text', - age => 'int', - address => 'text', - }, + type => { + name => 'text', + age => 'int', + address => 'text', + }, - descr => 'Missing required field', - errors => 1, - }, + descr => 'Missing required field', + errors => 1, + }, - { - cfg => 100, - type => 'range(200-1000)', - descr => 'value outside dynamic range', - errors => 1, - }, + { + cfg => 100, + type => 'range(200-1000)', + descr => 'value outside dynamic range', + errors => 1, + }, -); + ); foreach my $test (@failure) { my $ref = { v => $test->{type} }; @@ -334,13 +341,12 @@ foreach my $test (@failure) { #$v->debug(); my $result = $v->validate($cfg); 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; unless ($result) { is @{$v->errors}, $errors, "Caught failure for '$descr'"; - } - else { + } else { fail("Couldn't catch invalid '$test->{descr}'"); } } @@ -349,45 +355,45 @@ foreach my $test (@failure) { # clean old object undef $v; $v = Data::Validate::Struct->new({ - h1 => { h2 => { item => 'int' } } -}); + h1 => { h2 => { item => 'int' } } + }); ok !$v->validate({ - h1 => { h2 => { item => 'qux' } } -}), 'item is not an h1 => h2 => int'; + h1 => { h2 => { item => 'qux' } } + }), 'item is not an h1 => h2 => int'; is $v->errstr, q{'qux' doesn't match 'int' at 'h1 => h2'}, 'correct error trace'; # adding custom type my $ref3 = { - v1 => 'address', - v2 => 'list', - v3 => 'noob', - v4 => 'nonoob', -}; + v1 => 'address', + v2 => 'list', + v3 => 'noob', + v4 => 'nonoob', + }; my $cfg3 = { - v1 => 'Marblestreet 15', - v2 => 'a1, b2, b3', - v3 => 42, - v4 => 43, -}; + v1 => 'Marblestreet 15', + v2 => 'a1, b2, b3', + v3 => 42, + v4 => 43, + }; my $v3 = new Data::Validate::Struct($ref3); # add via hash note('added via hash'); my %h = ( - address => qr(^\w+\s\s*\d+$) -); + address => qr(^\w+\s\s*\d+$) + ); $v3->type(%h); # add via hash ref note('added via hash ref'); $v3->type({ list => - sub { - my $list = $_[0]; - my @list = split /\s*,\s*/, $list; - return scalar @list > 1; - } -}); + sub { + my $list = $_[0]; + my @list = split /\s*,\s*/, $list; + return scalar @list > 1; + } + }); # add via key => value 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 => 8}), "cache check second run, no error"); -# different references -my $v5 = Data::Validate::Struct->new({ foo => [{bar => 'int'}]}); -ok(!$v5->validate({foo=>{bar=>10}})); - +# optional array, see: +# https://github.com/TLINDEN/Data-Validate-Struct/issues/7 +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(); -