/usr/local/CPAN/HTTPx-Dispatcher/HTTPx/Dispatcher/Rule.pm


package HTTPx::Dispatcher::Rule;
use strict;
use warnings;
use base qw/Class::Accessor::Fast/;
use Scalar::Util qw/blessed/;
use Carp;

__PACKAGE__->mk_accessors(qw/re pattern controller action capture requirements conditions name/);

sub new {
    my ($class, $pattern, $args) = @_;
    $args ||= {};
    $args->{conditions} ||= {};

    my $self = bless { %$args }, $class;

    $self->compile($pattern);
    $self;
}

# compile url pattern to regex.
#   articles/:year/:month => qr{articles/(.+)/(.+)}
sub compile {
    my ($self, $pattern) = @_;

    # from URI Templates to url pattern
    #   articles/{year}/{month} => articles/:year/:month
    $pattern =~ s/{(\w+)}/:$1/g;
    # allow slash (eg. '/articles')
    $pattern =~ s!^/+!!;

    $self->pattern( $pattern );

    # emulate named capture
    my @capture;
    $pattern =~ s{:([a-z0-9_]+)}{
                push @capture, $1;
                '(.+)'
        }ge;
    $self->re( qr{^$pattern$} );
    $self->capture( \@capture );
}

sub match {
    my ($self, $req) = @_;

    my $uri = ref($req->uri) ? $req->uri->path : $req->uri;
    $uri =~ s!^/+!!;

    return unless $self->_condition_check( $req );

    if ($uri =~ $self->{re}) {
        my @last_match_start = @-; # backup perlre vars
        my @last_match_end   = @+;

        my $response = {};
        for my $key (qw/action controller/) {
            $response->{$key} = $self->{$key} if $self->{$key};
        }
        my $requirements = $self->requirements;
        my $cnt      = 1;
        for my $key (@{ $self->capture }) {
            $response->{$key} = substr($uri, $last_match_start[$cnt], $last_match_end[$cnt] - $last_match_start[$cnt]);

            # validate
            # XXX this function needs test.
            if ( exists( $requirements->{$key} )
                && !( $response->{$key} =~ $requirements->{$key} ) )
            {
                die "invalid args: $response->{$key} ( $key ) does not matched $requirements->{$key}";
            }

            $cnt++;
        }
        return $self->_filter_response( $response );
    } else {
        return;
    }
}

sub _filter_response {
    my ($self, $input) = @_;
    my $output = {};
    for my $key (qw/controller action/) {
        $output->{$key} = delete $input->{$key} or croak "missing $key";
    }
    $output->{args} = $input;
    return $output;
}

sub _condition_check {
    my ($self, $req) = @_;

    $self->_condition_check_method($req) && $self->_condition_check_function($req);
}

sub _condition_check_method {
    my ($self, $req) = @_;

    my $method = $self->conditions->{method};
    return 1 unless $method;

    $method = [ $method ] unless ref $method;

    if (grep { uc $req->method eq uc $_} @$method) {
        return 1;
    } else {
        return 0;
    }
}

sub _condition_check_function {
    my ($self, $req) = @_;

    my $function = $self->conditions->{function};
    return 1 unless $function;

    local $_ = $req;
    if ( $function->( $req ) ) {
        return 1;
    } else {
        return 0;
    }
}

sub uri_for {
    my ($self, $args) = @_;

    my $uri = $self->pattern;
    my %args = %$args;
    while (my ($key, $val) = each %args) {
         $uri = $self->_uri_for_match($uri, $key, $val);
         return unless defined $uri;
    }
    return "/$uri";
}

sub _uri_for_match {
    my ($self, $uri, $key, $val) = @_;

    if ($self->{$key} && $self->{$key} eq $val) { return $uri }

    if ($uri =~ s{:$key}{$val}) {
        return $uri;
    } else {
        return;
    }
}

1;