What am I missing in the following subtype and coercion chain? I'd like to be able to coerce an arrayref of validated types or die from the following inputs:
- Coercible string
- Valid string
- Arrayref of mixed coercible and valid strings
Assume that all types are fully namespaced and that the undeclared functions validate and coerce_str validate (returning bool) and coerce and return a valid string from input, respectively.
subtype 'CustomType'
=> as 'Str'
=> where { validate($_) }
;
coerce 'CustomType'
=> from 'Str'
=> via { if (my $coerced = coerce_str($_)) {
return $coerced;
}
return $_;
}
;
subtype 'ArrayRefofCustomTypes'
=> as 'ArrayRef[CustomType]'
;
coerce 'ArrayRefofCustomTypes'
=> from 'CustomType'
=> via { [ $_ ] }
;
has 'values' => ( is => 'ro', required => 1,
isa => 'ArrayRefofCustomTypes',
coerce => 1,
);
I know CustomType works; as I can define an attribute as it and initialize the object using either a coercible string or an already valid string. What I'm not so sure how to do is to explicitly handle delving into the passed arrayref from the constructor and validating all of the contained strings individually. I've read through the documentation on deep coercion (http://search.cpan.org/dist/Moose/lib/Moose/Manual/Types.pod#Deep_coercion) a couple times and I'm just not quite getting it and am hoping someone can point me in the right direction. Thanks!
Here, I'd pared it down to outline it more succinctly, but:
{
package My::Class;
use strict;
use warnings;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'CustomType'
=> as 'Str'
=> where { validate($_) }
;
coerce 'CustomType'
=> from 'Str'
=> via { if (my $coerced = coerce_str($_)) {
return $coerced;
}
return $_;
}
;
subtype 'ArrayRefofCustomTypes'
=> as 'ArrayRef[CustomType]'
;
coerce 'ArrayRefofCustomTypes'
=> from 'CustomType'
=> via { [ $_ ] }
;
has 'values' => ( is => 'ro', required => 1,
isa => 'ArrayRefofCustomTypes',
coerce => 1,
);
sub validate {
my $val = shift;
if ($val =~ /^\w+$/) {
return 1;
}
return ();
}
sub coerce_str {
my $val = shift;
$val =~ s/\W/_/g;
return $val;
}
}
{
package main;
use strict;
use warnings;
use Test::More qw/no_plan/;
new_ok( 'My::Class' => [ values => [ 'valid' ] ]); #ok
new_ok( 'My::Class' => [ values => [ qw/valid valid still_valid/ ] ]); #ok
new_ok( 'My::Class' => [ values => 'valid' ]); # ok
new_ok( 'My::Class' => [ values => [ 'invalid; needs some coercion - ^&%&^' ] ]); #not ok
new_ok( 'My::Class' => [ values => 'invalid; needs some coercion - ^&%&^' ]); # not ok
cmp_ok( My::Class::coerce_str('invalid; needs some coercion - ^&%&^'), 'eq', 'invalid__needs_some_coercion________', 'properly coerces strings'); #ok
}
Running that as-is gives me the below. The problem is not the validation, but that I'm not explicitly defining my coercions, and I'm not sure what I'm missing:
ok 1 - The object isa My::Class
ok 2 - The object isa My::Class
ok 3 - The object isa My::Class
not ok 4 - new() died
# Failed test 'new() died'
# at testcoercion.pl line 63.
# Error was: Attribute (values) does not pass the type constraint because: Validation failed for 'ArrayRefofCustomTypes' with value [ "invalid; needs some coercion - ^&%&^" ] at C:/strawberry/perl/site/lib/Moose/Meta/Attribute.pm line 1131
<< cut >>
not ok 5 - new() died
# Failed test 'new() died'
# at testcoercion.pl line 64.
# Error was: Attribute (values) does not pass the type constraint because: Validation failed for 'ArrayRefofCustomTypes' with value "invalid; needs some coercion - ^&%&^" at C:/strawberry/perl/site/lib/Moose/Meta/Attribute.pm line 1131
<< cut >>
ok 6 - properly coerces strings
1..6
# Looks like you failed 2 tests of 6.
coerce 'ArrayRefofCustomTypes' => from 'ArrayRef[Str]' via => {}and duplicating my coercion code but sticking it into an arrayref container'll handle it, but that duplicates the entire string->custom type conversion code. From rereading the documentation I suspect this is what it means by the bit about how all conversions must be explicitly defined, but I hate duplicating code like that. - Oesor