doc formatting and spelling fixes
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Meta / TypeConstraint / Structured.pm
CommitLineData
59deb858 1package ## Hide from PAUSE
2 MooseX::Meta::TypeConstraint::Structured;
8b810e83 3# ABSTRACT: Structured type constraints.
a30fa891 4
5use Moose;
797510e3 6use Devel::PartialDump;
a30fa891 7use Moose::Util::TypeConstraints ();
16aea7bf 8use MooseX::Meta::TypeCoercion::Structured;
a30fa891 9extends 'Moose::Meta::TypeConstraint';
10
a30fa891 11
12=head1 DESCRIPTION
13
14A structure is a set of L<Moose::Meta::TypeConstraint> that are 'aggregated' in
15such a way as that they are all applied to an incoming list of arguments. The
8b810e83 16idea here is that a Type Constraint could be something like, "An C<Int> followed by
17an C<Int> and then a C<Str>" and that this could be done so with a declaration like:
a30fa891 18
19 Tuple[Int,Int,Str]; ## Example syntax
8dbdca20 20
8b810e83 21So a structure is a list of type constraints (the C<Int,Int,Str> in the above
a30fa891 22example) which are intended to function together.
23
8dbdca20 24=attr type_constraints
a30fa891 25
26A list of L<Moose::Meta::TypeConstraint> objects.
27
28=cut
29
30has 'type_constraints' => (
31 is=>'ro',
32 isa=>'Ref',
33 predicate=>'has_type_constraints',
34);
35
8dbdca20 36=attr constraint_generator
a30fa891 37
8b810e83 38=for stopwords subref
39
a30fa891 40A subref or closure that contains the way we validate incoming values against
41a set of type constraints.
42
43=cut
44
e327145a 45has 'constraint_generator' => (
46 is=>'ro',
47 isa=>'CodeRef',
48 predicate=>'has_constraint_generator',
49);
a30fa891 50
c988e3f2 51has coercion => (
52 is => 'ro',
53 isa => 'Object',
54 builder => '_build_coercion',
55);
56
c988e3f2 57sub _build_coercion {
58 my ($self) = @_;
59 return MooseX::Meta::TypeCoercion::Structured->new(
16aea7bf 60 type_constraint => $self,
c988e3f2 61 );
62}
16aea7bf 63
8dbdca20 64=method validate
7559b71f 65
8b810e83 66Messing with validate so that we can support nicer error messages.
8dbdca20 67
7559b71f 68=cut
69
fbe3dfe7 70sub _clean_message {
71 my $message = shift @_;
72 $message =~s/MooseX::Types::Structured:://g;
73 return $message;
74}
75
7559b71f 76override 'validate' => sub {
9448ea2c 77 my ($self, $value, $message_stack) = @_;
78 unless ($message_stack) {
79 $message_stack = MooseX::Types::Structured::MessageStack->new();
80 }
7559b71f 81
9448ea2c 82 $message_stack->inc_level;
21d0e759 83
9448ea2c 84 if ($self->_compiled_type_constraint->($value, $message_stack)) {
d716430a 85 ## Everything is good, no error message to return
86 return undef;
7559b71f 87 } else {
d716430a 88 ## Whoops, need to figure out the right error message
21d0e759 89 my $args = Devel::PartialDump::dump($value);
9448ea2c 90 $message_stack->dec_level;
91 if($message_stack->has_messages) {
92 if($message_stack->level) {
93 ## we are inside a deeply structured constraint
21d0e759 94 return $self->get_message($args);
95 } else {
9448ea2c 96 my $message_str = $message_stack->as_string;
fbe3dfe7 97 return _clean_message($self->get_message("$args, Internal Validation Error is: $message_str"));
21d0e759 98 }
7559b71f 99 } else {
100 return $self->get_message($args);
101 }
102 }
103};
104
8dbdca20 105=method generate_constraint_for ($type_constraints)
a30fa891 106
107Given some type constraints, use them to generate validation rules for an ref
108of values (to be passed at check time)
109
110=cut
111
112sub generate_constraint_for {
113 my ($self, $type_constraints) = @_;
a4ae4800 114 return $self->constraint_generator->($self, $type_constraints);
a30fa891 115}
116
8b810e83 117=for stopwords parameterize
118
8dbdca20 119=method parameterize (@type_constraints)
a30fa891 120
121Given a ref of type constraints, create a structured type.
122
123=cut
124
125sub parameterize {
16aea7bf 126 my ($self, @type_constraints) = @_;
127 my $class = ref $self;
a30fa891 128 my $name = $self->name .'['. join(',', map {"$_"} @type_constraints) .']';
e327145a 129 my $constraint_generator = $self->__infer_constraint_generator;
67a8bc04 130
16aea7bf 131 return $class->new(
a30fa891 132 name => $name,
133 parent => $self,
134 type_constraints => \@type_constraints,
e327145a 135 constraint_generator => $constraint_generator,
136 );
137}
138
8dbdca20 139=method __infer_constraint_generator
e327145a 140
8b810e83 141=for stopwords servicable
142
e327145a 143This returns a CODEREF which generates a suitable constraint generator. Not
144user servicable, you'll never call this directly.
145
146=cut
147
148sub __infer_constraint_generator {
149 my ($self) = @_;
150 if($self->has_constraint_generator) {
151 return $self->constraint_generator;
152 } else {
153 return sub {
154 ## I'm not sure about this stuff but everything seems to work
67a8bc04 155 my $tc = shift @_;
156 my $merged_tc = [@$tc, @{$self->parent->type_constraints}];
8dbdca20 157 $self->constraint->($merged_tc, @_);
e327145a 158 };
8dbdca20 159 }
a30fa891 160}
161
8dbdca20 162=method compile_type_constraint
a30fa891 163
164hook into compile_type_constraint so we can set the correct validation rules.
165
166=cut
167
168around 'compile_type_constraint' => sub {
169 my ($compile_type_constraint, $self, @args) = @_;
8dbdca20 170
a30fa891 171 if($self->has_type_constraints) {
172 my $type_constraints = $self->type_constraints;
173 my $constraint = $self->generate_constraint_for($type_constraints);
8dbdca20 174 $self->_set_constraint($constraint);
a30fa891 175 }
176
177 return $self->$compile_type_constraint(@args);
178};
179
8dbdca20 180=method create_child_type
a4a88fef 181
182modifier to make sure we get the constraint_generator
183
184=cut
185
186around 'create_child_type' => sub {
187 my ($create_child_type, $self, %opts) = @_;
188 return $self->$create_child_type(
189 %opts,
190a34eb 190 constraint_generator => $self->__infer_constraint_generator,
a4a88fef 191 );
192};
193
8dbdca20 194=method is_a_type_of
a4a88fef 195
8dbdca20 196=method is_subtype_of
a4a88fef 197
8dbdca20 198=method equals
a4a88fef 199
16aea7bf 200Override the base class behavior.
201
202=cut
203
204sub equals {
205 my ( $self, $type_or_name ) = @_;
8c187148 206 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
207 or return;
16aea7bf 208
209 return unless $other->isa(__PACKAGE__);
8dbdca20 210
16aea7bf 211 return (
179af711 212 $self->parent->equals($other->parent)
16aea7bf 213 and
179af711 214 $self->type_constraints_equals($other)
16aea7bf 215 );
216}
217
179af711 218sub is_a_type_of {
219 my ( $self, $type_or_name ) = @_;
8c187148 220 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
221 or return;
179af711 222
223 if ( $other->isa(__PACKAGE__) and @{ $other->type_constraints || [] }) {
179af711 224 if ( $self->parent->is_a_type_of($other->parent) ) {
179af711 225 return $self->_type_constraints_op_all($other, "is_a_type_of");
226 } elsif ( $self->parent->is_a_type_of($other) ) {
227 return 1;
228 # FIXME compare?
229 } else {
230 return 0;
231 }
232 } else {
233 return $self->SUPER::is_a_type_of($other);
234 }
235}
236
237sub is_subtype_of {
238 my ( $self, $type_or_name ) = @_;
8c187148 239 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
240 or return;
179af711 241 if ( $other->isa(__PACKAGE__) ) {
242 if ( $other->type_constraints and $self->type_constraints ) {
243 if ( $self->parent->is_a_type_of($other->parent) ) {
244 return (
245 $self->_type_constraints_op_all($other, "is_a_type_of")
246 and
247 $self->_type_constraints_op_any($other, "is_subtype_of")
248 );
249 } elsif ( $self->parent->is_a_type_of($other) ) {
250 return 1;
251 # FIXME compare?
252 } else {
253 return 0;
254 }
255 } else {
256 if ( $self->type_constraints ) {
257 if ( $self->SUPER::is_subtype_of($other) ) {
258 return 1;
259 } else {
260 return;
261 }
262 } else {
263 return $self->parent->is_subtype_of($other->parent);
264 }
265 }
266 } else {
267 return $self->SUPER::is_subtype_of($other);
268 }
269}
270
8dbdca20 271=method type_constraints_equals
16aea7bf 272
67eec8f7 273Checks to see if the internal type constraints are equal.
16aea7bf 274
275=cut
276
277sub type_constraints_equals {
179af711 278 my ( $self, $other ) = @_;
279 $self->_type_constraints_op_all($other, "equals");
280}
281
282sub _type_constraints_op_all {
283 my ($self, $other, $op) = @_;
284
285 return unless $other->isa(__PACKAGE__);
286
16aea7bf 287 my @self_type_constraints = @{$self->type_constraints||[]};
288 my @other_type_constraints = @{$other->type_constraints||[]};
179af711 289
290 return unless @self_type_constraints == @other_type_constraints;
291
16aea7bf 292 ## Incoming ay be either arrayref or hashref, need top compare both
293 while(@self_type_constraints) {
294 my $self_type_constraint = shift @self_type_constraints;
179af711 295 my $other_type_constraint = shift @other_type_constraints;
8dbdca20 296
179af711 297 $_ = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_)
298 for $self_type_constraint, $other_type_constraint;
16aea7bf 299
fd29a93c 300 my $result = $self_type_constraint->$op($other_type_constraint);
301 return unless $result;
16aea7bf 302 }
8dbdca20 303
16aea7bf 304 return 1; ##If we get this far, everything is good.
305}
306
179af711 307sub _type_constraints_op_any {
308 my ($self, $other, $op) = @_;
309
310 return unless $other->isa(__PACKAGE__);
311
312 my @self_type_constraints = @{$self->type_constraints||[]};
313 my @other_type_constraints = @{$other->type_constraints||[]};
314
315 return unless @self_type_constraints == @other_type_constraints;
316
317 ## Incoming ay be either arrayref or hashref, need top compare both
318 while(@self_type_constraints) {
319 my $self_type_constraint = shift @self_type_constraints;
320 my $other_type_constraint = shift @other_type_constraints;
8dbdca20 321
179af711 322 $_ = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_)
323 for $self_type_constraint, $other_type_constraint;
8dbdca20 324
179af711 325 return 1 if $self_type_constraint->$op($other_type_constraint);
326 }
327
328 return 0;
329}
330
8dbdca20 331=method get_message
a4a88fef 332
797510e3 333Give you a better peek into what's causing the error. For now we stringify the
334incoming deep value with L<Devel::PartialDump> and pass that on to either your
335custom error message or the default one. In the future we'll try to provide a
336more complete stack trace of the actual offending elements
337
338=cut
339
340around 'get_message' => sub {
341 my ($get_message, $self, $value) = @_;
7559b71f 342 $value = Devel::PartialDump::dump($value)
343 if ref $value;
344 return $self->$get_message($value);
797510e3 345};
a4a88fef 346
a30fa891 347=head1 SEE ALSO
348
349The following modules or resources may be of interest.
350
351L<Moose>, L<Moose::Meta::TypeConstraint>
352
a30fa891 353=cut
354
b5deb412 355__PACKAGE__->meta->make_immutable(inline_constructor => 0);