Created
December 9, 2009 18:38
-
-
Save nperez/252662 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
==== Patch <moosify-validation-configurability.patch> level 1 | |
Source: 992f488a-d630-404b-95f9-f7d0fdf28443:/local/ccda:4220 [local] | |
Target: 4ad37cd2-5fec-0310-835f-b3785c72a374:/Catalyst-Controller-DBIC-API/1.003/trunk:12230 [mirrored] | |
(http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Controller-DBIC-API/1.003/trunk) | |
Log: | |
r4216@nicklaptop: nicholas | 2009-12-02 22:33:02 -0600 | |
Mirror/copy Catalyst::Controller::DBIC::API | |
r4217@nicklaptop: nicholas | 2009-12-06 17:45:04 -0600 | |
checkpoint | |
r4220@nicklaptop: nicholas | 2009-12-09 12:26:12 -0600 | |
Moosify, Validator, add configurability | |
=== t/rpc/list_search_allows.t | |
================================================================== | |
--- t/rpc/list_search_allows.t (revision 12230) | |
+++ t/rpc/list_search_allows.t (patch moosify-validation-configurability.patch level 1) | |
@@ -31,6 +31,7 @@ | |
my @expected_response = map { { $_->get_columns } } $base_rs->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
+ #use Data::Dumper; warn Dumper($response, \@expected_response); | |
is_deeply( { list => \@expected_response, success => 'true' }, $response, 'correct message returned' ); | |
} | |
@@ -44,6 +45,7 @@ | |
cmp_ok( $mech->status, '==', 200, 'search on position okay' ); | |
my @expected_response = map { { $_->get_columns } } $base_rs->search({ position => 1 })->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
+ #use Data::Dumper; warn Dumper($response, \@expected_response); | |
is_deeply( { list => \@expected_response, success => 'true' }, $response, 'correct message returned' ); | |
} | |
@@ -56,9 +58,11 @@ | |
$mech->request($req); | |
cmp_ok( $mech->status, '==', 400, 'search on title not okay' ); | |
- my $expected_response = map { { $_->get_columns } } $base_rs->search({ position => 1 })->all; | |
+ my @expected_response = map { { $_->get_columns } } $base_rs->search({ position => 1 })->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply({ success => 'false',messages => ["title is not a valid column"]}, $response, 'correct message returned' ); | |
+ #use Data::Dumper; warn Dumper($response, \@expected_response); | |
+ is($response->{success}, 'false', 'correct message returned'); | |
+ like($response->{messages}->[0], qr/is not an allowed search term/, 'correct message returned'); | |
} | |
{ | |
@@ -72,7 +76,9 @@ | |
my $expected_response = map { { $_->get_columns } } $base_rs->search({ position => 1 })->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply({ success => 'false',messages => ["title is not a valid column"]}, $response, 'correct message returned' ); | |
+ #use Data::Dumper; warn Dumper($response); | |
+ is($response->{success}, 'false', 'correct message returned'); | |
+ like($response->{messages}->[0], qr/is not an allowed search term/, 'correct message returned'); | |
} | |
{ | |
@@ -84,7 +90,9 @@ | |
$mech->request($req); | |
cmp_ok( $mech->status, '==', 400, 'search on various cd fields not okay' ); | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply({ success => 'false',messages => ["artist is not a valid column"]}, $response, 'correct message returned' ); | |
+ #use Data::Dumper; warn Dumper($response); | |
+ is($response->{success}, 'false', 'correct message returned'); | |
+ like($response->{messages}->[0], qr/is not an allowed search term/, 'correct message returned'); | |
} | |
{ | |
@@ -97,6 +105,7 @@ | |
cmp_ok( $mech->status, '==', 200, 'search on various cd fields okay' ); | |
my @expected_response = map { { $_->get_columns } } $base_rs->search({ 'cd.year' => '1999', 'cd.title' => 'Spoonful of bees' }, { join => 'cd' })->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
+ #use Data::Dumper; warn Dumper($response, \@expected_response); | |
is_deeply({ success => 'true',list => \@expected_response }, $response, 'correct message returned' ); | |
} | |
@@ -110,6 +119,7 @@ | |
cmp_ok( $mech->status, '==', 200, 'search with custom col okay' ); | |
my @expected_response = map { { $_->get_columns } } $base_rs->search({ 'cd.year' => '1999', 'cd.title' => 'Spoonful of bees' }, { join => 'cd' })->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
+ #use Data::Dumper; warn Dumper($response, \@expected_response); | |
is_deeply({ success => 'true',list => \@expected_response }, $response, 'correct message returned' ); | |
} | |
@@ -123,6 +133,7 @@ | |
cmp_ok( $mech->status, '==', 200, 'search on artist field okay due to wildcard' ); | |
my @expected_response = map { { $_->get_columns } } $base_rs->search({ 'artist.name' => 'Random Boy Band' }, { join => { cd => 'artist' } })->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
+ #use Data::Dumper; warn Dumper($response, \@expected_response); | |
is_deeply({ success => 'true',list => \@expected_response }, $response, 'correct message returned' ); | |
} | |
=== t/rpc/list_json_search.t | |
================================================================== | |
--- t/rpc/list_json_search.t (revision 12230) | |
+++ t/rpc/list_json_search.t (patch moosify-validation-configurability.patch level 1) | |
@@ -27,9 +27,9 @@ | |
my $req = GET( $uri, 'Accept' => 'text/x-json' ); | |
$mech->request($req); | |
cmp_ok( $mech->status, '==', 400, 'attempt with gibberish json not okay' ); | |
- | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply( { messages => ['can not parse search arg'], success => 'false' }, $response, 'correct data returned for gibberish in search' ); | |
+ is($response->{success}, 'false', 'correct data returned for gibberish in search' ); | |
+ like($response->{messages}->[0], qr/Attribute \(search\) does not pass the type constraint because: Validation failed for 'HashRef' failed with value \{"gibberish\}/, 'correct data returned for gibberish in search' ); | |
} | |
{ | |
=== t/rpc/list.t | |
================================================================== | |
--- t/rpc/list.t (revision 12230) | |
+++ t/rpc/list.t (patch moosify-validation-configurability.patch level 1) | |
@@ -88,7 +88,7 @@ | |
{ | |
my $uri = URI->new( $track_list_url ); | |
- $uri->query_form({ 'order_by' => 'position' }); | |
+ $uri->query_form({ 'list_ordered_by' => 'position' }); | |
my $req = GET( $uri, 'Accept' => 'text/x-json' ); | |
$mech->request($req); | |
cmp_ok( $mech->status, '==', 200, 'search related request okay' ); | |
@@ -132,7 +132,9 @@ | |
$mech->request($req); | |
cmp_ok( $mech->status, '==', 400, 'non numeric list_page request not okay' ); | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply({ success => 'false', messages => ["list_page must be numeric"]}, $response, 'correct data returned' ); | |
+ # use Data::Dumper; warn Dumper($response); | |
+ is($response->{success}, 'false', 'correct data returned'); | |
+ like($response->{messages}->[0], qr/Attribute \(page\) does not pass the type constraint because: Validation failed for 'Int' failed with value fgdg/, 'correct data returned'); | |
} | |
{ | |
@@ -142,7 +144,9 @@ | |
$mech->request($req); | |
cmp_ok( $mech->status, '==', 400, 'non numeric list_count request not okay' ); | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply({ success => 'false', messages => ["list_count must be numeric"]}, $response, 'correct data returned' ); | |
+ is($response->{success}, 'false', 'correct data returned'); | |
+ like($response->{messages}->[0], qr/Attribute \(count\) does not pass the type constraint because: Validation failed for 'Int' failed with value sdsdf/, 'correct data returned'); | |
+ | |
} | |
{ | |
=== t/rpc/list_prefetch.t | |
================================================================== | |
--- t/rpc/list_prefetch.t (revision 12230) | |
+++ t/rpc/list_prefetch.t (patch moosify-validation-configurability.patch level 1) | |
@@ -32,6 +32,7 @@ | |
my @rows = $rs->all; | |
my $expected_response = { list => \@rows, success => 'true' }; | |
my $response = JSON::Syck::Load( $mech->content); | |
+ #use Data::Dumper; warn Dumper($response, $expected_response); | |
is_deeply( $expected_response, $response, 'correct data returned for search with simple prefetch specified as param' ); | |
} | |
@@ -59,7 +60,9 @@ | |
my $expected_response = map { { $_->get_columns } } $schema->resultset('CD')->all; | |
my $response = JSON::Syck::Load( $mech->content); | |
- is_deeply({ success => 'false',messages => ["prefetch validation failed"]}, $response, 'correct message returned' ); | |
+ #use Data::Dumper; warn Dumper($response, $expected_response); | |
+ is($response->{success}, 'false', 'correct message returned' ); | |
+ like($response->{messages}->[0], qr/Prefetching is not allowed/, 'correct message returned' ); | |
} | |
{ | |
=== t/lib/RestTest/Controller/API/RPC/CD.pm | |
================================================================== | |
--- t/lib/RestTest/Controller/API/RPC/CD.pm (revision 12230) | |
+++ t/lib/RestTest/Controller/API/RPC/CD.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -1,8 +1,7 @@ | |
package RestTest::Controller::API::RPC::CD; | |
-use strict; | |
-use warnings; | |
-use base qw/Catalyst::Controller::DBIC::API::RPC/; | |
+use Moose; | |
+BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC' } | |
use JSON::Syck; | |
__PACKAGE__->config | |
=== t/lib/RestTest/Controller/API/RPC/TrackExposed.pm | |
================================================================== | |
--- t/lib/RestTest/Controller/API/RPC/TrackExposed.pm (revision 12230) | |
+++ t/lib/RestTest/Controller/API/RPC/TrackExposed.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -10,7 +10,7 @@ | |
class => 'RestTestDB::Track', | |
list_returns => [qw/position title/], | |
list_ordered_by => [qw/position/], | |
- list_search_exposes => [qw/position/, { cd => [qw/title year pretend/, { artist => ['*'] }] }], | |
+ list_search_exposes => [qw/position/, { cd => [qw/title year pretend/, { 'artist' => ['*'] } ]}], | |
); | |
1; | |
=== lib/Catalyst/Controller/DBIC/API/RPC.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/RPC.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/RPC.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -1,9 +1,7 @@ | |
package Catalyst::Controller::DBIC::API::RPC; | |
-use strict; | |
-use warnings; | |
-use base qw/Catalyst::Controller::DBIC::API::Base/; | |
-use JSON::Syck; | |
+use Moose; | |
+BEGIN { extends 'Catalyst::Controller::DBIC::API::Base'; } | |
__PACKAGE__->config( | |
'default' => 'application/json', | |
@@ -90,7 +88,8 @@ | |
my ($self, $c) = @_; | |
$c->forward('deserialize'); | |
- $c->req->params($c->stash->{_dbic_api}->{req_params}); | |
+ return if $self->get_errors($c); | |
+ $c->req->params($self->active_request->data); | |
} | |
sub object :Chained('setup') :CaptureArgs(1) :PathPart('id') { | |
=== lib/Catalyst/Controller/DBIC/API/Validator.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/Validator.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/Validator.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -0,0 +1,108 @@ | |
+package Catalyst::Controller::DBIC::API::Visitor; | |
+use Moose; | |
+use namespace::autoclean; | |
+ | |
+BEGIN { extends 'Data::DPath::Validator::Visitor'; } | |
+ | |
+use constant DEBUG => $ENV{DATA_DPATH_VALIDATOR_DEBUG} || 0; | |
+ | |
+around visit_array => sub | |
+{ | |
+ my ($orig, $self, $array) = @_; | |
+ $self->dive(); | |
+ warn 'ARRAY: '. $self->current_template if DEBUG; | |
+ if(@$array == 1 && $array->[0] eq '*') | |
+ { | |
+ $self->append_text('[reftype eq "HASH" ]'); | |
+ $self->add_template($self->current_template); | |
+ } | |
+ else | |
+ { | |
+ if($self->current_template =~ /\/$/) | |
+ { | |
+ my $temp = $self->current_template; | |
+ $self->reset_template(); | |
+ $temp =~ s/\/$//; | |
+ $self->append_text($temp); | |
+ } | |
+ $self->$orig($array); | |
+ } | |
+ $self->rise(); | |
+}; | |
+ | |
+sub visit_array_entry | |
+{ | |
+ my ($self, $elem, $index, $array) = @_; | |
+ $self->dive(); | |
+ warn 'ARRAYENTRY: '. $self->current_template if DEBUG; | |
+ if(!ref($elem)) | |
+ { | |
+ $self->append_text($elem . '/*'); | |
+ $self->add_template($self->current_template); | |
+ } | |
+ elsif(ref($elem) eq 'HASH') | |
+ { | |
+ $self->visit($elem); | |
+ } | |
+ $self->rise(); | |
+ $self->value_type('NONE'); | |
+}; | |
+ | |
+around visit_hash => sub | |
+{ | |
+ my ($orig, $self, $hash) = @_; | |
+ $self->dive(); | |
+ if($self->current_template =~ /\/$/) | |
+ { | |
+ my $temp = $self->current_template; | |
+ $self->reset_template(); | |
+ $temp =~ s/\/$//; | |
+ $self->append_text($temp); | |
+ } | |
+ warn 'HASH: '. $self->current_template if DEBUG; | |
+ $self->$orig($hash); | |
+ $self->rise(); | |
+}; | |
+ | |
+around visit_value => sub | |
+{ | |
+ my ($orig, $self, $val) = @_; | |
+ | |
+ if($self->value_type eq 'NONE') | |
+ { | |
+ $self->dive(); | |
+ $self->append_text($val . '/*'); | |
+ $self->add_template($self->current_template); | |
+ warn 'VALUE: ' . $self->current_template if DEBUG; | |
+ $self->rise(); | |
+ } | |
+ elsif($self->value_type eq 'HashKey') | |
+ { | |
+ $self->append_text($val); | |
+ warn 'VALUE: ' . $self->current_template if DEBUG; | |
+ } | |
+ else | |
+ { | |
+ $self->$orig($val); | |
+ } | |
+ | |
+}; | |
+ | |
+ | |
+Catalyst::Controller::DBIC::API::Visitor->meta->make_immutable; | |
+ | |
+package Catalyst::Controller::DBIC::API::Validator; | |
+use Moose; | |
+use namespace::autoclean; | |
+ | |
+BEGIN { extends 'Data::DPath::Validator'; } | |
+ | |
+has '+visitor' => ( 'builder' => '_build_custom_visitor' ); | |
+ | |
+sub _build_custom_visitor | |
+{ | |
+ return Catalyst::Controller::DBIC::API::Visitor->new(); | |
+} | |
+ | |
+Catalyst::Controller::DBIC::API::Validator->meta->make_immutable; | |
+1; | |
=== lib/Catalyst/Controller/DBIC/API/Request.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/Request.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/Request.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -0,0 +1,17 @@ | |
+package Catalyst::Controller::DBIC::API::Request; | |
+use Moose; | |
+use MooseX::Aliases; | |
+use MooseX::Types::Moose(':all'); | |
+use namespace::autoclean; | |
+ | |
+has 'application' => | |
+( | |
+ is => 'ro', | |
+ isa => Object, | |
+ required => 1, | |
+ handles => 'Catalyst::Controller::DBIC::API::StoredResultSource', | |
+); | |
+ | |
+with 'Catalyst::Controller::DBIC::API::RequestArguments'; | |
+ | |
+1; | |
=== lib/Catalyst/Controller/DBIC/API/Base.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/Base.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/Base.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -1,46 +1,35 @@ | |
-package # hide from PAUSE | |
+package # hide from PAUSE | |
Catalyst::Controller::DBIC::API::Base; | |
-use strict; | |
-use warnings; | |
-use base qw/Catalyst::Controller/; | |
-use CGI::Expand qw/expand_hash/; | |
+use Moose; | |
+BEGIN { extends 'Catalyst::Controller'; } | |
+use CGI::Expand (); | |
use DBIx::Class::ResultClass::HashRefInflator; | |
use JSON::Any; | |
-use Test::Deep::NoTest qw/eq_deeply/; | |
+use Test::Deep::NoTest (); | |
+use MooseX::Types::Moose(':all'); | |
+use MooseX::Aliases; | |
+use Try::Tiny; | |
+use Catalyst::Controller::DBIC::API::Request; | |
+use namespace::autoclean; | |
-__PACKAGE__->mk_accessors(qw/ | |
- class create_requires | |
- update_requires update_allows | |
- create_allows | |
- list_count list_returns list_prefetch list_prefetch_allows | |
- list_grouped_by list_search_exposes list_ordered_by | |
- rs_stash_key object_stash_key | |
- setup_list_method setup_dbic_args_method | |
-/); | |
+with 'Catalyst::Controller::DBIC::API::StoredResultSource'; | |
+with 'Catalyst::Controller::DBIC::API::StaticArguments'; | |
+with 'Catalyst::Controller::DBIC::API::RequestArguments' => { static => 1 }; | |
-__PACKAGE__->config( | |
- class => undef, | |
- create_requires => [], | |
- create_allows => [], | |
- update_requires => [], | |
- update_allows => [], | |
- list_returns => [], | |
- list_prefetch => undef, | |
- list_prefetch_allows => [], | |
- list_grouped_by => [], | |
- list_search_exposes => [], | |
- list_ordered_by => [], | |
- list_count => undef, | |
- object_stash_key => 'object', | |
- rs_stash_key => 'class_rs' | |
-); | |
+has 'rs_stash_key' => ( is => 'ro', isa => Str, default => 'class_rs' ); | |
+has 'object_stash_key' => ( is => 'ro', isa => Str, default => 'object' ); | |
+has 'setup_list_method' => ( is => 'ro', isa => Str, predicate => 'has_setup_list_method'); | |
+has 'setup_dbic_args_method' => ( is => 'ro', isa => Str, predicate => 'has_setup_dbic_args_method'); | |
+has 'active_request' => ( is => 'rw', isa => 'Catalyst::Controller::DBIC::API::Request' ); | |
+__PACKAGE__->config(); | |
+ | |
sub setup :Chained('specify.in.subclass.config') :CaptureArgs(0) :PathPart('specify.in.subclass.config') { | |
my ($self, $c) = @_; | |
- $c->stash->{$self->rs_stash_key} = $c->model($self->class); | |
+ $c->stash->{$self->rs_stash_key} = $self->stored_model; | |
} | |
# from Catalyst::Action::Serialize | |
@@ -51,8 +40,8 @@ | |
if ($c->req->data && scalar(keys %{$c->req->data})) { | |
$req_params = $c->req->data; | |
} else { | |
- $req_params = expand_hash($c->req->params); | |
- foreach my $param (qw/search list_count list_ordered_by list_grouped_by list_prefetch/) { | |
+ $req_params = CGI::Expand->expand_hash($c->req->params); | |
+ foreach my $param (@{[$self->search_arg, $self->count_arg, $self->page_arg, $self->ordered_by_arg, $self->grouped_by_arg, $self->prefetch_arg]}) { | |
# these params can also be composed of JSON | |
eval { | |
my $deserialized = JSON::Any->from_json($req_params->{$param}); | |
@@ -60,7 +49,42 @@ | |
}; | |
} | |
} | |
- $c->stash->{_dbic_api}->{req_params} = $req_params; | |
+ | |
+ if(exists($req_params->{$self->data_root})) | |
+ { | |
+ my $val = delete $req_params->{$self->data_root}; | |
+ $req_params->{data} = $val; | |
+ } | |
+ else | |
+ { | |
+ $req_params->{data} = \%$req_params; | |
+ } | |
+ | |
+ try | |
+ { | |
+ my $req = Catalyst::Controller::DBIC::API::Request->new | |
+ ( | |
+ application => $self, | |
+ prefetch_allows => $self->prefetch_allows, | |
+ search_exposes => $self->search_exposes, | |
+ select_exposes => $self->select_exposes, | |
+ data => $req_params->{data}, | |
+ ); | |
+ | |
+ $req->_set_prefetch($req_params->{$self->prefetch_arg}); | |
+ $req->_set_select($req_params->{$self->select_arg}); | |
+ $req->_set_grouped_by($req_params->{$self->grouped_by_arg}); | |
+ $req->_set_ordered_by($req_params->{$self->ordered_by_arg}); | |
+ $req->_set_search($req_params->{$self->search_arg}) if exists $req_params->{$self->search_arg}; | |
+ $req->_set_count($req_params->{$self->count_arg}) if exists $req_params->{$self->count_arg}; | |
+ $req->_set_page($req_params->{$self->page_arg}) if exists $req_params->{$self->page_arg}; | |
+ | |
+ $self->active_request($req); | |
+ } | |
+ catch | |
+ { | |
+ $self->push_error($c, { message => $_ }); | |
+ } | |
} | |
sub list :Private { | |
@@ -71,7 +95,7 @@ | |
return unless ($ret && ref $ret); | |
my ($params, $args) = @{$ret}; | |
return if $self->get_errors($c); | |
- | |
+ | |
$c->stash->{$self->rs_stash_key} = $c->stash->{$self->rs_stash_key}->search($params, $args); | |
# add the total count of all rows in case of a paged resultset | |
eval { | |
@@ -93,64 +117,38 @@ | |
my ($self, $c) = @_; | |
my $args = {}; | |
- my $req_params = $c->stash->{_dbic_api}->{req_params}; | |
- my $prefetch = $req_params->{list_prefetch} || $self->list_prefetch || undef; | |
- if ($prefetch) { | |
- $prefetch = [$prefetch] unless ref $prefetch; | |
- # validate the prefetch param against list_prefetch_allows | |
- foreach my $prefetch_allows (@{$self->list_prefetch_allows}) { | |
- if (eq_deeply($prefetch, $prefetch_allows)) { | |
- $args->{prefetch} = $prefetch; | |
- # stop looking for a valid prefetch param | |
- last; | |
- } | |
- } | |
- unless (exists $args->{prefetch}) { | |
- $self->push_error($c, { message => "prefetch validation failed" }); | |
- } | |
- } | |
+ my $req = $self->active_request(); | |
if ( my $action_name = $self->setup_list_method ) { | |
my $setup_action = $self->action_for($action_name); | |
if ( defined $setup_action ) { | |
- $c->forward("/$setup_action", [ $req_params ]); | |
+ $c->forward("/$setup_action", [ $req->data, $req ]); | |
} else { | |
$c->log->error("setup_list_method was configured, but action $action_name not found"); | |
} | |
} | |
- my $source = $c->stash->{$self->rs_stash_key}->result_source; | |
+ my $source = $self->stored_result_source; | |
my ($params, $join); | |
- if ($req_params->{search} && !ref $req_params->{search}) { | |
- $self->push_error($c, { message => "can not parse search arg" }); | |
- return; | |
- } | |
+ ($params, $join) = $self->_format_search($c, { params => $req->search, source => $source }) if $req->has_search; | |
+ | |
+ $args->{prefetch} = $req->prefetch || $self->prefetch || undef; | |
+ $args->{group_by} = $req->grouped_by || ((scalar(@{$self->grouped_by})) ? $self->grouped_by : undef); | |
+ $args->{order_by} = $req->ordered_by || ((scalar(@{$self->ordered_by})) ? $self->ordered_by : undef); | |
+ $args->{rows} = $req->count || $self->count; | |
+ $args->{page} = $req->page; | |
- ($params, $join) = $self->_format_search($c, { params => $req_params->{search}, source => $source }) if ($req_params->{search}); | |
- | |
- $args->{group_by} = $req_params->{list_grouped_by} || ((scalar(@{$self->list_grouped_by})) ? $self->list_grouped_by : undef); | |
- $args->{order_by} = $req_params->{list_ordered_by} || ((scalar(@{$self->list_ordered_by})) ? $self->list_ordered_by : undef); | |
- $args->{rows} = $req_params->{list_count} || $self->list_count; | |
- $args->{page} = $req_params->{list_page}; | |
- if ($args->{page}) { | |
- unless ($args->{page} =~ /^\d+$/xms) { | |
- $self->push_error($c, { message => "list_page must be numeric" }); | |
- } | |
- } | |
- if ($args->{rows}) { | |
- unless ($args->{rows} =~ /^\d+$/xms) { | |
- $self->push_error($c, { message => "list_count must be numeric" }); | |
- } | |
- } | |
if ($args->{page} && !$args->{rows}) { | |
$self->push_error($c, { message => "list_page can only be used with list_count" }); | |
} | |
- $args->{select} = $req_params->{list_returns} || ((scalar(@{$self->list_returns})) ? $self->list_returns : undef); | |
+ | |
+ $args->{select} = $req->select || ((scalar(@{$self->select})) ? $self->select : undef); | |
if ($args->{select}) { | |
# make sure all columns have an alias to avoid ambiguous issues | |
$args->{select} = [map { ($_ =~ m/\./) ? $_ : "me.$_" } (ref $args->{select}) ? @{$args->{select}} : $args->{select}]; | |
} | |
+ | |
$args->{join} = $join; | |
if ( my $action_name = $self->setup_dbic_args_method ) { | |
my $format_action = $self->action_for($action_name); | |
@@ -172,38 +170,25 @@ | |
my $join = {}; | |
my %search_params; | |
- | |
+ | |
+ my $search_exposes = $self->search_exposes; | |
# munge list_search_exposes into format that's easy to do with | |
- my %valid = map { (ref $_) ? %{$_} : ($_ => 1) } @{$p->{_list_search_exposes} || $self->list_search_exposes}; | |
+ my %valid = map { (ref $_) ? %{$_} : ($_ => 1) } @{$p->{_list_search_exposes} || $search_exposes}; | |
if ($valid{'*'}) { | |
# if the wildcard is passed they can access any column or relationship | |
$valid{$_} = 1 for $source->columns; | |
$valid{$_} = ['*'] for $source->relationships; | |
} | |
# figure out the valid cols, defaulting to all cols if not specified | |
- my @valid_cols = @{$self->list_search_exposes} ? (grep { $valid{$_} eq 1 } keys %valid) : $source->columns; | |
+ my @valid_cols = @$search_exposes ? (grep { $valid{$_} eq 1 } keys %valid) : $source->columns; | |
# figure out the valid rels, defaulting to all rels if not specified | |
- my @valid_rels = @{$self->list_search_exposes} ? (grep { ref $valid{$_} } keys %valid) : $source->relationships; | |
+ my @valid_rels = @$search_exposes ? (grep { ref $valid{$_} } keys %valid) : $source->relationships; | |
my %_col_map = map { $_ => 1 } @valid_cols; | |
my %_rel_map = map { $_ => 1 } @valid_rels; | |
my %_source_col_map = map { $_ => 1 } $source->columns; | |
- # validate search params | |
- foreach my $key (keys %{$params}) { | |
- # if req args is a ref, assume it refers to a rel | |
- # XXX this is broken when attempting complex search | |
- # XXX clauses on a col like { col => { LIKE => '%dfdsfs%' } } | |
- # XXX when rel and col have the same name | |
- next if $valid{'*'}; | |
- if (ref $params->{$key} && $_rel_map{$key}) { | |
- $self->push_error($c, { message => "${key} is not a valid relation" }) unless (exists $_rel_map{$key}); | |
- } else { | |
- $self->push_error($c, { message => "${key} is not a valid column" }) unless exists $_col_map{$key}; | |
- } | |
- } | |
- | |
# build up condition on root source | |
foreach my $column (@valid_cols) { | |
next unless (exists $params->{$column}); | |
@@ -271,9 +256,6 @@ | |
sub update :Private { | |
my ($self, $c) = @_; | |
- # expand params unless they have already been expanded | |
- my $req_params = $c->stash->{_dbic_api}->{req_params}; | |
- | |
die "no object to update (looking at " . $self->object_stash_key . ")" | |
unless ( defined $c->stash->{$self->object_stash_key} ); | |
@@ -312,7 +294,7 @@ | |
sub validate { | |
my ($self, $c, $object) = @_; | |
- my $params = $c->stash->{_dbic_api}->{req_params}; | |
+ my $params = $self->active_request->data(); | |
my %values; | |
my %requires_map = map { $_ => 1 } @{($object->in_storage) ? [] : $c->stash->{create_requires} || $self->create_requires}; | |
@@ -323,20 +305,7 @@ | |
my $allowed_fields = $allows_map{$key}; | |
if (ref $allowed_fields) { | |
my $related_source = $object->result_source->related_source($key); | |
- unless ($related_source) { | |
- $self->push_error($c, { message => "${key} is not a valid relation" }); | |
- next; | |
- } | |
- | |
my $related_params = $params->{$key}; | |
- # it's an error for $c->req->params->{$key} to be defined but not be an array | |
- unless (ref $related_params) { | |
- unless (!defined $related_params) { | |
- $self->push_error($c, { message => "Value of ${key} must be a hash" }); | |
- } | |
- next; | |
- } | |
- | |
my %allowed_related_map = map { $_ => 1 } @{$allowed_fields}; | |
my $allowed_related_cols = ($allowed_related_map{'*'}) ? [$related_source->columns] : $allowed_fields; | |
foreach my $related_col (@{$allowed_related_cols}) { | |
@@ -416,13 +385,13 @@ | |
# Check for errors caught elsewhere | |
if ( $c->res->status and $c->res->status != 200 ) { | |
$default_status = $c->res->status; | |
- $c->stash->{response}->{success} = 'false'; | |
+ $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::false : 'false'; | |
} elsif ($self->get_errors($c)) { | |
$c->stash->{response}->{messages} = $self->get_errors($c); | |
- $c->stash->{response}->{success} = 'false'; | |
+ $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::false : 'false'; | |
$default_status = 400; | |
} else { | |
- $c->stash->{response}->{success} = 'true'; | |
+ $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::true : 'true'; | |
$default_status = 200; | |
} | |
=== lib/Catalyst/Controller/DBIC/API/REST.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/REST.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/REST.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -1,8 +1,7 @@ | |
package Catalyst::Controller::DBIC::API::REST; | |
-use strict; | |
-use warnings; | |
-use base qw/Catalyst::Controller::DBIC::API::Base/; | |
+use Moose; | |
+BEGIN { extends 'Catalyst::Controller::DBIC::API::Base'; } | |
__PACKAGE__->config( | |
'default' => 'application/json', | |
@@ -83,7 +82,7 @@ | |
sub object :Chained('setup') :Args(1) :PathPart('') :ActionClass('REST') { | |
my ($self, $c, $id) = @_; | |
- my $object = $c->stash->{$self->rs_stash_key}->find( $id ); | |
+ my $object = $self->stored_model->find( $id ); | |
unless ($object) { | |
$self->push_error($c, { message => "Invalid id" }); | |
$c->detach; # no point continuing | |
=== lib/Catalyst/Controller/DBIC/API/StaticArguments.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/StaticArguments.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/StaticArguments.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -0,0 +1,46 @@ | |
+package Catalyst::Controller::DBIC::API::StaticArguments; | |
+use Moose::Role; | |
+use MooseX::Types::Moose(':all'); | |
+use namespace::autoclean; | |
+ | |
+requires 'check_column_relation'; | |
+ | |
+foreach my $var (qw/create_requires create_allows update_requires update_allows/) | |
+{ | |
+ has $var => | |
+ ( | |
+ is => 'ro', | |
+ isa => ArrayRef[Str|HashRef], | |
+ traits => ['Array'], | |
+ default => sub { [] }, | |
+ trigger => sub | |
+ { | |
+ my ($self, $new) = @_; | |
+ $self->check_column_relation($_, 1) for @$new; | |
+ }, | |
+ handles => | |
+ { | |
+ "get_${var}_column" => 'get', | |
+ "set_${var}_column" => 'set', | |
+ "delete_${var}_column" => 'delete', | |
+ "insert_${var}_column" => 'insert', | |
+ "count_${var}_column" => 'count', | |
+ "all_${var}_columns" => 'elements', | |
+ } | |
+ ); | |
+ | |
+ before "set_${var}_column" => sub { $_[0]->check_column_relation($_[2], 1) }; #" | |
+ before "insert_${var}_column" => sub { $_[0]->check_column_relation($_[2], 1) }; #" | |
+} | |
+ | |
+has 'count_arg' => ( is => 'ro', isa => Str, default => 'list_count' ); | |
+has 'page_arg' => ( is => 'ro', isa => Str, default => 'list_page' ); | |
+has 'select_arg' => ( is => 'ro', isa => Str, default => 'list_returns' ); | |
+has 'search_arg' => ( is => 'ro', isa => Str, default => 'search' ); | |
+has 'grouped_by_arg' => ( is => 'ro', isa => Str, default => 'list_grouped_by' ); | |
+has 'ordered_by_arg' => ( is => 'ro', isa => Str, default => 'list_ordered_by' ); | |
+has 'prefetch_arg' => ( is => 'ro', isa => Str, default => 'list_prefetch' ); | |
+has 'data_root' => ( is => 'ro', isa => Str, default => 'list'); | |
+has 'use_json_boolean' => ( is => 'ro', isa => Bool, default => 0 ); | |
+ | |
+1; | |
=== lib/Catalyst/Controller/DBIC/API/RequestArguments.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/RequestArguments.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/RequestArguments.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -0,0 +1,257 @@ | |
+package Catalyst::Controller::DBIC::API::RequestArguments; | |
+use MooseX::Role::Parameterized; | |
+use Moose::Util::TypeConstraints; | |
+use MooseX::Types::Moose(':all'); | |
+use Data::Dumper; | |
+use namespace::autoclean; | |
+ | |
+requires qw/check_has_relation check_column_relation/; | |
+ | |
+with 'MooseX::Role::BuildInstanceOf' => | |
+{ | |
+ 'target' => 'Catalyst::Controller::DBIC::API::Validator', | |
+ 'prefix' => 'search_validator', | |
+}; | |
+ | |
+with 'MooseX::Role::BuildInstanceOf' => | |
+{ | |
+ 'target' => 'Catalyst::Controller::DBIC::API::Validator', | |
+ 'prefix' => 'select_validator', | |
+}; | |
+ | |
+with 'MooseX::Role::BuildInstanceOf' => | |
+{ | |
+ 'target' => 'Catalyst::Controller::DBIC::API::Validator', | |
+ 'prefix' => 'prefetch_validator', | |
+}; | |
+ | |
+parameter static => ( isa => Bool, default => 0 ); | |
+ | |
+role { | |
+ | |
+ my $p = shift; | |
+ | |
+ has 'count' => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_count', | |
+ isa => Int, | |
+ predicate => 'has_count', | |
+ traits => ['Aliased'], | |
+ alias => 'list_count' | |
+ ); | |
+ | |
+ has 'page' => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_page', | |
+ isa => Int, | |
+ predicate => 'has_page', | |
+ traits => ['Aliased'], | |
+ alias => 'list_page' | |
+ ); | |
+ | |
+ subtype 'OrderedBy' => as Maybe[ArrayRef[Str|HashRef|ScalarRef]]; | |
+ coerce 'OrderedBy' => from Str, via { [$_] }; | |
+ | |
+ has 'ordered_by' => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_ordered_by', | |
+ isa => 'OrderedBy', | |
+ predicate => 'has_ordered_by', | |
+ traits => ['Aliased'], | |
+ coerce => 1, | |
+ default => sub { $p->static ? [] : undef }, | |
+ alias => 'list_ordered_by', | |
+ ); | |
+ | |
+ subtype 'GroupedBy' => as Maybe[ArrayRef[Str]]; | |
+ coerce 'GroupedBy' => from Str, via { [$_] }; | |
+ | |
+ has 'grouped_by' => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_grouped_by', | |
+ isa => 'GroupedBy', | |
+ predicate => 'has_grouped_by', | |
+ traits => ['Aliased'], | |
+ coerce => 1, | |
+ default => sub { $p->static ? [] : undef }, | |
+ alias => 'list_grouped_by', | |
+ ); | |
+ | |
+ subtype 'Prefetch' => as Maybe[ArrayRef[Str|HashRef]]; | |
+ coerce 'Prefetch' => | |
+ from Str, via { [$_] }, | |
+ from HashRef, via { [$_] }; | |
+ | |
+ has prefetch => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_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 allowd 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; | |
+ } | |
+ }, | |
+ traits => ['Aliased'], | |
+ alias => 'list_prefetch', | |
+ ); | |
+ | |
+ has prefetch_allows => | |
+ ( | |
+ is => 'ro', | |
+ isa => ArrayRef[ArrayRef|Str|HashRef], | |
+ default => sub { [ ] }, | |
+ predicate => 'has_prefetch_allows', | |
+ trigger => sub | |
+ { | |
+ my ($self, $new) = @_; | |
+ foreach my $rel (@$new) | |
+ { | |
+ if(ArrayRef->check($rel)) | |
+ { | |
+ foreach my $rel_sub (@$rel) | |
+ { | |
+ $self->check_has_relation($rel_sub, undef, undef, $p->static); | |
+ $self->prefetch_validator->load($rel_sub); | |
+ } | |
+ } | |
+ elsif(HashRef->check($rel)) | |
+ { | |
+ $self->check_has_relation(%$rel, undef, $p->static); | |
+ $self->prefetch_validator->load($rel); | |
+ } | |
+ else | |
+ { | |
+ $self->check_has_relation($rel, undef, undef, $p->static); | |
+ $self->prefetch_validator->load($rel); | |
+ } | |
+ } | |
+ }, | |
+ traits => ['Aliased'], | |
+ alias => 'list_prefetch_allows', | |
+ ); | |
+ | |
+ has 'search_exposes' => | |
+ ( | |
+ is => 'ro', | |
+ isa => ArrayRef[Str|HashRef], | |
+ predicate => 'has_search_exposes', | |
+ traits => ['Aliased'], | |
+ default => sub { [ ] }, | |
+ alias => 'list_search_exposes', | |
+ trigger => sub | |
+ { | |
+ my ($self, $new) = @_; | |
+ $self->search_validator->load($_) for @$new; | |
+ }, | |
+ ); | |
+ | |
+ has 'search' => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_search', | |
+ isa => HashRef, | |
+ predicate => 'has_search', | |
+ trigger => sub | |
+ { | |
+ my ($self, $new) = @_; | |
+ | |
+ if($self->has_search_exposes and @{$self->search_exposes}) | |
+ { | |
+ while( my ($k, $v) = each %$new) | |
+ { | |
+ $DB::single = 1 if $k eq 'cd'; | |
+ local $Data::Dumper::Terse = 1; | |
+ die qq|{ $k => ${\Dumper($v)} } is not an allowed search term in: ${\join("\n", @{$self->search_validator->templates})}| | |
+ unless $self->search_validator->validate({$k=>$v})->[0]; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ while( my ($k, $v) = each %$new) | |
+ { | |
+ $self->check_column_relation({$k => $v}); | |
+ } | |
+ } | |
+ }, | |
+ ); | |
+ | |
+ has 'select_exposes' => | |
+ ( | |
+ is => 'ro', | |
+ isa => ArrayRef[Str|HashRef], | |
+ predicate => 'has_select_exposes', | |
+ default => sub { [ ] }, | |
+ traits => ['Aliased'], | |
+ alias => 'list_returns_exposes', | |
+ trigger => sub | |
+ { | |
+ my ($self, $new) = @_; | |
+ $self->select_validator->load($_) for @$new; | |
+ }, | |
+ ); | |
+ | |
+ subtype 'SelectColumns' => as Maybe[ArrayRef[Str|HashRef]]; | |
+ coerce 'SelectColumns' => from Str, via { [$_] }; | |
+ | |
+ has select => | |
+ ( | |
+ is => 'ro', | |
+ writer => '_set_select', | |
+ isa => 'SelectColumns', | |
+ default => sub { $p->static ? [] : undef }, | |
+ traits => ['Aliased'], | |
+ alias => 'list_returns', | |
+ coerce => 1, | |
+ trigger => sub | |
+ { | |
+ my ($self, $new) = @_; | |
+ if($self->has_select_exposes) | |
+ { | |
+ foreach my $val (@$new) | |
+ { | |
+ die "'$val' is not allowed in a select" | |
+ unless $self->select_validator->validate($val); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ $self->check_column_relation($_, $p->static) for @$new; | |
+ } | |
+ }, | |
+ ); | |
+ | |
+ has 'data' => | |
+ ( | |
+ is => 'ro', | |
+ isa => HashRef, | |
+ ); | |
+}; | |
+ | |
+1; | |
=== lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm | |
================================================================== | |
--- lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm (revision 12230) | |
+++ lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm (patch moosify-validation-configurability.patch level 1) | |
@@ -0,0 +1,86 @@ | |
+package Catalyst::Controller::DBIC::API::StoredResultSource; | |
+use Moose::Role; | |
+use Moose::Util::TypeConstraints; | |
+use MooseX::Types::Moose(':all'); | |
+use Try::Tiny; | |
+use namespace::autoclean; | |
+ | |
+requires '_application'; | |
+ | |
+has 'class' => ( is => 'ro', isa => Str ); | |
+ | |
+has 'stored_result_source' => | |
+( | |
+ is => 'ro', | |
+ isa => class_type('DBIx::Class::ResultSource'), | |
+ lazy_build => 1, | |
+); | |
+ | |
+has 'stored_model' => | |
+( | |
+ is => 'ro', | |
+ isa => class_type('DBIx::Class'), | |
+ lazy_build => 1, | |
+); | |
+ | |
+sub _build_stored_model | |
+{ | |
+ return $_[0]->_application->model($_[0]->class); | |
+} | |
+ | |
+sub _build_stored_result_source | |
+{ | |
+ return shift->stored_model->result_source(); | |
+} | |
+ | |
+sub check_has_column | |
+{ | |
+ my ($self, $col) = @_; | |
+ confess "Column '$col' does not exist in ResultSet '${\$self->class}'" | |
+ unless $self->stored_result_source->has_column($col); | |
+} | |
+ | |
+sub check_has_relation | |
+{ | |
+ my ($self, $rel, $other, $nest, $static) = @_; | |
+ | |
+ $nest ||= $self->stored_result_source; | |
+ | |
+ if(HashRef->check($other)) | |
+ { | |
+ my $rel_src = $nest->related_source($rel); | |
+ die "Relation '$rel_src' does not exist" if not defined($rel_src); | |
+ return $self->check_has_relation(%$other, $rel_src, $static); | |
+ } | |
+ else | |
+ { | |
+ return 1 if $static && ArrayRef->check($other) && $other->[0] eq '*'; | |
+ die "Relation '$rel' does not exist in ${\ref($nest)}" | |
+ unless $nest->has_relationship($rel) || $nest->has_column($rel); | |
+ return 1; | |
+ } | |
+} | |
+ | |
+sub check_column_relation | |
+{ | |
+ my ($self, $col_rel, $static) = @_; | |
+ | |
+ if(HashRef->check($col_rel)) | |
+ { | |
+ try | |
+ { | |
+ $self->check_has_relation(%$col_rel, undef, $static); | |
+ } | |
+ catch | |
+ { | |
+ # not a relation but a column with a predicate | |
+ $self->check_has_column(keys %$col_rel); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ $self->check_has_column($col_rel); | |
+ } | |
+} | |
+ | |
+1; | |
=== Makefile.PL | |
================================================================== | |
--- Makefile.PL (revision 12230) | |
+++ Makefile.PL (patch moosify-validation-configurability.patch level 1) | |
@@ -10,6 +10,7 @@ | |
requires 'CGI::Expand' => 2.02; | |
requires 'JSON::Any' => 1.19; | |
requires 'Test::Deep' => 0.104; | |
+requires 'Data::DPath::Validator' => 0.093411; | |
build_requires 'Test::More' => 0.88; | |
build_requires 'Catalyst::Model::DBIC::Schema' => 0.20; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment