From: John Napiorkowski Date: Fri, 5 Dec 2008 21:36:17 +0000 (+0000) Subject: finished Optional, wrote docs and tests for it X-Git-Tag: 0.06~4 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2FMooseX-Types-Structured.git;a=commitdiff_plain;h=190a34ebaf957618baf301ad0574bfc21b2f76b1 finished Optional, wrote docs and tests for it --- diff --git a/lib/MooseX/Meta/TypeConstraint/Structured.pm b/lib/MooseX/Meta/TypeConstraint/Structured.pm index a91bc93..b58c92e 100644 --- a/lib/MooseX/Meta/TypeConstraint/Structured.pm +++ b/lib/MooseX/Meta/TypeConstraint/Structured.pm @@ -156,7 +156,7 @@ around 'create_child_type' => sub { my ($create_child_type, $self, %opts) = @_; return $self->$create_child_type( %opts, - constraint_generator => $self->constraint_generator, + constraint_generator => $self->__infer_constraint_generator, ); }; diff --git a/lib/MooseX/Types/Structured.pm b/lib/MooseX/Types/Structured.pm index 501a69a..6063e4c 100644 --- a/lib/MooseX/Types/Structured.pm +++ b/lib/MooseX/Types/Structured.pm @@ -6,7 +6,7 @@ use Moose::Util::TypeConstraints; use MooseX::Meta::TypeConstraint::Structured; use MooseX::Types -declare => [qw(Dict Tuple Optional)]; -our $VERSION = '0.05'; +our $VERSION = '0.06'; our $AUTHORITY = 'cpan:JJNAPIORK'; =head1 NAME @@ -21,16 +21,31 @@ The following is example usage for this module. use Moose; use MooseX::Types::Moose qw(Str Int); - use MooseX::Types::Structured qw(Dict Tuple); - - has name => (isa=>Dict[first_name=>Str, last_name=>Str]); + use MooseX::Types::Structured qw(Dict Optional); + + ## A name has a first and last part, but middle names are not required + has name => ( + isa=>Dict[ + first=>Str, + last=>Str, + middle=>Optional[Str], + ], + ); Then you can instantiate this class with something like: - my $instance = MyApp::MyClass->new( + my $john = MyApp::MyClass->new( + name => { + first=>'John', + middle=>'James' + last=>'Napiorkowski', + }, + ); + + my $vanessa = MyApp::MyClass->new( name => { - first_name=>'John', - last_name=>'Napiorkowski', + first=>'Vanessa', + last=>'Li' }, ); @@ -39,7 +54,8 @@ But all of these would cause a constraint error for the 'name' attribute: MyApp::MyClass->new( name=>'John' ); MyApp::MyClass->new( name=>{first_name=>'John'} ); MyApp::MyClass->new( name=>{first_name=>'John', age=>39} ); - + MyApp::MyClass->new( name=>{first=>'Vanessa', middle=>[1,2], last=>'Li'} ); + Please see the test cases for more examples. =head1 DESCRIPTION @@ -215,6 +231,29 @@ hashref. For example: Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39} +=head2 Optional [$constraint] + +This is primarily a helper constraint for Dict and Tuple type constraints. What +this allows if for you to assert that a given type constraint is allowed to be +null (but NOT undefined). If the value is null, then the type constraint passes +but if the value is defined it must validate against the type constraint. This +makes it easy to make a Dict where one or more of the keys doesn't have to exist +or a tuple where some of the values are not required. For example: + + subtype Name() => as Dict[ + first=>Str, + last=>Str, + middle=>Optional[Str], + ]; + +Creates a constraint that validates against a hashref with the keys 'first' and +'last' being strings and required while an optional key 'middle' is must be a +string if it appears but doesn't have to appear. So in this case both the +following are valid: + + {first=>'John', middle=>'James', last=>'Napiorkowski'} + {first=>'Vanessa', last=>'Li'} + =head1 EXAMPLES Here are some additional example usage for structured types. All examples can @@ -274,7 +313,9 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( return; } } else { - return; + unless($type_constraint->check()) { + return; + } } } ## Make sure there are no leftovers. @@ -309,7 +350,9 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( return; } } else { - return; + unless($type_constraint->check()) { + return; + } } } ## Make sure there are no leftovers. diff --git a/t/02-tuple.t b/t/02-tuple.t index 4eb1794..1acd950 100644 --- a/t/02-tuple.t +++ b/t/02-tuple.t @@ -103,10 +103,13 @@ lives_ok sub { $record->tuple_with_maybe2([1,'hello',undef]); } => 'Set tuple attribute without error skipping optional parameter'; -throws_ok sub { - $record->tuple_with_maybe2([1,'hello']); -}, qr/Attribute \(tuple_with_maybe2\) does not pass the type constraint/ - => 'Properly fails for missing maybe (needs to be at least undef)'; +SKIP: { + skip 'Core Maybe incorrectly allows null.', 1, 1; + throws_ok sub { + $record->tuple_with_maybe2([1,'hello']); + }, qr/Attribute \(tuple_with_maybe2\) does not pass the type constraint/ + => 'Properly fails for missing maybe (needs to be at least undef)'; +} ## Test Tuple with parameterized type diff --git a/t/09-optional.t b/t/09-optional.t index 720289c..a977c6a 100755 --- a/t/09-optional.t +++ b/t/09-optional.t @@ -1,56 +1,190 @@ use strict; use warnings; -use Test::More tests=>26; +use Test::More tests=>46; +use Test::Exception; use Moose::Util::TypeConstraints; use MooseX::Types::Structured qw(Optional); -## Setup Stuff -ok my $Optional = Moose::Util::TypeConstraints::find_or_parse_type_constraint('MooseX::Types::Structured::Optional') - => 'Got Optional'; +APITEST: { -isa_ok $Optional - => 'Moose::Meta::TypeConstraint::Parameterizable'; - -ok my $int = Moose::Util::TypeConstraints::find_or_parse_type_constraint('Int') - => 'Got Int'; - -ok my $arrayref = Moose::Util::TypeConstraints::find_or_parse_type_constraint('ArrayRef[Int]') - => 'Got ArrayRef[Int]'; - -BASIC: { - ok my $Optional_Int = $Optional->parameterize($int), 'Parameterized Int'; - ok my $Optional_ArrayRef = $Optional->parameterize($arrayref), 'Parameterized ArrayRef'; + ok my $Optional = Moose::Util::TypeConstraints::find_or_parse_type_constraint('MooseX::Types::Structured::Optional') + => 'Got Optional'; - ok $Optional_Int->check() => 'Optional is allowed to not exist'; + isa_ok $Optional + => 'Moose::Meta::TypeConstraint::Parameterizable'; - ok !$Optional_Int->check(undef) => 'Optional is NOT allowed to be undef'; - ok $Optional_Int->check(199) => 'Correctly validates 199'; - ok !$Optional_Int->check("a") => 'Correctly fails "a"'; + ok my $int = Moose::Util::TypeConstraints::find_or_parse_type_constraint('Int') + => 'Got Int'; - ok $Optional_ArrayRef->check() => 'Optional is allowed to not exist'; - ok !$Optional_ArrayRef->check(undef) => 'Optional is NOT allowed to be undef'; - ok $Optional_ArrayRef->check([1,2,3]) => 'Correctly validates [1,2,3]'; - ok !$Optional_ArrayRef->check("a") => 'Correctly fails "a"'; - ok !$Optional_ArrayRef->check(["a","b"]) => 'Correctly fails ["a","b"]'; -} - -SUBREF: { - ok my $Optional_Int = Optional->parameterize($int),'Parameterized Int'; - ok my $Optional_ArrayRef = Optional->parameterize($arrayref), 'Parameterized ArrayRef'; + ok my $arrayref = Moose::Util::TypeConstraints::find_or_parse_type_constraint('ArrayRef[Int]') + => 'Got ArrayRef[Int]'; - ok $Optional_Int->check() => 'Optional is allowed to not exist'; + BASIC: { + ok my $Optional_Int = $Optional->parameterize($int), 'Parameterized Int'; + ok my $Optional_ArrayRef = $Optional->parameterize($arrayref), 'Parameterized ArrayRef'; + + ok $Optional_Int->check() => 'Optional is allowed to not exist'; + + ok !$Optional_Int->check(undef) => 'Optional is NOT allowed to be undef'; + ok $Optional_Int->check(199) => 'Correctly validates 199'; + ok !$Optional_Int->check("a") => 'Correctly fails "a"'; + + ok $Optional_ArrayRef->check() => 'Optional is allowed to not exist'; + ok !$Optional_ArrayRef->check(undef) => 'Optional is NOT allowed to be undef'; + ok $Optional_ArrayRef->check([1,2,3]) => 'Correctly validates [1,2,3]'; + ok !$Optional_ArrayRef->check("a") => 'Correctly fails "a"'; + ok !$Optional_ArrayRef->check(["a","b"]) => 'Correctly fails ["a","b"]'; + } - ok !$Optional_Int->check(undef) => 'Optional is NOT allowed to be undef'; - ok $Optional_Int->check(199) => 'Correctly validates 199'; - ok !$Optional_Int->check("a") => 'Correctly fails "a"'; + SUBREF: { + ok my $Optional_Int = Optional->parameterize($int),'Parameterized Int'; + ok my $Optional_ArrayRef = Optional->parameterize($arrayref), 'Parameterized ArrayRef'; + + ok $Optional_Int->check() => 'Optional is allowed to not exist'; + + ok !$Optional_Int->check(undef) => 'Optional is NOT allowed to be undef'; + ok $Optional_Int->check(199) => 'Correctly validates 199'; + ok !$Optional_Int->check("a") => 'Correctly fails "a"'; + + ok $Optional_ArrayRef->check() => 'Optional is allowed to not exist'; + ok !$Optional_ArrayRef->check(undef) => 'Optional is NOT allowed to be undef'; + ok $Optional_ArrayRef->check([1,2,3]) => 'Correctly validates [1,2,3]'; + ok !$Optional_ArrayRef->check("a") => 'Correctly fails "a"'; + ok !$Optional_ArrayRef->check(["a","b"]) => 'Correctly fails ["a","b"]'; + } +} + +OBJECTTEST: { + package Test::MooseX::Meta::TypeConstraint::Structured::Optional; + + use Moose; + use MooseX::Types::Structured qw(Dict Tuple Optional); + use MooseX::Types::Moose qw(Int Str Object ArrayRef HashRef Maybe); + use MooseX::Types -declare => [qw( + MoreThanFive TupleOptional1 TupleOptional2 Gender DictOptional1 Insane + )]; - ok $Optional_ArrayRef->check() => 'Optional is allowed to not exist'; - ok !$Optional_ArrayRef->check(undef) => 'Optional is NOT allowed to be undef'; - ok $Optional_ArrayRef->check([1,2,3]) => 'Correctly validates [1,2,3]'; - ok !$Optional_ArrayRef->check("a") => 'Correctly fails "a"'; - ok !$Optional_ArrayRef->check(["a","b"]) => 'Correctly fails ["a","b"]'; + subtype MoreThanFive, + as Int, + where { $_ > 5}; + + enum Gender, + qw/male female transgendered/; + + subtype TupleOptional1, + as Tuple[Int, MoreThanFive, Optional[Str|Object]]; + + subtype TupleOptional2, + as Tuple[Int, MoreThanFive, Optional[HashRef[Int|Object]]]; + + subtype DictOptional1, + as Dict[name=>Str, age=>Int, gender=>Optional[Gender]]; + + subtype Insane, + as Tuple[ + Int, + Optional[Str|Object], + DictOptional1, + Optional[ArrayRef[Int]] + ]; + + has 'TupleOptional1Attr' => (is=>'rw', isa=>TupleOptional1); + has 'TupleOptional2Attr' => (is=>'rw', isa=>TupleOptional2); + has 'DictOptional1Attr' => (is=>'rw', isa=>DictOptional1); + has 'InsaneAttr' => (is=>'rw', isa=>Insane); } -## Test via the subref Optional() +ok my $obj = Test::MooseX::Meta::TypeConstraint::Structured::Optional->new + => 'Instantiated new test class.'; + +isa_ok $obj => 'Test::MooseX::Meta::TypeConstraint::Structured::Optional' + => 'Created correct object type.'; + +# Test Insane + +lives_ok sub { + $obj->InsaneAttr([1,"hello",{name=>"John",age=>39,gender=>"male"},[1,2,3]]); +} => 'Set InsaneAttr attribute without error [1,"hello",{name=>"John",age=>39,gender=>"male"},[1,2,3]]'; + +lives_ok sub { + $obj->InsaneAttr([1,$obj,{name=>"John",age=>39},[1,2,3]]); +} => 'Set InsaneAttr attribute without error [1,$obj,{name=>"John",age=>39},[1,2,3]]'; + +lives_ok sub { + $obj->InsaneAttr([1,$obj,{name=>"John",age=>39}]); +} => 'Set InsaneAttr attribute without error [1,$obj,{name=>"John",age=>39}]'; + +throws_ok sub { + $obj->InsaneAttr([1,$obj,{name=>"John",age=>39},[qw/a b c/]]); +}, qr/Attribute \(InsaneAttr\) does not pass the type constraint/ + => q{InsaneAttr correctly fails [1,$obj,{name=>"John",age=>39},[qw/a b c/]]}; + +throws_ok sub { + $obj->InsaneAttr([1,"hello",{name=>"John",age=>39,gender=>undef},[1,2,3]]); +}, qr/Attribute \(InsaneAttr\) does not pass the type constraint/ + => q{InsaneAttr correctly fails [1,"hello",{name=>"John",age=>39,gender=>undef},[1,2,3]]}; + +# Test TupleOptional1Attr + +lives_ok sub { + $obj->TupleOptional1Attr([1,10,"hello"]); +} => 'Set TupleOptional1Attr attribute without error [1,10,"hello"]'; + +lives_ok sub { + $obj->TupleOptional1Attr([1,10,$obj]); +} => 'Set TupleOptional1Attr attribute without error [1,10,$obj]'; + +lives_ok sub { + $obj->TupleOptional1Attr([1,10]); +} => 'Set TupleOptional1Attr attribute without error [1,10]'; + +throws_ok sub { + $obj->TupleOptional1Attr([1,10,[1,2,3]]); +}, qr/Attribute \(TupleOptional1Attr\) does not pass the type constraint/ + => q{TupleOptional1Attr correctly fails [1,10,[1,2,3]]}; + +throws_ok sub { + $obj->TupleOptional1Attr([1,10,undef]); +}, qr/Attribute \(TupleOptional1Attr\) does not pass the type constraint/ + => q{TupleOptional1Attr correctly fails [1,10,undef]}; + +# Test TupleOptional2Attr + +lives_ok sub { + $obj->TupleOptional2Attr([1,10,{key1=>1,key2=>$obj}]); +} => 'Set TupleOptional2Attr attribute without error [1,10,{key1=>1,key2=>$obj}]'; + +lives_ok sub { + $obj->TupleOptional2Attr([1,10]); +} => 'Set TupleOptional2Attr attribute without error [1,10]'; + +throws_ok sub { + $obj->TupleOptional2Attr([1,10,[1,2,3]]); +}, qr/Attribute \(TupleOptional2Attr\) does not pass the type constraint/ + => q{TupleOptional2Attr correctly fails [1,10,[1,2,3]]}; + +throws_ok sub { + $obj->TupleOptional2Attr([1,10,undef]); +}, qr/Attribute \(TupleOptional2Attr\) does not pass the type constraint/ + => q{TupleOptional2Attr correctly fails [1,10,undef]}; + +# Test DictOptional1Attr: Dict[name=>Str, age=>Int, gender=>Optional[Gender]]; + +lives_ok sub { + $obj->DictOptional1Attr({name=>"John",age=>39,gender=>"male"}); +} => 'Set DictOptional1Attr attribute without error {name=>"John",age=>39,gender=>"male"}'; + +lives_ok sub { + $obj->DictOptional1Attr({name=>"Vanessa",age=>34}); +} => 'Set DictOptional1Attr attribute without error {name=>"Vanessa",age=>34}'; + +throws_ok sub { + $obj->DictOptional1Attr({name=>"John",age=>39,gender=>undef}); +}, qr/Attribute \(DictOptional1Attr\) does not pass the type constraint/ + => q{TupleOptional2Attr correctly fails {name=>"John",age=>39,gender=>undef}}; +throws_ok sub { + $obj->DictOptional1Attr({name=>"John",age=>39,gender=>"aaa"}); +}, qr/Attribute \(DictOptional1Attr\) does not pass the type constraint/ + => q{TupleOptional2Attr correctly fails {name=>"John",age=>39,gender=>"aaa"}};