package Plack::Middleware::REST;
{
  $Plack::Middleware::REST::VERSION = '0.01';
}
#ABSTRACT: Route PSGI requests for RESTful web applications
use strict;
use warnings;

use Carp qw(croak);
use Scalar::Util qw(reftype);

use parent 'Plack::Middleware';
use Plack::Util::Accessor qw(get create upsert delete list pass_through);

our %METHOD = (
	resource   => {
	    GET    => 'get',
    	PUT    => 'upsert', 
	    DELETE => 'delete',
	},
	collection => {
		GET	   => 'list',
		POST   => 'create',
	},
);

sub prepare_app {
	my ($self) = @_;

	$self->pass_through(0)
		unless defined $self->pass_through;

	my @actions = qw(get create upsert delete list);
	foreach my $action (@actions)  {
		my $app = $self->{$action};

		# alias
		$self->{$action} = $self->{$app} if $app and !ref $app;

		croak "PSGI application '$action' must be code reference"
			if $self->{action} and (reftype($self->{$action}) || '') ne 'CODE';
	}

	while (my ($type,$method) = each %METHOD) {
		my @allow = sort grep { $self->{ $method->{$_} } } keys %$method;
		$self->{allow}->{$type} = \@allow;
	}
}

sub call {
	my ($self, $env) = @_;

	my $type   = ($env->{PATH_INFO} || '/') eq '/' 
		? 'collection' : 'resource';

	my $method = $METHOD{ $type }->{ $env->{REQUEST_METHOD} };

	my $app = $method ? $self->{ $method } : undef;

	$app ||= $self->{app} if $self->pass_through; 

	if ( $app ) {
		$app->($env);
	} else {
		my $allow = join ', ', @{ $self->{allow}->{$type} };
	    [ 405, [ Allow => $allow ], ['Method Not Allowed'] ];
	}
}

1;


__END__
=pod

=head1 NAME

Plack::Middleware::REST - Route PSGI requests for RESTful web applications

=head1 VERSION

version 0.01

=head1 SYNOPSIS

    # $get, $create, $update, $list, $app must be PSGI applications
	builder {
		enable 'REST',
			get          => $get,      # HTTP GET on a resource
			create       => $create,   # HTTP POST in '/'
			upsert       => $update,   # HTTP PUT on a resource
			list         => $list,     # HTTP GET on '/'
			pass_through => 1;         # pass if no defined REST request
		$app; 
	};

=head1 DESCRIPTION

Plack::Middleware::REST routes HTTP requests, given in L<PSGI> request format,
based on the principles of Representational State Transfer (REST). In short,
the application manages a set of E<resources> on a common base URL, each
identified by its URL. One can retrieve, create, update, delete, and list
resources based on HTTP request methods. 

Let's say an instance of Plack::Middleware::REST is mounted at the base URL
C<http://example.org/item/>. The following HTTP request types can be
recognized, once they have been assigned on constructing the middleware:

=over 4

=item C<POST> at C<http://example.org/item/>

Calls the PSGI application C<create> to create a new resource with URL assigned
by the application.

=item C<GET> at C<http://example.org/item/123>

Calls the application C<get> to retrieve an existing resource identified by
C<http://example.org/item/123>.

=item C<PUT> at C<http://example.org/item/123>

Calls the PSGI application C<upsert> to either update an existing resource
identified by C<http://example.org/item/123> or to create a new resource with
this URL. The application may reject updates and/or creation of new resources,
acting like an update or insert method.

=item C<DELETE> at C<http://example.org/item/123>

Calls the PSGI application C<delete> to delete an existing resource identified
by C<http://example.org/item/123>.

=item C<GET> at C<http://example.org/item/>

Calls the PSGI application C<list> to get a list of existing resources.

=back

Additional HTTP request types C<HEAD>, C<OPTIONS>, and C<PATCH> may be added in
a later version of this module.

Other requests result either result in a PSGI response with error code 405 and
a list of possible request types in the C<Accept> header, or the request is
passed to the underlying application in the PSGI middleware stack, if option
C<pass_through> is set.

=head1 CONFIGURATION

The options C<get>, C<create>, C<upsert>, C<delete>, C<list> can be set to PSGI
applications to enable the corresponding REST request type. One can also use
string aliases, including C<app> to pass the request in the middleware stack:

    builder {
	    enable 'REST',
			get          => 'app',   # pass GET requests on resource to $wrapped
			create       => $create, # pass POST to base URL to $create
			upsert       => $update; # pass PUT requests on resources to $update
			pass_through => 0;       # respond other requests with 405
	    $wrapped;
	};

=head1 SEE ALSO

See L<Plack::Middleware::Negotiate> to add content negotiation.

=head1 AUTHOR

Jakob Voß <voss@gbv.de>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Jakob Voß.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

