NAME

URI::Dispatch - determine which code to execute based upon path

SYNOPSIS

my $dispatch = URI::Dispatch->new();
$dispatch->add( '/', 'Homepage' );

# common matching patterns are available
$dispatch->add( '/user/#id', 'Profile' );

# optional parts of the path
$dispatch->add( '/article/#id[/#slug]', 'Article' );

# named captures
$dispatch->add( '/tag/#name:slug', 'Tag' );

# use a custom regexp
$dispatch->add( '/a-z/#letter:([a-z])', 'AZ::Page' );

# pass in a path and determine what matches
my( $handler, $options) 
    = $dispatch->handler( '/article/5/awesome-article' );
# handler='Article', options=['5','awesome-article']

# automatically calls Tag::get (as that matches the path)
my $response = $dispatch->dispatch( '/tag/perl' );

# construct paths
my $uri = $dispatch->url( 'article', [ '1', 'some-article' ] );
# uri='/article/1/some-article'

METHODS

add( path, handler )

Add path that can be handled by handler. The $path string will be matched literally, except for the special markers described below. They have been specially chosen because they are not legal URI path characters, so should never break your actual chosen URI scheme.

captures

To capture part of the path for later use, mark it with a hash (#) and the capture type. Builtin types are:

id

matches digits

hex

matches digits and the letters a, b, c, d, e, and f case insensitively

slug

matches lowercase letters, digits and hyphens

year

matches four digits

month

matches numbers 01 through 12

day

matches numbers 01 through 31

date

matches year, month and day separated by dashes

hour

matches numbers 00 through 23

minute

matches numbers 00 through 59

second

matches numbers 00 through 59

time

matches hour, minute and second separated by any of colons, periods or dashes

*

matches anything

(regexp)

matches a custom regular expression

named captures

Rather than relying on the order of the captures, they can be named. The name goes immediately after the hash (#), is formed of "word" characters (alphanumeric plus underscore) and is followed by a colon and then the capture type. Some examples:

  • #id:id

  • #title:slug

  • #letter:([a-z])

optional segments

To mark part of the path as optional, surround it with square brackets. Optional segments cannot be nested.

Limitations

Adding a new path with the same handler will overwrite the previous path.

Different handlers having the same path will result in unpredictable behaviour.

handler( path )

Determine which handler should be used for the given path.

Returns the handler string, and either an array of the captured elements, or a hash if the captures were named. For example, this code:

$dispatch->add( '/article/#key:id/#title:slug', 'article' );
my( $handler, $captures )
    = $dispatch->handler( '/article/5/awesome-article' );

would return $captures set to:

{
    key   => '5',
    title => 'awesome-article',
}

dispatch( path or request )

Call the handler that matches the given argument, which can either be a simple string that represents a path, or it can be a Plack::Request object.

The handler is interpreted as a class, and the HTTP method is the subroutine within the class to call.

path string

When dispatch() is called with a simple string, the method is assumed to be an HTTP GET. For example:

$dispatch->add( '/tag/#name:slug', 'Tags::SingleTag' );
my $response = $dispatch->dispatch( '/tag/perl' );

would set $response to the return value of

Tags::SingleTag::get( { name => 'perl' } );

Plack::Request

When dispatch() is called with a Plack::Request object, the path and method are determined automatically; and the object is passed as the second argument to the dispatcher. For example:

$dispatch->add( '/tag/#name:slug', 'Tags::SingleTag' );

# $env contains the environment of an HTTP DELETE 
# request on /tag/perl
my $request  = Plack::Request->new( $env );
my $response = $dispatch->dispatch( $request );

would set $response to the return value of

Tags::SingleTag::delete( { name => 'perl' }, $request );

url( handler, $arguments )

Build a path that handler would accept. If the path contains captures, you can pass them as an arrayref (or hashref if they are named captures).

The $arguments are tested to ensure they would match. If they would not, an Ouch exception is thrown. This can be caught in your code like so:

use Ouch qw( :traditional );

...

$dispatch->add( '/list/#letter:([a-z])', 'az-page' );
try { $url = $dispatch->url( 'az-page', 'too big' ); };
if ( catch 'wrong_input' ) {
    # handle errors
}

EXCEPTIONS

cannot_mix

Named and positional captures cannot be mixed. An attempt to do so will throw this exception.

unmatched_brackets

Thrown if the opening and closing square brackets representing optional segments of a path do not match up.

wrong_input

A provided argument when calling url() will not match the relevant capture type.

args_short

Not enough arguments are provided when calling url().

args_wrong

The wrong type of arguments (arrayref versus hashref) were provided when calling url().

no_param

An unknown builtin parameter type was requested.

AUTHOR

Mark Norman Francis, norm@cackhanded.net.

COPYRIGHT AND LICENSE

Copyright 2011 Mark Norman Francis.

This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.