Глубокое принуждение Moose - ArrayRef пользовательских типов - PullRequest
5 голосов
/ 22 июня 2011

Чего мне не хватает в следующем подтипе и цепочке приведения?Я хотел бы иметь возможность принудительно использовать arrayref проверенных типов или умереть из следующих входных данных:

  • Coercible string
  • Valid string
  • Arrayref смешанного принудительного примененияи допустимые строки

Предположим, что все типы полностью разделены именами и что необъявленные функции validate и coerce_str проверяют (возвращает bool) и приводят и возвращают допустимую строку из ввода соответственно.

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,
                );

Я знаю, что CustomType работает;поскольку я могу определить атрибут как он и инициализировать объект, используя или принудительную строку или уже допустимую строку.То, что я не очень уверен, как это сделать, это явно обработать углубление в переданный arrayref из конструктора и проверить все содержащиеся в нем строки по отдельности.Я прочитал документацию по глубокому принуждению (http://search.cpan.org/dist/Moose/lib/Moose/Manual/Types.pod#Deep_coercion) пару раз, и я просто не совсем понимаю, и я надеюсь, что кто-то может направить меня в правильном направлении. Спасибо!

ЗдесьЯ бы сократил его, чтобы обрисовать более кратко, но:

{ 
  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

}

Запуск «как есть» дает мне следующее. Проблема не в проверке, а в том, что я не определяю явнопринуждения, и я не уверен, что мне не хватает:

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.

Ответы [ 2 ]

2 голосов
/ 23 июня 2011

Так что да, принуждение должно быть явно определено от базовых типов до пользовательских типов для всех перестановок ввода, которые вы хотите принять. Перемещение кода приведения и проверки в подпрограммы помогает предотвратить дублирование кода, но не устраняет его полностью. Следующий код работает, как я ожидал, вместе с планом TAP, чтобы доказать это.

Хотя, пока это работает, я не совсем уверен, что это намеченный способ справиться с подобными вещами. Он выполняет явное приведение из базовых типов к пользовательскому типу arrayref, и я не уверен, насколько хорошо это будет работать в более широком контексте, если метод доступа принимает несколько типов с принуждением.

Редактировать: На самом деле, на этом этапе coerce 'ArrayRefofCustomTypes' => from 'CustomType' совершенно не нужен, => from 'Str' будет обрабатывать как действительный, так и недействительный ввод.

{ 
  package My::Class;

  use strict;
  use warnings;

  use Moose;
  use Moose::Util::TypeConstraints;

  subtype 'CustomType'
    => as 'Str'
      => where { validate_cust($_) }
    ;

  coerce 'CustomType'
    => from 'Str'
      => via { coerce_str_to_cust($_) }
    ;

  subtype 'ArrayRefofCustomTypes'
    => as 'ArrayRef[CustomType]'
    ;

  coerce 'ArrayRefofCustomTypes'
    => from 'CustomType'
      => via { [ $_ ] }
    => from 'ArrayRef[Str]'
      => via { [ map { coerce_str_to_cust($_) } @$_ ] }
    => from 'Str'
      => via { [ coerce_str_to_cust($_) ] }
    ;

  has 'values' => ( is => 'ro', required => 1,
                    isa => 'ArrayRefofCustomTypes', 
                    coerce => 1,
                  );

  sub validate_cust {
    my $val = shift;
    if ($val =~ /^\w+$/) {
      return 1;
    }
    return ();
  }

  sub coerce_str_to_cust {
    my $val = shift;
    my $coerced = $val;
    $coerced =~ s/\s/_/g;

    if (validate_cust($coerced)) {
      return $coerced;
    }
    else {
      return $val;
    }
  }
}

{
  package main;

  use strict;
  use warnings;
  use Test::More tests => 12;
  use Test::Exception;

  new_ok( 'My::Class' => [ values => [ 'valid' ] ]);
  new_ok( 'My::Class' => [ values => [ qw/valid valid still_valid/ ] ]);
  new_ok( 'My::Class' => [ values => 'valid' ]);
  new_ok( 'My::Class' => [ values => [ 'invalid and needs some coercion' ] ]);
  new_ok( 'My::Class' => [ values => 'invalid and needs some coercion' ]);
  new_ok( 'My::Class' => [ values => [ 'valid', 'valid', 'invalid and needs some coercion' ] ]);
  throws_ok { my $obj =  My::Class->new( values => [ q/can't be coerced cause it has &^%#$*&^%#$s in it/ ] ); } qr/Attribute \(values\) does not pass the type constraint because: Validation failed/, 'throws exception on uncoercible input';

  my $uncoercible = q/can't be coerced cause it has &^%#$*&^%#$s in it/;
  cmp_ok( My::Class::coerce_str_to_cust('invalid and needs some coercion'), 'eq', 'invalid_and_needs_some_coercion', 'properly coerces strings');
  cmp_ok( My::Class::coerce_str_to_cust($uncoercible), 'eq', $uncoercible , 'returns uncoercible strings unmodified');
  ok( My::Class::validate_cust('valid'), 'valid string validates');
  ok( My::Class::validate_cust(My::Class::coerce_str_to_cust('invalid and needs some coercion')), 'coerced string validates');
  ok( !My::Class::validate_cust('invalid and needs some coercion'), "invalid string doesn't validate");
}
2 голосов
/ 23 июня 2011

Все, что вы использовали, должно работать нормально. Например, рассмотрим этот тест:

my $customtype = Moose::Util::TypeConstraints::find_type_constraint('CustomType');
print "'a' validates as customtype? ", ($customtype->check('a') ? 'yes' : 'no'), "\n";

my $arraytype = Moose::Util::TypeConstraints::find_type_constraint('ArrayRefofCustomTypes');
print "[ 'a' ] validates as array? ", ($arraytype->check([ 'a' ]) ? 'yes' : 'no'), "\n";

{
    package Class;
    use Moose;
    has 'values' => ( is => 'ro', required => 1,
                      isa => 'ArrayRefofCustomTypes',
                      coerce => 1,
                    );
}

my $obj = Class->new(values => 'a');
print $obj->dump(2);

Это печатает:

'a' validates as customtype? yes
[ 'a' ] validates as array? yes
$VAR1 = bless( {
                 'values' => [
                               'a'
                             ]
               }, 'Class' );

Вывод: если у вас возникли проблемы, то это в другом коде. Вы можете вставить какой-нибудь код, который не работает так, как вы ожидаете?

...