I'm creating a class which will contain a list of IP addresses, as Net::IP objects.
I've wrapped the Net::IP object as a subtype (IPAddress), and defined a coercion from a string to IPAddress. Then I've added an attribute to the class called ip_list with the type ArrayRef[IPAddress], and delegated to the push method of the Array trait.
use MooseX::Declare;
use Moose::Util::TypeConstraints;
use Net::IP;
subtype 'IPAddress'
=> as 'Object'
=> where { $_->isa('Net::IP') };
coerce 'IPAddress'
=> from 'Str'
=> via { Net::IP->new( $_ ) };
class IPs {
has 'ip_list' => ( traits => ['Array'],
isa => 'ArrayRef[IPAddress]',
is => 'rw',
coerce => 1,
auto_deref => 1,
default => sub { [] },
handles => {
add_ip => 'push'
}
);
}
However if I try to call the delegated method like so:
my $o = IPs->new();
$o->add_ip( '192.168.0.1' );
I get the error "Value SCALAR(0x8017e8) did not pass container type constraint 'IPAddress' at ..."
So obviously the parameter to add_ip is not being coerced.
Is it possible to do what I'm attempting, or should I just do all this manually? I've trawled through the Moose manuals but I've not seen anything that would indicate either way, but I am probably missing something.
Unfortunately Moose does not chain coercions (it would be really complicated to parse these internally and figure out what the "right thing to do" is in an automatic fashion), so you need to define the chain yourself:
use Net::IP;
class_type 'Net::IP';
coerce 'Net::IP'
=> from 'Str'
=> via { Net::IP->new( $_ ) };
subtype 'ArrayRefOfIPAddresses'
=> as 'ArrayRef[Net::IP]';
coerce 'ArrayRefOfIPAddresses'
=> from 'ArrayRef[Str]'
=> via { [ map { Net::IP->new($_) } @$_ ] };
coerce 'ArrayRefOfIPAddresses'
=> from 'Str'
=> via { [ Net::IP->new($_) ] };
coerce 'ArrayRefOfIPAddresses'
=> from 'Net::IP'
=> via { [ $_ ] };
class IPs {
has 'ip_list' => ( traits => ['Array'],
isa => 'ArrayRefOfIPAddresses',
# ... rest of declaration as before
);
}
PS. since you are using the Array native delegation trait, I would recommend you avoid auto_deref
- add a handler instead:
has ip_list => (
is => 'bare',
# ...
handles => {
# ...
ip_list => 'elements',
},
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With