moved prefetch_allows to StaticArguments to fix controller instantiation failures...
[catagits/Catalyst-Controller-DBIC-API.git] / lib / Catalyst / Controller / DBIC / API / RequestArguments.pm
index ad9280e..523c07f 100644 (file)
@@ -6,56 +6,49 @@ use Catalyst::Controller::DBIC::API::Types(':all');
 use MooseX::Types::Moose(':all');
 use Scalar::Util('reftype');
 use Data::Dumper;
+use Catalyst::Controller::DBIC::API::Validator;
 use namespace::autoclean;
 
 use Catalyst::Controller::DBIC::API::JoinBuilder;
 
-
 =attribute_private search_validator
 
 A Catalyst::Controller::DBIC::API::Validator instance used solely to validate search parameters
 
 =cut
 
-with 'MooseX::Role::BuildInstanceOf' =>
-{
-    'target' => 'Catalyst::Controller::DBIC::API::Validator',
-    'prefix' => 'search_validator',
-};
-
 =attribute_private select_validator
 
 A Catalyst::Controller::DBIC::API::Validator instance used solely to validate select parameters
 
 =cut
 
-with 'MooseX::Role::BuildInstanceOf' =>
-{
-    'target' => 'Catalyst::Controller::DBIC::API::Validator',
-    'prefix' => 'select_validator',
-};
-
 =attribute_private prefetch_validator
 
 A Catalyst::Controller::DBIC::API::Validator instance used solely to validate prefetch parameters
 
 =cut
 
