Skip to content

Instantly share code, notes, and snippets.

@nperez
Created December 9, 2009 18:38
Show Gist options
  • Save nperez/252662 to your computer and use it in GitHub Desktop.
Save nperez/252662 to your computer and use it in GitHub Desktop.
==== 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