proper support for an Optional keyword
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
CommitLineData
d24da8ec 1package MooseX::Types::Structured;
2
6c2f284c 3use Moose;
4use Moose::Util::TypeConstraints;
5use MooseX::Meta::TypeConstraint::Structured::Positional;
6use MooseX::Meta::TypeConstraint::Structured::Named;
7#use MooseX::Types::Moose qw();
8#use MooseX::Types -declare => [qw( Dict Tuple Optional )];
9 use Sub::Exporter
13cf7c3b 10 -setup => { exports => [ qw(Dict Tuple Optional) ] };
6c2f284c 11
d24da8ec 12our $VERSION = '0.01';
13our $AUTHORITY = 'cpan:JJNAPIORK';
14
15=head1 NAME
16
17MooseX::Types::Structured; Structured Type Constraints for Moose
18
19=head1 SYNOPSIS
20
6c2f284c 21The following is example usage for this module. You can define a class that has
22an attribute with a structured type like so:
23
24 package MyApp::MyClass;
25
26 use Moose;
27 use MooseX::Types::Moose qw(Str Int);
28 use MooseX::Types::Structured qw(Dict Tuple);
29
30 has name => (isa=>Dict[first_name=>Str, last_name=>Str]);
31
32Then you can instantiate this class with something like:
33
34 my $instance = MyApp::MyClass->new(
35 name=>{first_name=>'John', last_name=>'Napiorkowski'},
36 );
d24da8ec 37
6c2f284c 38But all of these would cause an error:
39
40 my $instance = MyApp::MyClass->new(name=>'John');
41 my $instance = MyApp::MyClass->new(name=>{first_name=>'John'});
42 my $instance = MyApp::MyClass->new(name=>{first_name=>'John', age=>39});
43
44Please see the test cases for more examples.
d24da8ec 45
46=head1 DESCRIPTION
47
6c2f284c 48This type library enables structured type constraints. Basically, this is very
49similar to parameterized constraints that are built into the core Moose types,
50except that you are allowed to define the container's entire structure. For
51example, you could define a parameterized constraint like so:
52
53 subtype HashOfInts, as Hashref[Int];
54
55which would constraint a value to something like [1,2,3,...] and so one. A
56structured constraint like so:
57
58 subtype StringFollowedByInt, as Tuple[Str,Int];
59
60would constrain it's value to something like ['hello', 111];
61
62These structures can be as simple or elaborate as you wish. You can even
63combine various structured, parameterized and simple constraints all together:
64
65 subtype crazy, as Tuple[Int, Dict[name=>Str, age=>Int], ArrayRef[Int]];
66
67Which would match "[1, {name=>'John', age=>25},[10,11,12]]".
68
69You should exercise some care as to whether or not your complex structured
70constraints would be better off contained by a real object as in the following
71example:
72
73 {
74 package MyApp::MyStruct;
75 use Moose;
76
77 has $_ for qw(name age);
78
79 package MyApp::MyClass;
80 use Moose;
81
82 has person => (isa=>'MyApp::MyStruct');
83 }
84
85 my $instance = MyApp::MyClass
86 ->new( person=>MyApp::MyStruct->new(name=>'John', age=>39) );
87
88This method may take some additional time to setup but will give you more
89flexibility. However, structured constraints are highly compatible with this
90method, granting some interesting possibilities for coercion. Try:
91
92 subtype 'MyStruct',
93 as 'MyApp::MyStruct';
94
95 coerce 'MyStruct',
96 from (Dict[name=>Str, age=>Int]),
97 via {
98 MyApp::MyStruct->new(%$_);
99 },
100 from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
101 via {
102 my $name = _->{first_name} .' '. $_->{last_name};
103 my $age = $_->{dob} - DateTime->now;
104 MyApp::MyStruct->new(
105 name=>$name,
106 age=>$age->years );
107 };
108
d24da8ec 109
110=head1 TYPES
111
112This class defines the following types and subtypes.
113
114=cut
115
13cf7c3b 116sub Optional($) {
117 return bless {args=>shift}, 'MooseX::Types::Optional';
118}
119
120sub Tuple($) {
121 my ($args, $optional) = _normalize_args(@_);
6c2f284c 122 my @args = @$args;
123 my @optional = ref $optional eq 'ARRAY' ? @$optional : ();
124
125 return MooseX::Meta::TypeConstraint::Structured::Positional->new(
126 name => 'Tuple',
127 parent => find_type_constraint('ArrayRef'),
128 package_defined_in => __PACKAGE__,
129 signature => [map {
130 _normalize_type_constraint($_);
131 } @args],
132 optional_signature => [map {
133 _normalize_type_constraint($_);
134 } @optional],
135 );
136}
137
13cf7c3b 138sub Dict($) {
139 my ($args, $optional) = _normalize_args(@_);
6c2f284c 140 my %args = @$args;
141 my %optional = ref $optional eq 'ARRAY' ? @$optional : ();
142
143 return MooseX::Meta::TypeConstraint::Structured::Named->new(
144 name => 'Dict',
145 parent => find_type_constraint('HashRef'),
146 package_defined_in => __PACKAGE__,
147 signature => {map {
148 $_ => _normalize_type_constraint($args{$_});
149 } keys %args},
150 optional_signature => {map {
151 $_ => _normalize_type_constraint($optional{$_});
152 } keys %optional},
153 );
154}
155
13cf7c3b 156sub _normalize_args {
157 my $args = shift @_;
158 confess "Structured Type Constraints can only accept an ArrayRef as arguments"
159 unless ref $args eq 'ARRAY';
160
161 my @args = @$args;
162 my $last = pop @args;
163
164 if(blessed $last && $last->isa('MooseX::Types::Optional')) {
165 return ([@args], $last->{args});
166 } else {
167 return ([@args, $last]);
168 }
169
170}
6c2f284c 171sub _normalize_type_constraint {
172 my $tc = shift @_;
424215da 173
174 ## If incoming is an object, we will assume it's something that implements
175 ## what a type constraint is. We should probably have a Role for this...
176 if(defined $tc && blessed $tc) {
6c2f284c 177 return $tc;
178 } elsif($tc) {
179 return Moose::Util::TypeConstraints::find_or_parse_type_constraint($tc);
180 }
181}
d24da8ec 182
183=head1 SEE ALSO
184
185The following modules or resources may be of interest.
186
187L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>
188
189=head1 BUGS
190
191No known or reported bugs.
192
193=head1 AUTHOR
194
195John Napiorkowski, C<< <jjnapiork@cpan.org> >>
196
197=head1 COPYRIGHT & LICENSE
198
199This program is free software; you can redistribute it and/or modify
200it under the same terms as Perl itself.
201
202=cut
203
6c2f284c 2041;