package Catalyst::ActionRole::QueryMatching; use Moose::Role; use Moose::Util::TypeConstraints (); requires 'match', 'match_captures', 'list_extra_info'; sub _query_attr { @{shift->attributes->{Query}||[]} } has is_slurpy => ( is=>'ro', init_arg=>undef, isa=>'Bool', required=>1, lazy=>1, builder=>'_build_is_slurpy'); sub _build_is_slurpy { my $self = shift; my($query, @extra) = $self->_query_attr; return $query =~m/^.+,\.\.\.$/ ? 1:0; } has query_constraints => ( is=>'ro', init_arg=>undef, isa=>'ArrayRef|Ref', required=>1, lazy=>1, builder=>'_build_query_constraints'); sub _build_query_constraints { my $self = shift; my ($constraint_proto, @extra) = $self->_query_attr; die "Action ${\$self->private_path} defines more than one 'Query' attribute" if scalar @extra; return +{} unless defined($constraint_proto); $constraint_proto =~s/^(.+),\.\.\.$/$1/; # slurpy is handled elsewhere # Query may be a Hash like Query(p=>Int,q=>Str) OR it may be a Ref like # Query(Tuple[p=>Int, slurpy HashRef]). The only way to figure is to eval it # and look at what we have. my @signature = eval "package ${\$self->class}; $constraint_proto" or die "'$constraint_proto' is not valid Query Contraint at action ${\$self->private_path}, error '$@'"; if(scalar(@signature) > 1) { # Do a dance to support old school stringy types # At this point we 'should' have a hash... my %pairs = @signature; foreach my $key(keys %pairs) { next if ref $pairs{$key}; $pairs{$key} = Moose::Util::TypeConstraints::find_or_parse_type_constraint($pairs{$key}) || die "'$pairs{$key}' is not a valid type constraint in Action ${\$self->private_path}"; } return \%pairs; } else { # We have a 'reference type' constraint, like Dict[p=>Int,...] return $signature[0] if ref($signature[0]); # Is like Tiny::Type return Moose::Util::TypeConstraints::find_or_parse_type_constraint($signature[0]) || die "'$signature[0]' is not a valid type constraint in Action ${\$self->private_path}"; } } around ['match','match_captures'] => sub { my ($orig, $self, $c, @args) = @_; my $tc = $self->query_constraints; if(ref $tc eq 'HASH') { # Do the key names match, unless slurpy? unless($self->is_slurpy) { return 0 unless $self->_compare_arrays([sort keys %$tc],[sort keys %{$c->req->query_parameters}]); } for my $key(keys %$tc) { $tc->{$key}->check($c->req->query_parameters->{$key}) || return 0; } } else { $tc->check($c->req->query_parameters) || return 0; } return $self->$orig($c, @args); }; around 'list_extra_info' => sub { my ($orig, $self, @args) = @_; return { %{ $self->$orig(@args) }, }; }; sub _compare_arrays { my ($self, $first, $second) = @_; no warnings; # silence spurious -w undef complaints return 0 unless @$first == @$second; for (my $i = 0; $i < @$first; $i++) { return 0 if $first->[$i] ne $second->[$i]; } return 1; } 1; =head1 NAME Catalyst::ActionRole::QueryMatching - Match on GET parameters using type constraints =head1 SYNOPSIS TBD =head1 DESCRIPTION TBD =head1 METHODS This role defines the following methods =head2 TBD TBD =head1 AUTHORS Catalyst Contributors, see Catalyst.pm =head1 COPYRIGHT This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut