The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Catalyst::ActionRole::HTTPMethods;

use Moose::Role;

requires 'match', 'match_captures', 'list_extra_info';

sub allowed_http_methods { @{shift->attributes->{Method}||[]} }

sub _has_expected_http_method {
  my ($self, $expected) = @_;
  return 1 unless scalar(my @allowed = $self->allowed_http_methods);
  return scalar(grep { lc($_) eq lc($expected) } @allowed) ?
    1 : 0;
}

around ['match','match_captures'] => sub {
  my ($orig, $self, $ctx, @args) = @_;
  return 0 unless $self->$orig($ctx, @args);

  my $expected = $ctx->req->method;
  return $self->_has_expected_http_method($expected);
};

around 'list_extra_info' => sub {
  my ($orig, $self, @args) = @_;
  return {
    %{ $self->$orig(@args) }, 
    HTTP_METHODS => [sort $self->allowed_http_methods],
  };
};

1;

=head1 NAME

Catalyst::ActionRole::HTTPMethods - Match on HTTP Methods

=head1 SYNOPSIS

    package MyApp::Web::Controller::MyController;

    use Moose;
    use MooseX::MethodAttributes;

    extends 'Catalyst::Controller';

    sub user_base : Chained('/') CaptureArg(0) { ... }

      sub get_user     : Chained('user_base') Args(1) GET { ... }
      sub post_user    : Chained('user_base') Args(1) POST { ... }
      sub put_user     : Chained('user_base') Args(1) PUT { ... }
      sub delete_user  : Chained('user_base') Args(1) DELETE { ... }
      sub head_user    : Chained('user_base') Args(1) HEAD { ... }
      sub options_user : Chained('user_base') Args(1) OPTIONS { ... }
      sub patch_user   : Chained('user_base') Args(1) PATCH { ... }


      sub post_and_put : Chained('user_base') POST PUT Args(1) { ... }
      sub method_attr  : Chained('user_base') Method('DELETE') Args(0) { ... }

    __PACKAGE__->meta->make_immutable;

=head1 DESCRIPTION

This is an action role that lets your L<Catalyst::Action> match on standard
HTTP methods, such as GET, POST, etc.

Since most web browsers have limited support for rich HTTP Method vocabularies
we use L<Plack::Middleware::MethodOverride> which allows you to 'tunnel' your
request method over POST  This works in two ways.  You can set an extension
HTTP header C<X-HTTP-Method-Override> which will contain the value of the
desired request method, or you may set a search query parameter
C<x-tunneled-method>.  Remember, these only work over HTTP Request type
POST.  See L<Plack::Middleware::MethodOverride> for more.

=head1 REQUIRES

This role requires the following methods in the consuming class.

=head2 match

=head2 match_captures

Returns 1 if the action matches the existing request and zero if not.

=head1 METHODS

This role defines the following methods

=head2 match

=head2 match_captures

Around method modifier that return 1 if the request method matches one of the
allowed methods (see L</http_methods>) and zero otherwise.

=head2 allowed_http_methods

An array of strings that are the allowed http methods for matching this action
normalized as noted above (using X-Method* overrides).

=head2 list_extra_info

Adds a key => [@values] "HTTP_METHODS" whose value is an ArrayRef of sorted
allowed methods to the ->list_extra_info HashRef.  This is used primarily for
debugging output.

=head2 _has_expected_http_method ($expected)

Private method which returns 1 if C<$expected> matches one of the allowed
in L</http_methods> and zero otherwise.

=head1 AUTHORS

Catalyst Contributors, see Catalyst.pm

=head1 COPYRIGHT

This library is free software. You can redistribute it and/or modify it under
the same terms as Perl itself.

=cut