Mailing List Archive

using Type::Tiny constraint objects as Args() parameters
I found a fascinating feature documented at https://metacpan.org/pod/Catalyst::Controller#Args
and https://metacpan.org/dist/Catalyst-Runtime/view/lib/Catalyst/RouteMatching.pod#Type-Constraints-in-Args-and-Capture-Args
where you can provide a Type::Tiny or other constraint object to
Args() or CaptureArgs() instead of specifying the number of
arguments to capture as an integer.

However, when I tried it out, I found that even though the
documentation and the Catalyst code (in Catalyst::Action
resolve_type_constraint()) make it clear that a type object is
expected, what is actually received is a string.

Looking deeper, it appears that https://metacpan.org/pod/MooseX::MethodAttributes
is used to retrieve the parameters provided to Args(), and its
documentation shows that only a string will be passed through.

From the Catalyst documentation, it seems like it was possible at
one time to pass constraint objects to Args(). I'm not sure at
what point it stopped working, or if it still works, and I need to
do something differently to enable it.

package MyApp::Controller::MyController;

use Moose;
use MooseX::MethodAttributes;
use Types::Standard qw(Int);

use namespace::autoclean;

sub view : PathPart('view') Args(Int) {
my ( $self, $c ) = @_;

$c->stash->{template} = 'foo.tt';
$c->forward( 'MyApp::View::TT' );
}

When I add some debugging to resolve_type_constraint() in
Catalyst::Action, I find that what is received is not an object,
even though we check to see if it is an object.

The code that evals the package and tries to instantiate the type
object also does not appear to be successful.

sub resolve_type_constraint {
my ($self, $name) = @_;

if(defined($name) && blessed($name) && $name->can('check')) {
# Its already a TC, good to go.
warn "$name is a type constraint\n";
return $name;
}

warn "$name is " . (defined($name) ? '' : 'NOT') . " defined\n";
warn "$name is " . (blessed($name) ? '' : 'NOT') . " blessed\n";
warn "$name can " . ($name->can('check') ? '' : 'NOT') . " check\n";
# This is broken for when there is more than one constraint
if($name=~m/::/) {
eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
my $tc = Type::Registry->new->foreign_lookup($name);
return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
}

my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
warn "$name eval'ed within " . $self->class . " becomes:"
. join(', ', map { defined($_) ? "'$_'" : undef } @tc) . "\n";

...
}

The output I get is:

Int is defined
Int is NOT blessed
Int can NOT check
Int eval'ed within MyApp::Controller::MyController becomes:

with no values displayed.

I'd really like to be able to use this functionality. Any
suggestions for what I might try?

-kolibrie
Re: using Type::Tiny constraint objects as Args() parameters [ In reply to ]
Try removing use namespace::autoclean;
On Thursday, May 12, 2022, 04:01:49 PM CDT, Nathan Gray <kolibrie@cpan.org> wrote:

I found a fascinating feature documented at https://metacpan.org/pod/Catalyst::Controller#Args
and https://metacpan.org/dist/Catalyst-Runtime/view/lib/Catalyst/RouteMatching.pod#Type-Constraints-in-Args-and-Capture-Args
where you can provide a Type::Tiny or other constraint object to
Args() or CaptureArgs() instead of specifying the number of
arguments to capture as an integer.

However, when I tried it out, I found that even though the
documentation and the Catalyst code (in Catalyst::Action
resolve_type_constraint()) make it clear that a type object is
expected, what is actually received is a string.

Looking deeper, it appears that https://metacpan.org/pod/MooseX::MethodAttributes
is used to retrieve the parameters provided to Args(), and its
documentation shows that only a string will be passed through.

From the Catalyst documentation, it seems like it was possible at
one time to pass constraint objects to Args().  I'm not sure at
what point it stopped working, or if it still works, and I need to
do something differently to enable it.

    package MyApp::Controller::MyController;

    use Moose;
    use MooseX::MethodAttributes;
    use Types::Standard qw(Int);

    use namespace::autoclean;

    sub view : PathPart('view') Args(Int) {
        my ( $self, $c ) = @_;

        $c->stash->{template} = 'foo.tt';
        $c->forward( 'MyApp::View::TT' );
    }

When I add some debugging to resolve_type_constraint() in
Catalyst::Action, I find that what is received is not an object,
even though we check to see if it is an object.

The code that evals the package and tries to instantiate the type
object also does not appear to be successful.

    sub resolve_type_constraint {
      my ($self, $name) = @_;
   
      if(defined($name) && blessed($name) && $name->can('check')) {
        # Its already a TC, good to go.
        warn "$name is a type constraint\n";
        return $name;
      }
   
      warn "$name is " . (defined($name) ? '' : 'NOT') . " defined\n";
      warn "$name is " . (blessed($name) ? '' : 'NOT') . " blessed\n";
      warn "$name can " . ($name->can('check') ? '' : 'NOT') . " check\n";
      # This is broken for when there is more than one constraint
      if($name=~m/::/) {
        eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
        my $tc =  Type::Registry->new->foreign_lookup($name);
        return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
      }
     
      my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
      warn "$name eval'ed within " . $self->class . " becomes:"
          . join(', ', map { defined($_) ? "'$_'" : undef } @tc) .  "\n";

      ...
    }

The output I get is:

    Int is  defined
    Int is NOT blessed
    Int can NOT check
    Int eval'ed within MyApp::Controller::MyController becomes:

with no values displayed.

I'd really like to be able to use this functionality.  Any
suggestions for what I might try?

-kolibrie
_______________________________________________
List: Catalyst@lists.scsys.co.uk
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
Dev site: http://dev.catalyst.perl.org/
Re: using Type::Tiny constraint objects as Args() parameters [ In reply to ]
On Thu, Jul 28, 2022 at 06:00:11PM +0000, John Napiorkowski wrote:
> Try removing use namespace::autoclean;

Thank you for that suggestion. I was able to get constraint
objects to work for CaptureArgs after switching from
namespace::autoclean to namespace::clean.

-kolibrie

_______________________________________________
List: Catalyst@lists.scsys.co.uk
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
Dev site: http://dev.catalyst.perl.org/
Re: using Type::Tiny constraint objects as Args() parameters [ In reply to ]
Yeah autoclean removes the constraints from the namespace and breaks this :). Then you get wierd issues because the code assumes you're using old style Moose stringy constraints.   Maybe not the most user friendly.
On Monday, August 1, 2022, 02:42:28 PM CDT, Nathan Gray <kolibrie@graystudios.org> wrote:

On Thu, Jul 28, 2022 at 06:00:11PM +0000, John Napiorkowski wrote:
>  Try removing use namespace::autoclean;

Thank you for that suggestion.  I was able to get constraint
objects to work for CaptureArgs after switching from
namespace::autoclean to namespace::clean.

-kolibrie

_______________________________________________
List: Catalyst@lists.scsys.co.uk
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
Dev site: http://dev.catalyst.perl.org/