Parameterizing a Moose subtype by another subtype via coercion

165 views Asked by At

I'm unsuccessfully trying to create a type _Varchar which would use another type _VarcharRange as a parameter. _VarcharRange, in turn, can be coerced from one of the Moose base types.

Let me start with _Varchar_Range as an example

package MyTypes;

use strict;
use warnings;

use Moose;
use MooseX::Types::Moose qw(Str Int ArrayRef HashRef);
use MooseX::Types::Common::Numeric qw( PositiveOrZeroInt );
use MooseX::Types::Parameterizable qw(Parameterizable);
use MooseX::Types -declare=>[qw(_VarcharRange)];

subtype _VarcharRange,

    as HashRef[PositiveOrZeroInt], # Must ask for it from MooseX

    where {
        return 0 if ( grep {$_ ne 'min' && $_ ne 'max' } keys %{$_});
        return ( $_->{min} <= $_->{max} ) if ( defined $_->{max} && defined $_->{min} );
        return 1;
    },

    message {"Trololo"};

coerce _VarcharRange,

  from ArrayRef[PositiveOrZeroInt],

  via {
    my $result;
    my @keys = qw(min max);

    foreach my $val (reverse @$_) {
        my $key = pop @keys // 'bad_range';
        $result->{$key} = $val;
    }

    return $result;
  };

has 'my_range' => ( isa => _VarcharRange, is => 'ro', coerce => 1 );

1;

And the corresponding test file:

!/usr/bin/env perl

use MyTypes qw(_VarcharRange);

my $check = MyTypes->new(
    #my_range => [5, 10],   # works fine
    #my_range => [1, 0],    # fails, as expected
    #my_range => [0, 1, 2], # fails, as expected
    #my_range => [10],      # works fine
);

So far so good, but now I try to add a type _Varchar parameterizable by _VarcharRange. Adding it to MyTypes.pm:

subtype _Varchar,

    as Parameterizable[Str, _VarcharRange],

    where {
        my ( $string, $range ) = @_;
        my $len = length($string);
        return 0 if ($range->{min} && $len < $range->{min});
        return 0 if ($range->{max} && $len > $range->{max});
        return 1;
    },

    message { "'$_[0]' length is not within range $_[1]->{min} - $_[1]->{max}" };

    has 'my_string' => ( isa => _Varchar[ [1, 10] ], is => 'ro' );

And this does not work.

What I expected is that _VarcharRange would be implicitly created by coercion from [1, 10] array reference. But looks like my code does not even proceed to creating _VarcharRange, since I'm trying to parameterize my _Varchar directly with ArrayRef.

Is there a way to achieve what I want? Pass an array reference as a parameter, but have intermediate type _VarcharRange coerced from it and actually be used for parameterization.

I don't want to parameterize _Varchar directly from ArrayRef because it is not very object-oriented. It feels like having an independent type for a range is the correct way.

0

There are 0 answers