package OX::RouteBuilder::REST;

# ABSTRACT: OX::RouteBuilder which routes to an action method in a controller class based on HTTP verbs
our $VERSION = '0.006'; # VERSION

use Moose;
use namespace::autoclean;
use Try::Tiny;

with 'OX::RouteBuilder';

sub import {
    my $caller = caller;
    my $meta   = Moose::Util::find_meta($caller);
    $meta->add_route_builder('OX::RouteBuilder::REST');
}

sub compile_routes {
    my $self = shift;
    my ($app) = @_;

    my $spec   = $self->route_spec;
    my $params = $self->params;
    my ( $defaults, $validations ) =
        $self->extract_defaults_and_validations($params);
    $defaults = { %$spec, %$defaults };

    my $target = sub {
        my ($req) = @_;

        my $match = $req->mapping;
        my $c     = $match->{controller};
        my $a     = $match->{action};

        my $err;
        my $s = try { $app->fetch($c) } catch { ($err) = split "\n"; undef };
        return [
            500, [], [ "Cannot resolve $c in " . blessed($app) . ": $err" ]
            ]
            unless $s;

        my $component = $s->get;
        my $method    = uc( $req->method );
        my $action    = $a . '_' . $method;

        if ( $component->can($action) ) {
            return $component->$action(@_);
        }
        elsif ($method eq 'HEAD' && $component->can($a . '_GET')) {
            $action = $a . '_GET';
            my $res = $component->$action(@_);
            if (ref($res) eq 'ARRAY') {
                $res->[2]=[''];
            }
            elsif (blessed($res) && $res->can('content')) {
                $res->content('');
            }
            # else don't mess with content..
            return $res;
        }
        else {
            return [ 501, [],
                ["Controller '$c' (".blessed($component).") has no method '$action'"] ];
        }
    };

    return {
        path        => $self->path,
        defaults    => $defaults,
        target      => $target,
        validations => $validations,
    };
}

sub parse_action_spec {
    my $class = shift;
    my ($action_spec) = @_;

    return if ref($action_spec);
    return unless $action_spec =~ /^REST\.(\w+)\.(\w+)$/;

    return {
        controller => $1,
        action     => $2,
        name       => $action_spec,
    };
}

__PACKAGE__->meta->make_immutable;

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

OX::RouteBuilder::REST - OX::RouteBuilder which routes to an action method in a controller class based on HTTP verbs

=head1 VERSION

version 0.006

=head1 SYNOPSIS

  package MyApp;
  use OX;
  use OX::RouteBuilder::REST;

  has thing => (
      is  => 'ro',
      isa => 'MyApp::Controller::Thing',
  );

  router as {
      route '/thing'     => 'REST.thing.root';
      route '/thing/:id' => 'REST.thing.item';
  };


  package MyApp::Controller::Thing;
  use Moose;

  sub root_GET {
      my ($self, $req) = @_;
      ... # return a list if things
  }

  sub root_PUT {
      my ($self, $req) = @_;
      ... # create a new thing
  }

  sub item_GET {
      my ($self, $req, $id) = @_;
      ... # view a thing
  }

  sub item_POST {
      my ($self, $req, $id) = @_;
      ... # update a thing
  }

=head1 DESCRIPTION

This is an L<OX::RouteBuilder> which routes to an action method in a
controller class based on HTTP verbs. It's a bit of a mixture between
L<OX::RouteBuilder::ControllerAction> and
L<OX::RouteBuilder::HTTPMethod>.

To enable this RouteBuilder, you need to C<use OX::RouteBuilder::REST>
in your main application class.

The C<action_spec> should be a string in the form
C<"REST.$controller.$action">, where C<$controller> is the name of a
service which provides a controller instance. For each HTTP verb you
want to support you will need to set up an action with the name
C<$action_$verb> (e.g. C<$action_GET>, C<$action_PUT>, etc). If no
matching action-verb-method is found, a 501 error will be returned.

C<controller> and C<action> will also be automatically added as
defaults for the route, as well as C<name> (which will be set to
C<"REST.$controller.$action">).

A C<HEAD> request will be redirect to C<GET> (with a potential
response body removed), unless you implement a method named
C<$action_HEAD>.

To generate a link to an action, use C<uri_for> with either the name
(eg C<"REST.$controller.$action">), or by passing a HashRef C<<{
    controller => $controller, action => $action }>>. See F<t/test.t>
    for some examples.

=for Pod::Coverage import
  compile_routes
  parse_action_spec

=head1 AUTHORS

=over 4

=item *

Thomas Klausner <domm@plix.at>

=item *

Validad GmbH http://validad.com

=back

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 - 2023 by Thomas Klausner.

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