-with 'MooseX::Role::BuildInstanceOf' =>
-{
-    'target' => 'Catalyst::Controller::DBIC::API::Validator',
-    'prefix' => 'prefetch_validator',
-};
+has [qw( search_validator select_validator )] => (
+    is => 'ro',
+    isa => 'Catalyst::Controller::DBIC::API::Validator',
+    lazy => 1,
+    builder => '_build_validator',
+);
+
+sub _build_validator {
+    return Catalyst::Controller::DBIC::API::Validator->new;
+}
 
 parameter static => ( isa => Bool, default => 0 );
 
 role {
-    
+
     my $p = shift;
-    
+
     if($p->static)
     {
-        requires qw/check_has_relation check_column_relation/;
+        requires qw/check_has_relation check_column_relation prefetch_allows /;
     }
     else
     {
@@ -146,86 +139,26 @@ prefetch is passed to ->search to optimize the number of database fetches for jo
     (
         is => 'ro',
         writer => '_set_prefetch',
-        isa => Prefetch, 
+        isa => Prefetch,
         default => sub { $p->static ? [] : undef },
         coerce => 1,
         trigger => sub
         {
             my ($self, $new) = @_;
-            if($self->has_prefetch_allows and @{$self->prefetch_allows})
-            {
-                foreach my $pf (@$new)
-                {
-                    if(HashRef->check($pf))
-                    {
-                        die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
-                            unless $self->prefetch_validator->validate($pf)->[0];
-                    }
-                    else
-                    {
-                        die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
-                            unless $self->prefetch_validator->validate({$pf => 1})->[0];
-                    }
-                }
-            }
-            else
-            {
-                return if not defined($new);
-                die 'Prefetching is not allowed' if @$new;
-            }
-        },
-    );
-
-=attribute_public prefetch_allows is: ro, isa: ArrayRef[ArrayRef|Str|HashRef]
-
-prefetch_allows limits what relations may be prefetched when executing searches with joins. This is necessary to avoid denial of service attacks in form of queries which would return a large number of data and unwanted disclosure of data.
 
-Like the synopsis in DBIC::API shows, you can declare a "template" of what is allowed (by using an '*'). Each element passed in, will be converted into a Data::DPath and added to the validator.
-
-    prefetch_allows => [ 'cds', { cds => tracks }, { cds => producers } ] # to be explicit
-    prefetch_allows => [ 'cds', { cds => '*' } ] # wildcard means the same thing
-
-=cut
-
-    has prefetch_allows =>
-    (
-        is => 'ro',
-        writer => '_set_prefetch_allows',
-        isa => ArrayRef[ArrayRef|Str|HashRef], 
-        default => sub { [ ] },
-        predicate => 'has_prefetch_allows',
-        trigger => sub
-        {
-            my ($self, $new) = @_;
-
-            sub check_rel {
-                my ($self, $rel, $static) = @_;
-                if(ArrayRef->check($rel))
-                {
-                    foreach my $rel_sub (@$rel)
-                    {
-                        $self->check_rel($rel_sub, $static);
-                    }
-                }
-                elsif(HashRef->check($rel))
+            foreach my $pf (@$new)
+            {
+                if(HashRef->check($pf))
                 {
-                    while(my($k,$v) = each %$rel)
-                    {
-                        $self->check_has_relation($k, $v, undef, $static);
-                    }
-                    $self->prefetch_validator->load($rel);
+                    die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
+                        unless $self->prefetch_validator->validate($pf)->[0];
                 }
                 else
                 {
-                    $self->check_has_relation($rel, undef, undef, $static);
-                    $self->prefetch_validator->load($rel);
+                    die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
+                        unless $self->prefetch_validator->validate({$pf => 1})->[0];
                 }
             }
-
-            foreach my $rel (@$new)
-            {
-                $self->check_rel($rel, $p->static);
-            }
         },
     );
 
@@ -269,7 +202,7 @@ Please see L</generate_parameters_attributes> for details on how the format work
         trigger => sub
         {
             my ($self, $new) = @_;
-            
+
             if($self->has_search_exposes and @{$self->search_exposes})
             {
                 foreach my $foo (@$new)
@@ -292,7 +225,7 @@ Please see L</generate_parameters_attributes> for details on how the format work
                     }
                 }
             }
-            
+
             my ($search_parameters, $search_attributes) = $self->generate_parameters_attributes($new);
             $self->_set_search_parameters($search_parameters);
             $self->_set_search_attributes($search_attributes);
@@ -384,7 +317,7 @@ Please see L<DBIx::Class::ResultSet/select> for more details.
         default => sub { $p->static ? [] : undef },
         coerce => 1,
         trigger => sub
-        {   
+        {
             my ($self, $new) = @_;
             if($self->has_select_exposes)
             {
@@ -462,10 +395,12 @@ request_data holds the raw (but deserialized) data for ths request
         is => 'ro',
         isa => HashRef,
         writer => '_set_request_data',
+        predicate => 'has_request_data',
         trigger => sub
         {
             my ($self, $new) = @_;
             my $controller = $self->_controller;
+            return unless defined($new) && keys %$new;
             $self->_set_prefetch($new->{$controller->prefetch_arg}) if exists $new->{$controller->prefetch_arg};
             $self->_set_select($new->{$controller->select_arg}) if exists $new->{$controller->select_arg};
             $self->_set_as($new->{$controller->as_arg}) if exists $new->{$controller->as_arg};
@@ -489,7 +424,7 @@ format_search_parameters iterates through the provided params ArrayRef, calling
     method format_search_parameters => sub
     {
         my ($self, $params) = @_;
-        
+
         my $genparams = [];
 
         foreach my $param (@$params)
@@ -510,21 +445,21 @@ generate_column_parameters recursively generates properly aliased parameters for
     {
         my ($self, $source, $param, $join, $base) = @_;
         $base ||= 'me';
-        my $search_params;
+        my $search_params = {};
 
         # build up condition
         foreach my $column (keys %$param)
         {
-            if($source->has_relationship($column))
+            if ($source->has_relationship($column))
             {
+                # check if the value isn't a hashref
                 unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH')
                 {
                     $search_params->{join('.', $base, $column)} = $param->{$column};
                     next;
                 }
 
-                %$search_params = 
-                %{
+                $search_params = { %$search_params, %{
                     $self->generate_column_parameters
                     (
                         $source->related_source($column),
@@ -532,12 +467,24 @@ generate_column_parameters recursively generates properly aliased parameters for
                         Catalyst::Controller::DBIC::API::JoinBuilder->new(parent => $join, name => $column),
                         $column
                     )
-                };
+                }};
             }
-            else
+            elsif ($source->has_column($column))
             {
                 $search_params->{join('.', $base, $column)} = $param->{$column};
             }
+            # might be a sql function instead of a column name
+            # e.g. {colname => {like => '%foo%'}}
+            else
+            {
+                # but only if it's not a hashref
+                unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH') {
+                    $search_params->{join('.', $base, $column)} = $param->{$column};
+                }
+                else {
+                    die "$column is neither a relationship nor a column\n";
+                }
+            }
         }
 
         return $search_params;
@@ -566,7 +513,7 @@ This builder method generates the search attributes
     {
         my ($self, $args) = @_;
         my $static = $self->_controller;
-        my $search_attributes = 
+        my $search_attributes =
         {
             group_by => $self->grouped_by || ((scalar(@{$static->grouped_by})) ? $static->grouped_by : undef),
             order_by => $self->ordered_by || ((scalar(@{$static->ordered_by})) ? $static->ordered_by : undef),
@@ -574,6 +521,7 @@ This builder method generates the search attributes
             as => $self->as || ((scalar(@{$static->as})) ? $static->as : undef),
             prefetch => $self->prefetch || $static->prefetch || undef,
             rows => $self->count || $static->count,
+            page => $static->page,
             offset => $self->offset,
             join => $self->build_joins,
         };
@@ -587,15 +535,15 @@ This builder method generates the search attributes
             $search_attributes->{page} = $search_attributes->{offset} / $search_attributes->{rows} + 1;
             delete $search_attributes->{offset};
         }
-        
 
-        $search_attributes = 
-        {   
+
+        $search_attributes =
+        {
             map { @$_ }
             grep
             {
-                defined($_->[1]) 
-                ? 
+                defined($_->[1])
+                ?
                     (ref($_->[1]) && reftype($_->[1]) eq 'HASH' && keys %{$_->[1]})
                     || (ref($_->[1]) && reftype($_->[1]) eq 'ARRAY' && @{$_->[1]})
                     || length($_->[1])
@@ -610,7 +558,7 @@ This builder method generates the search attributes
         if ($search_attributes->{page} && !$search_attributes->{rows}) {
             die 'list_page can only be used with list_count';
         }
-        
+
         if ($search_attributes->{select}) {
             # make sure all columns have an alias to avoid ambiguous issues
             # but allow non strings (eg. hashrefs for db procs like 'count')
@@ -619,7 +567,7 @@ This builder method generates the search attributes
         }
 
         return $search_attributes;
-        
+
     };
 
 };