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