Catalyst-Action-REST

 view release on metacpan or  search on metacpan

lib/Catalyst/Action/REST.pm  view on Meta::CPAN

sub dispatch {
    my $self = shift;
    my $c    = shift;

    my $rest_method = $self->name . "_" . uc( $c->request->method );

    return $self->_dispatch_rest_method( $c, $rest_method );
}

sub _dispatch_rest_method {
    my $self        = shift;
    my $c           = shift;
    my $rest_method = shift;
    my $req         = $c->request;

    my $controller = $c->component( $self->class );

    my ($code, $name);

    # Execute normal 'foo' action.
    $c->execute( $self->class, $self, @{ $req->args } );

    # Common case, for foo_GET etc
    if ( $code = $controller->action_for($rest_method) ) {
        return $c->forward( $code,  $req->args ); # Forward to foo_GET if it's an action
    }
    elsif ($code = $controller->can($rest_method)) {
        $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below.
    }

    # Generic handling for foo_*
    if (!$code) {
        my $code_action = {
            OPTIONS => sub {
                $name = $rest_method;
                $code = sub { $self->_return_options($self->name, @_) };
            },
            HEAD => sub {
              $rest_method =~ s{_HEAD$}{_GET}i;
              $self->_dispatch_rest_method($c, $rest_method);
            },
            default => sub {
                # Otherwise, not implemented.
                $name = $self->name . "_not_implemented";
                $code = $controller->can($name) # User method
                    # Generic not implemented
                    || sub { $self->_return_not_implemented($self->name, @_) };
            },
        };
        my ( $http_method, $action_name ) = ( $rest_method, $self->name );
        $http_method =~ s{\Q$action_name\E\_}{};
        my $respond = ($code_action->{$http_method}
                       || $code_action->{'default'})->();
        return $respond unless $name;
    }

    # localise stuff so we can dispatch the action 'as normal, but get
    # different stats shown, and different code run.
    # Also get the full path for the action, and make it look like a forward
    local $self->{code} = $code;
    my @name = split m{/}, $self->reverse;
    $name[-1] = $name;
    local $self->{reverse} = "-> " . join('/', @name);

    $c->execute( $self->class, $self, @{ $req->args } );
}

sub get_allowed_methods {
    my ( $self, $controller, $c, $name ) = @_;
    my $class = ref($controller) ? ref($controller) : $controller;
    my $methods = {
      map { /^$name\_(.+)$/ ? ( $1 => 1 ) : () }
        @{ Class::Inspector->methods($class) }
    };
    $methods->{'HEAD'} = 1 if $methods->{'GET'};
    delete $methods->{'not_implemented'};
    return sort keys %$methods;
};

sub _return_options {
    my ( $self, $method_name, $controller, $c) = @_;
    my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
    $c->response->content_type('text/plain');
    $c->response->status(200);
    $c->response->header( 'Allow' => \@allowed );
    $c->response->body(q{});
}

sub _return_not_implemented {
    my ( $self, $method_name, $controller, $c ) = @_;

    my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
    $c->response->content_type('text/plain');
    $c->response->status(405);
    $c->response->header( 'Allow' => \@allowed );
    $c->response->body( "Method "
          . $c->request->method
          . " not implemented for "
          . $c->uri_for( $method_name ) );
}

__PACKAGE__->meta->make_immutable;

1;

=back

=head1 SEE ALSO

You likely want to look at L<Catalyst::Controller::REST>, which implements a
sensible set of defaults for a controller doing REST.

This class automatically adds the L<Catalyst::TraitFor::Request::REST> role to
your request class.  If you're writing a web application which provides RESTful
responses and still needs to accommodate web browsers, you may prefer to use
L<Catalyst::TraitFor::Request::REST::ForBrowsers> instead.

L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>

=head1 TROUBLESHOOTING



( run in 0.430 second using v1.01-cache-2.11-cpan-71847e10f99 )