[Catalyst-commits] r12449 - in Catalyst-Controller-DBIC-API/1.003/trunk: . lib/Catalyst/Controller/DBIC lib/Catalyst/Controller/DBIC/API t/lib/RestTest/Controller/API/RPC t/rest t/rpc t/var

lukes at dev.catalyst.perl.org lukes at dev.catalyst.perl.org
Mon Dec 21 17:31:29 GMT 2009


Author: lukes
Date: 2009-12-21 17:31:29 +0000 (Mon, 21 Dec 2009)
New Revision: 12449

Added:
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Request.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StaticArguments.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Types.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Validator.pm
Modified:
   Catalyst-Controller-DBIC-API/1.003/trunk/Changes
   Catalyst-Controller-DBIC-API/1.003/trunk/Makefile.PL
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Base.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/REST.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RPC.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/CD.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/TrackExposed.pm
   Catalyst-Controller-DBIC-API/1.003/trunk/t/rest/list.t
   Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list.t
   Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_json_search.t
   Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_prefetch.t
   Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_search_allows.t
   Catalyst-Controller-DBIC-API/1.003/trunk/t/var/DBIxClass.db
Log:
manually merged

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/Changes
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/Changes	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/Changes	2009-12-21 17:31:29 UTC (rev 12449)
@@ -1,5 +1,13 @@
 Revision history for Catalyst-Controller-DBIC-API
 
+1.004000
+- Moosify
+- Move validation for *_exposes/*_allows to Data::DPath::Validator
+- Reorganize internals to use Moose and roles
+- Allow maximum configuration for what request parameters are named
+- Properly handle JSON boolean values
+- Earlier and more consistent validation of configuration and request parameters
+
 1.003004
 - Database errors are also handled for searches + tests
 - totalcount isn't included in the response if a db error occurs while fetching data

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/Makefile.PL
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/Makefile.PL	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/Makefile.PL	2009-12-21 17:31:29 UTC (rev 12449)
@@ -10,6 +10,8 @@
 requires 'CGI::Expand' => 2.02;
 requires 'JSON::Any' => 1.19;
 requires 'Test::Deep' => 0.104;
+requires 'Data::DPath::Validator' => 0.093411;
+requires 'MooseX::Aliases' => 0.07;
 
 build_requires 'Test::More'       => 0.88;
 build_requires 'Catalyst::Model::DBIC::Schema' => 0.20;

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Base.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Base.pm	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Base.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -1,46 +1,45 @@
-package													# hide from PAUSE
+package		# hide from PAUSE
 	Catalyst::Controller::DBIC::API::Base;
+our $VERSION = '1.004000';
 
-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('eq_deeply');
+use MooseX::Types::Moose(':all');
+use MooseX::Aliases;
+use Moose::Util;
+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');
 
+__PACKAGE__->config();
+
+sub begin :Private {
+    my ($self, $c) = @_;
+    
+    Catalyst::Controller::DBIC::API::Request->meta->apply($c->req)
+        unless Moose::Util::does_role($c->req, 'Catalyst::Controller::DBIC::API::Request');
+    $c->forward('deserialize');
+}
+
 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,16 +50,50 @@
 	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 {
+			try 
+            {
 				my $deserialized = JSON::Any->from_json($req_params->{$param});
 				$req_params->{$param} = $deserialized;
-			};
+			}
+            catch { $c->log->debug("Param '$param' did not deserialize appropriately: $_") if $c->debug }
 		}
 	}
-	$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
+    {
+        # set static arguments
+        $c->req->_set_application($self); 
+        $c->req->_set_prefetch_allows($self->prefetch_allows);
+        $c->req->_set_search_exposes($self->search_exposes);
+        $c->req->_set_select_exposes($self->select_exposes);
+        $c->req->_set_request_data($req_params->{data});
+
+        # set request arguments
+        $c->req->_set_prefetch($req_params->{$self->prefetch_arg}) if exists $req_params->{$self->prefetch_arg};
+        $c->req->_set_select($req_params->{$self->select_arg}) if exists $req_params->{$self->select_arg};
+        $c->req->_set_grouped_by($req_params->{$self->grouped_by_arg}) if exists $req_params->{$self->grouped_by_arg};
+        $c->req->_set_ordered_by($req_params->{$self->ordered_by_arg}) if exists $req_params->{$self->ordered_by_arg};
+        $c->req->_set_search($req_params->{$self->search_arg}) if exists $req_params->{$self->search_arg};
+        $c->req->_set_count($req_params->{$self->count_arg}) if exists $req_params->{$self->count_arg};
+        $c->req->_set_page($req_params->{$self->page_arg}) if exists $req_params->{$self->page_arg};
+    }
+    catch
+    {
+        $self->push_error($c, { message => $_ });
+    }
 }
 
 sub list :Private {
@@ -71,86 +104,72 @@
 	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 {
+    try 
+    {
         $c->stash->{_dbic_api}->{totalcount} = $c->stash->{$self->rs_stash_key}->pager->total_entries
             if $args->{page};
-    };
-    if ($@) {
-        $c->log->error($@);
+        $c->forward('format_list');
+    }
+    catch
+    {
+        $c->log->error($_);
         # send a generic error to the client to not give out infos about
         # the database schema
         $self->push_error($c, { message => 'a database error has occured.' });
     }
-    else {
-        $c->forward('format_list');
-    }
 }
 
 sub generate_dbic_search_args :Private {
 	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 = $c->req;
+    my $pre_format_params;
 
 	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->request_data, $req ]);
+            if(exists($req->request_data->{$self->search_arg}))
+            {
+                if(!$req->has_search)
+                {
+                    $req->_set_search($req->request_data->{$self->search_arg});
+                }
+                elsif(!eq_deeply($req->has_search, $req->request_data->{$self->search_arg}))
+                {
+                    $req->_set_search($req->request_data->{$self->search_arg});
+                }
+            }
 		} 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,42 +191,29 @@
 
 	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});
-		next if ($_rel_map{$column} && ref $params->{$column});
+		next if ($_rel_map{$column} && (ref $params->{$column} && !($params->{$column} == JSON::Any::true() || $params->{$column} == JSON::Any::false())));
 
 		if ($_source_col_map{$column}) {
 			$search_params{join('.', $base, $column)} = $params->{$column};
@@ -235,16 +241,9 @@
 	# it still is what they expect (and not inflating to a hash ref)
 	my $rs = $c->stash->{$self->rs_stash_key}->search;
 	$rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-    eval {
-	    $c->stash->{response}->{list} = [ $rs->all ];
-    };
-    if ($@) {
-        $c->log->error($@);
-        # send a generic error to the client to not give out infos about
-        # the database schema
-        $self->push_error($c, { message => 'a database error has occured.' });
-    }
-    else {
+    try
+    {
+	    $c->stash->{response}->{$self->data_root} = [ $rs->all ];
         # only add the totalcount to the response if also data is returned
         if (my $totalcount = $c->stash->{_dbic_api}->{totalcount}) {
             # numify which is important for JSON
@@ -252,6 +251,13 @@
             $c->stash->{response}->{totalcount} = $totalcount;
         }
     }
+    catch
+    {
+        $c->log->error($_);
+        # send a generic error to the client to not give out infos about
+        # the database schema
+        $self->push_error($c, { message => 'a database error has occured.' });
+    }
 }
 
 sub create :Private {
@@ -266,14 +272,13 @@
 
 	my $empty_object = $c->stash->{$self->rs_stash_key}->new_result({});
 	$c->stash->{created_object} = $self->validate_and_save_object($c, $empty_object);
+    %{$c->stash->{response}->{$self->data_root}} = $c->stash->{created_object}->get_inflated_columns
+        if $self->return_object;
 }
 
 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} );
 
@@ -285,7 +290,10 @@
 	}
 
 	my $object = $c->stash->{$self->object_stash_key};
-	$self->validate_and_save_object($c, $object);
+	$object = $self->validate_and_save_object($c, $object);
+    %{$c->stash->{response}->{$self->data_root}} = $object->get_inflated_columns
+        if $self->return_object;
+
 }
 
 sub delete :Private {
@@ -312,7 +320,7 @@
 
 sub validate {
 	my ($self, $c, $object) = @_;
-	my $params = $c->stash->{_dbic_api}->{req_params};
+	my $params = $c->req->request_data();
 
 	my %values;
 	my %requires_map = map { $_ => 1 } @{($object->in_storage) ? [] : $c->stash->{create_requires} || $self->create_requires};
@@ -323,20 +331,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}) {
@@ -361,8 +356,9 @@
 			# TODO: do automatic col type checking here
 			
 			# check for multiple values
-			if (ref($value)) {
-				$self->push_error($c, { message => "Multiple values for '${key}'" });
+			if (ref($value) && !($value == JSON::Any::true || $value == JSON::Any::false)) {
+                require Data::Dumper;
+				$self->push_error($c, { message => "Multiple values for '${key}': ${\Data::Dumper::Dumper($value)}" });
 			}
 
 			# check exists so we don't just end up with hash of undefs
@@ -382,10 +378,12 @@
 sub save_object {
 	my ($self, $c, $object, $params) = @_;
 
-    eval {
+    try
+    {
     	if ($object->in_storage) {
     		foreach my $key (keys %{$params}) {
-    			if (ref $params->{$key}) {
+                my $value = $params->{$key};
+    			if (ref($value) && !($value == JSON::Any::true || $value == JSON::Any::false)) {
     				my $related_params = delete $params->{$key};
     				my $row = $object->find_related($key, {} , {});
     				$row->update($related_params);
@@ -396,15 +394,16 @@
     		$object->set_columns($params);
     		$object->insert;
     	}
-    };
-    if ($@) {
+    }
+    catch
+    {
         $c->log->error($@);
         # send a generic error to the client to not give out infos about
         # the database schema
         $self->push_error($c, { message => 'a database error has occured.' });
-    }
-
-	return $object;
+    };
+    
+    return $object;
 }
 
 sub end :Private {
@@ -416,17 +415,17 @@
 	# 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;
 	}
 	
-	delete $c->stash->{response}->{list} unless ($default_status == 200);
+	delete $c->stash->{response}->{$self->data_root} unless ($default_status == 200);
 	$c->res->status( $default_status || 200 );
 	$c->forward('serialize');
 }
@@ -454,6 +453,10 @@
 
 Luke Saunders <luke.saunders at gmail.com>
 
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
+
 =cut
 
 1;

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/REST.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/REST.pm	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/REST.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -1,9 +1,8 @@
 package Catalyst::Controller::DBIC::API::REST;
+our $VERSION = '1.004000';
+use Moose;
+BEGIN { extends 'Catalyst::Controller::DBIC::API::Base'; }
 
-use strict;
-use warnings;
-use base qw/Catalyst::Controller::DBIC::API::Base/;
-
 __PACKAGE__->config(
 						'default'   => 'application/json',
 						'stash_key' => 'response',
@@ -74,16 +73,10 @@
 
 =cut 
 
-sub begin :Private {
-	my ($self, $c) = @_;
-
-	$c->forward('deserialize');
-}
-
 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

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RPC.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RPC.pm	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RPC.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -1,10 +1,8 @@
 package Catalyst::Controller::DBIC::API::RPC;
+our $VERSION = '1.004000';
+use Moose;
+BEGIN { extends 'Catalyst::Controller::DBIC::API::Base'; }
 
-use strict;
-use warnings;
-use base qw/Catalyst::Controller::DBIC::API::Base/;
-use JSON::Syck;
-
 __PACKAGE__->config(
 						'default'   => 'application/json',
 						'stash_key' => 'response',
@@ -86,13 +84,6 @@
 
 =cut 
 
-sub begin :Private {
-	my ($self, $c) = @_;
-
-	$c->forward('deserialize');
-	$c->req->params($c->stash->{_dbic_api}->{req_params});
-}
-
 sub object :Chained('setup') :CaptureArgs(1) :PathPart('id') {
 	my ($self, $c, $id) = @_;
 

Added: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Request.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Request.pm	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Request.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -0,0 +1,22 @@
+package Catalyst::Controller::DBIC::API::Request;
+our $VERSION = '1.004000';
+use Moose::Role;
+use MooseX::Aliases;
+use MooseX::Types::Moose('Object');
+use namespace::autoclean;
+
+### XXX Stupid hack to make role attribute handles work
+sub check_has_relation { }
+sub check_column_relation { }
+
+has 'application' =>
+(
+    is => 'ro',
+    writer => '_set_application',
+    isa => Object,
+    handles => 'Catalyst::Controller::DBIC::API::StoredResultSource',
+);
+
+with 'Catalyst::Controller::DBIC::API::RequestArguments';
+
+1;

Added: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -0,0 +1,247 @@
+package Catalyst::Controller::DBIC::API::RequestArguments;
+our $VERSION = '1.004000';
+use MooseX::Role::Parameterized;
+use Catalyst::Controller::DBIC::API::Types(':all');
+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'
+    );
+
+    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',
+    );
+
+    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',
+    );
+
+    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',
+        writer => '_set_prefetch_allows',
+        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',
+        writer => '_set_search_exposes',
+        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)
+                {
+                    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',
+        writer => '_set_select_exposes',
+        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;
+        },
+    );
+
+    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 'request_data' =>
+    (
+        is => 'ro',
+        writer => '_set_request_data',
+        isa => HashRef,
+    );
+};
+
+1;

Added: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StaticArguments.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StaticArguments.pm	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StaticArguments.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -0,0 +1,48 @@
+package Catalyst::Controller::DBIC::API::StaticArguments;
+our $VERSION = '1.004000';
+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 );
+has 'return_object' => ( is => 'ro', isa => Bool, default => 0 );
+
+1;

Added: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/StoredResultSource.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -0,0 +1,87 @@
+package Catalyst::Controller::DBIC::API::StoredResultSource;
+our $VERSION = '1.004000';
+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;

Added: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Types.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Types.pm	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Types.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -0,0 +1,22 @@
+package Catalyst::Controller::DBIC::API::Types;
+our $VERSION = '1.004000';
+
+use warnings;
+use strict;
+
+use MooseX::Types -declare => [qw/OrderedBy GroupedBy Prefetch SelectColumns/];
+use MooseX::Types::Moose(':all');
+
+subtype Prefetch, as Maybe[ArrayRef[Str|HashRef]];
+coerce Prefetch, from Str, via { [$_] }, from HashRef, via { [$_] };
+
+subtype GroupedBy, as Maybe[ArrayRef[Str]];
+coerce GroupedBy, from Str, via { [$_] };
+
+subtype OrderedBy, as Maybe[ArrayRef[Str|HashRef|ScalarRef]];
+coerce OrderedBy, from Str, via { [$_] };
+
+subtype SelectColumns, as Maybe[ArrayRef[Str|HashRef]];
+coerce SelectColumns, from Str, via { [$_] };
+
+1;

Added: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Validator.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Validator.pm	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API/Validator.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -0,0 +1,110 @@
+package Catalyst::Controller::DBIC::API::Visitor;
+our $VERSION = '1.004000';
+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;
+our $VERSION = '1.004000';
+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;

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API.pm	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/lib/Catalyst/Controller/DBIC/API.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -5,11 +5,11 @@
 
 =head1 VERSION
 
-Version 1.003004
+Version 1.004
 
 =cut
 
-our $VERSION = '1.003004';
+our $VERSION = '1.004000';
 
 =head1 NAME
 
@@ -27,16 +27,19 @@
       create_allows => ['nickname'], # additional non-required columns that create allows
       update_allows => ['name', 'age', 'nickname'], # columns that update allows
       update_allows => ['name', 'age', 'nickname'], # columns that update allows
-      list_returns => [qw/name age/], # columns that list returns
-      list_prefetch => ['cds'], # relationships that are prefetched when no prefetch param is passed
-      list_prefetch_allows => [ # every possible prefetch param allowed
+      select => [qw/name age/], # columns that data returns
+      prefetch => ['cds'], # relationships that are prefetched when no prefetch param is passed
+      prefetch_allows => [ # every possible prefetch param allowed
           'cds',
           qw/ cds /,
           { cds => 'tracks' },
           { cds => [qw/ tracks /] }
       ],
-      list_ordered_by => [qw/age/], # order of generated list
-      list_search_exposes => [qw/age nickname/, { cds => [qw/title year/] }], # columns that can be searched on via list
+      ordered_by => [qw/age/], # order of generated list
+      search_exposes => [qw/age nickname/, { cds => [qw/title year/] }], # columns that can be searched on via list
+      data_root => 'data' # defaults to "list" for backwards compatibility
+      use_json_boolean => 1, # use JSON::Any::true|false in the response instead of strings
+      return_object => 1, # makes create and update actions return the object
       );
 
   # Provides the following functional endpoints:
@@ -86,6 +89,18 @@
 
 Whatever you would pass to $c->model to get a resultset for this class. MyAppDB::Track for example.
 
+head2 data_root
+
+By default, the response data is serialized into $c->stash->{response}->{$self->data_root} and data_root defaults to 'list' to preserve backwards compatibility. This is now configuable to meet the needs of the consuming client.
+
+head2 use_json_boolean
+
+By default, the response success status is set to a string value of "true" or "false". If this attribute is true, JSON::Any's true() and false() will be used instead. Note, this does not effect other internal processing of boolean values.
+
+head2 count_arg, page_arg, select_arg, search_arg, grouped_by_arg, ordered_by_arg, prefetch_arg
+
+These attributes allow customization of the component to understand requests made by clients where these argument names are not flexible and cannot conform to this components defaults.
+
 =head2 create_requires
 
 Arrayref listing columns required to be passed to create in order for the request to be valid.
@@ -98,15 +113,19 @@
 
 Arrayref listing columns that update will allow. Columns passed to update that are not listed here will be ignored.
 
-=head2 list_returns
+=head2 select
 
 Arguments to pass to L<DBIx::Class::ResultSet/select> when performing search for L</list>.
 
-=head2 list_prefetch
+=head2 select_exposes
 
+Columns and related columns that are okay to return in the resultset since clients can request more or less information specified than the above select argument.
+
+=head2 prefetch
+
 Arguments to pass to L<DBIx::Class::ResultSet/prefetch> when performing search for L</list>.
 
-=head2 list_prefetch_allows
+=head2 prefetch_allows
 
 Arrayref listing relationships that are allowed to be prefetched.
 This is necessary to avoid denial of service attacks in form of
@@ -116,21 +135,20 @@
 So for three searches, all requiring different prefetch parameters,
 three elements have to be passed to list_prefetch_allows in the controller.
 
-=head2 list_grouped_by
+=head2 grouped_by
 
 Arguments to pass to L<DBIx::Class::ResultSet/group_by> when performing search for L</list>.
 
-=head2 list_ordered_by
+=head2 ordered_by
 
 Arguments to pass to L<DBIx::Class::ResultSet/order_by> when performing search for L</list>.
 
-=head2 list_search_exposes
+=head2 search_exposes
 
 Columns and related columns that are okay to search on. For example if only the position column and all cd columns were to be allowed
 
- list_search_exposes => [qw/position/, { cd => ['*'] }]
+ search_exposes => [qw/position/, { cd => ['*'] }]
 
-
 You can also use this to allow custom columns should you wish to allow them through in order to be caught by a custom resultset. For example:
 
   package RestTest::Controller::API::RPC::TrackExposed;
@@ -159,11 +177,11 @@
     my $rs = $self->SUPER::search(@_);
   }
 
-=head2 list_count
+=head2 count
 
 Arguments to pass to L<DBIx::Class::ResultSet/rows> when performing search for L</list>.
 
-=head2 list_page
+=head2 page
 
 Arguments to pass to L<DBIx::Class::ResultSet/rows> when performing search for L</list>.
 
@@ -216,6 +234,10 @@
 
 Note: see the individual interface classes - L<Catalyst::Controller::DBIC::API::RPC> and L<Catalyst::Controller::DBIC::API::REST> - for details of the endpoints to these abstract methods.
 
+=head2 begin
+
+A begin method is provided to apply the L<Catalyst::Controller::DBIC::API::Request> role to $c->request, and perform deserialization and validation of request parameters
+
 =head2 setup
 
 This action is the chain root of the controller. It must either be overridden or configured to provide a base pathpart to the action and also a parent action. For example, for class MyAppDB::Track you might have
@@ -246,11 +268,11 @@
 
 List level action chained from L</setup>. Checks $c->req->params for each column specified in the L</create_requires> and L</create_allows> parameters of the controller config. If all of the required columns are present then the object is created.
 
-Does not populate the response with any additional information.
+Does not populate the response with any additional information unless the return_object option is set to true, then the created object will be serialized within $c->stash->{response}->{$self->data_root}.
 
 =head2 list
 
-List level action chained from L</setup>. By default populates $c->stash->{response}->{list} with a list of hashrefs representing each object in the class resultset. If the L</list_returns> config param is defined then the hashes will contain only those columns, otherwise all columns in the object will be returned. Similarly L</list_count>, L</list_page>, L</list_grouped_by> and L</list_ordered_by> affect the maximum number of rows returned as well as the ordering and grouping. Note that if list_returns, list_count, list_ordered_by or list_grouped_by request parameters are present then these will override the values set on the class.
+List level action chained from L</setup>. By default populates $c->stash->{response}->{$self->data_root} with a list of hashrefs representing each object in the class resultset. If the L</select> config param is defined then the hashes will contain only those columns, otherwise all columns in the object will be returned. Similarly L</count>, L</page>, L</grouped_by> and L</ordered_by> affect the maximum number of rows returned as well as the ordering and grouping. Note that if select, count, ordered_by or grouped_by request parameters are present then these will override the values set on the class with select becoming bound by the select_exposes attribute.
 
 If not all objects in the resultset are required then it's possible to pass conditions to the method as request parameters. You can use a JSON string as the 'search' parameter for maximum flexibility or use L</CGI::Expand> syntax. In the second case the request parameters are expanded into a structure and then $c->req->params->{search} is used as the search condition.
 
@@ -266,7 +288,7 @@
 
 Note that if pagination is needed, this can be achieved using a combination of the L</list_count> and L</list_page> parameters. For example:
 
-  ?list_page=2&list_count=20
+  ?page=2&count=20
 
 Would result in this search:
  
@@ -276,13 +298,13 @@
 
 =head2 format_list
 
-Used by L</list> to populate response based on class resultset. By default populates $c->stash->{response}->{list} with a list of hashrefs representing each object in the resultset. Can be overidden to format the list as required.
+Used by L</list> to populate response based on class resultset. By default populates $c->stash->{response}->{$self->data_root} with a list of hashrefs representing each object in the resultset. Can be overidden to format the list as required.
 
 =head2 update
 
 Object level action chained from L</object>. Checks $c->req->params for each column specified in the L</update_allows> parameter of the controller config. If any of these columns are found in $c->req->params then the object set by L</object> is updated with those columns.
 
-Does not populate the response with any additional information.
+Does not populate the response with any additional information uness the return_object option is set to true, then the updated object will be serialized within $c->stash->{response}->{$self->data_root}.
 
 =head2 delete
 
@@ -292,17 +314,20 @@
 
 =head2 end
 
-If the request was successful then $c->stash->{response}->{success} is set to 1, if not then it is set to 0 and $c->stash->{response}->{messages} set to an arrayref containing all error messages.
+$c->stash->{response}->{success} is set to 'true' or 'false' (or their respective JSON::Any values for true and false) regarding the success of the request. If the request failed, $c->stash->{response}->{messages} is set to an arrayref containing all error messages.
 
 Then the contents of $c->stash->{response} are serialized using L<Catalyst::Action::Serialize>.
 
 =head1 EXTENDING
 
-By default the create, delete and update actions will not return anything apart from the success parameter set in L</end>, often this is not ideal but the required behaviour varies from application to application. So normally it's sensible to write an intermediate class which your main controller classes subclass from. For example if you wanted create to return the JSON for the newly created object you might have something like:
+By default the create, delete and update actions will not return anything apart from the success parameter set in L</end>, often this is not ideal but the required behaviour varies from application to application. So normally it's sensible to write an intermediate class which your main controller classes subclass from.
 
+For example if you wanted create to return the JSON for the newly created object you might have something like:
+
   package MyApp::ControllerBase::DBIC::API::RPC;
   ...
-  use base qw/Catalyst::Controller::DBIC::API::RPC/;
+  use Moose;
+  BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC' };
   ...
   sub create :Chained('setup') :Args(0) :PathPart('create') {
     my ($self, $c) = @_;
@@ -319,19 +344,36 @@
 
   package MyApp::Controller::API::RPC::Track;
   ...
-  use base qw/MyApp::ControllerBase::DBIC::API::RPC/;
+  use Moose;
+  BEGIN { extends 'MyApp::ControllerBase::DBIC::API::RPC' };
   ...
 
-If you were using the RPC style. For REST the only difference besides the class names would be that create should be :Private rather than an endpoint.
+It should be noted that the L</return_object> attribute will produce the above result for you, free of charge.
 
+For REST the only difference besides the class names would be that create should be :Private rather than an endpoint.
+
 Similarly you might want create, update and delete to all forward to the list action once they are done so you can refresh your view. This should also be simple enough.
 
+If more extensive customization is required, it is recommened to peer into the roles that comprise the system and make use 
+
+=head1 NOTES
+
+It should be noted that version 1.004 and above makes a rapid depature from the status quo. The internals were revamped to use more modern tools such as Moose and its role system to refactor functionality out into self-contained roles.
+
+To this end, internally, this module now understands JSON boolean values (as represented by JSON::Any) and will Do The Right Thing in handling those values. This means you can have ColumnInflators installed that can covert between JSON::Any booleans and whatever your database wants for boolean values.
+
+Validation for various *_allows or *_exposes is now accomplished via Data::DPath::Validator with a lightly simplified, via subclass, Data::DPath::Validator::Visitor. The rough jist of the process goes as follows: Arguments provided to those attributes are fed into the Validator and Data::DPaths are generated. Then, incoming requests are validated against these paths generated. The validator is set in "loose" mode meaning only one path is required to match. for more information, please see L<Data::DPath::Validator> and more specifically L<Catalyst::Controller::DBIC::API::Validator>.
+
+All in all, significant efforts have been made to preserve backwards compatibility with previous versions. This means arguments to config and even internal structures (ie, the stash) should Just Work. This is accomplished by using L<MooseX::Aliases> to provide a mapping from old names to new names. Even the validator behavior /should/ be the same if not a bit more consistent. Internal validation of ->config arguments also happens much, much sooner. And, request parameters are validated as upfront as possible before ->search.
+
 =head1 AUTHOR
 
   Luke Saunders <luke.saunders at gmail.com>
 
 =head1 CONTRIBUTORS
 
+  Nicholas Perez <nperez at cpan.org>
+
   J. Shirley <jshirley at gmail.com>
 
   Zbigniew Lukasiak <zzbbyy at gmail.com>

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/CD.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/CD.pm	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/CD.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -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

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/TrackExposed.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/TrackExposed.pm	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/lib/RestTest/Controller/API/RPC/TrackExposed.pm	2009-12-21 17:31:29 UTC (rev 12449)
@@ -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;

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/rest/list.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/rest/list.t	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/rest/list.t	2009-12-21 17:31:29 UTC (rev 12449)
@@ -78,7 +78,6 @@
 
   my @expected_response = map { { $_->get_columns } } $schema->resultset('Artist')->search({ 'cds.title' => 'Forkful of bees' }, { join => 'cds' })->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 data returned for class with list_returns specified' );
 }
 

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list.t	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list.t	2009-12-21 17:31:29 UTC (rev 12449)
@@ -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');
+    
 }
 
 {

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_json_search.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_json_search.t	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_json_search.t	2009-12-21 17:31:29 UTC (rev 12449)
@@ -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' );
 }
 
 {

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_prefetch.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_prefetch.t	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_prefetch.t	2009-12-21 17:31:29 UTC (rev 12449)
@@ -10,7 +10,7 @@
 use RestTest;
 use DBICTest;
 use URI;
-use Test::More tests => 15;
+use Test::More tests => 17;
 use Test::WWW::Mechanize::Catalyst 'RestTest';
 use HTTP::Request::Common;
 use JSON::Syck;
@@ -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/not an allowed prefetch in:/, 'correct message returned' );
 }
 
 {

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_search_allows.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_search_allows.t	2009-12-21 17:28:49 UTC (rev 12448)
+++ Catalyst-Controller-DBIC-API/1.003/trunk/t/rpc/list_search_allows.t	2009-12-21 17:31:29 UTC (rev 12449)
@@ -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' );
 }
 

Modified: Catalyst-Controller-DBIC-API/1.003/trunk/t/var/DBIxClass.db
===================================================================
(Binary files differ)




More information about the Catalyst-commits mailing list