param= # required parameter
@param~ # optional multiple parameter
@param= # required multiple parameter
- * # include all other parameters
- @* # include all other parameters as multiple
+ :param~ # optional parameter in hashref
+ :param= # required parameter in hashref
+ :@param~ # optional multiple in hashref
+ :@param= # required multiple in hashref
+ * # include all other parameters in hashref
+ @* # include all other parameters as multiple in hashref
-separated by the & character.
+separated by the & character. The arguments added to the request are
+one per non-:/* parameter (scalar for normal, arrayref for multiple),
+plus if any :/* specs exist a hashref containing those values.
So, to match a page parameter with an optional order_by parameter one
would write:
sub (?page=&order_by~) {
-
-Parameters selected are turned into a hashref; in the case of singular
-parameters then if multiple values are found the last one is used. In the
-case of multiple parameters an arrayref of all values (or an empty arrayref
-for a missing optional) is used. The resulting hashref is provided as a
-match argument. So we might write something like:
-
- sub (?page=&order_by~) {
- my ($self, $p) = @_;
- return unless $p->{page} =~ /^\d+$/;
- $p->{order_by} ||= 'id';
+ my ($self, $page, $order_by) = @_;
+ return unless $page =~ /^\d+$/;
+ $page ||= 'id';
response_filter {
$_[1]->search_rs({}, $p);
}
to implement paging and ordering against a L<DBIx::Class::ResultSet> object.
+To get all parameters as a hashref of arrayrefs, write:
+
+ sub(?@*) {
+ my ($self, $params) = @_;
+ ...
+
=head3 Combining matches
Matches may be combined with the + character - e.g.
my $unpacker = Web::Simple::ParamParser->can("get_unpacked_${type}_from");
for ($_[1]) {
- my (@required, @single, %multi, $star, $multistar);
+ my (@required, @single, %multi, $star, $multistar, %positional, $have_kw);
+ my $pos_idx = 0;
PARAM: { do {
- # per param flag
+ # ?:foo or ?@:foo
- my $multi = 0;
+ my $is_kw = /\G\:/gc;
# ?@foo or ?@*
- /\G\@/gc and $multi = 1;
+ my $multi = /\G\@/gc;
# @* or *
if (/\G\*/gc) {
+ $self->_blam("* is always named; no need to supply :") if $is_kw;
+
$multi ? ($multistar = 1) : ($star = 1);
+ $have_kw = 1;
+
if ($star && $multistar) {
$self->_blam("Can't use * and \@* in the same parameter match");
}
# record the key in the right category depending on the multi (@) flag
$multi ? ($multi{$name} = 1) : (push @single, $name);
+
+ # record positional or keyword
+
+ $is_kw ? ($have_kw = 1) : ($positional{$name} = $pos_idx++);
}
} while (/\G\&/gc) }
foreach my $name (@required) {
return unless exists $raw->{$name};
}
- my %p;
+ my (%p, %done);
+ my @p = (undef) x $pos_idx;
foreach my $name (
@single,
($star
: ()
)
) {
- $p{$name} = $raw->{$name}->[-1] if exists $raw->{$name};
+ if (exists $raw->{$name}) {
+ if (exists $positional{$name}) {
+ $p[$positional{$name}] = $raw->{$name}->[-1];
+ } else {
+ $p{$name} = $raw->{$name}->[-1];
+ }
+ }
+ $done{$name} = 1;
}
foreach my $name (
keys %multi,
($multistar
- ? (grep { !exists $p{$_} } keys %$raw)
+ ? (grep { !exists $done{$_} && !exists $multi{$_} } keys %$raw)
: ()
)
) {
- $p{$name} = $raw->{$name}||[];
+ if (exists $positional{$name}) {
+ $p[$positional{$name}] = $raw->{$name}||[];
+ } else {
+ $p{$name} = $raw->{$name}||[];
+ }
}
- return ({}, \%p);
+ $p[$pos_idx] = \%p if $have_kw;
+ return ({}, @p);
};
}
}
evil => [ '/' ],
);
-my $foo = $dp->parse_dispatch_specification('?foo=');
+foreach my $lose ('?foo=','?:foo=','?@foo=','?:@foo=') {
+ my $foo = $dp->parse_dispatch_specification($lose);
-is_deeply(
- [ $foo->({ QUERY_STRING => '' }) ],
- [],
- '?foo= fails with no query'
-);
+ is_deeply(
+ [ $foo->({ QUERY_STRING => '' }) ],
+ [],
+ "${lose} fails with no query"
+ );
+
+ is_deeply(
+ [ $foo->({ QUERY_STRING => 'bar=baz' }) ],
+ [],
+ "${lose} fails with query missing foo key"
+ );
+}
foreach my $win (
- [ '?foo=' => { foo => 'FOO' } ],
- [ '?spoo~' => { } ],
- [ '?@spoo~' => { spoo => [] } ],
- [ '?bar=' => { bar => 'BAR2' } ],
- [ '?@bar=' => { bar => [ qw(BAR1 BAR2) ] } ],
- [ '?foo=&@bar=' => { foo => 'FOO', bar => [ qw(BAR1 BAR2) ] } ],
- [ '?baz=&evil=' => { baz => 'one two', evil => '/' } ],
+ [ '?foo=' => 'FOO' ],
+ [ '?:foo=' => { foo => 'FOO' } ],
+ [ '?spoo~' => undef ],
+ [ '?:spoo~' => {} ],
+ [ '?@spoo~' => [] ],
+ [ '?:@spoo~' => { spoo => [] } ],
+ [ '?bar=' => 'BAR2' ],
+ [ '?:bar=' => { bar => 'BAR2' } ],
+ [ '?@bar=' => [ qw(BAR1 BAR2) ] ],
+ [ '?:@bar=' => { bar => [ qw(BAR1 BAR2) ] } ],
+ [ '?foo=&@bar=' => 'FOO', [ qw(BAR1 BAR2) ] ],
+ [ '?foo=&:@bar=' => 'FOO', { bar => [ qw(BAR1 BAR2) ] } ],
+ [ '?:foo=&:@bar=' => { foo => 'FOO', bar => [ qw(BAR1 BAR2) ] } ],
+ [ '?:baz=&:evil=' => { baz => 'one two', evil => '/' } ],
[ '?*' => \%all_single ],
[ '?@*' => \%all_multi ],
- [ '?foo=&@*' => { %all_multi, foo => 'FOO' } ],
- [ '?@bar=&*' => { %all_single, bar => [ qw(BAR1 BAR2) ] } ],
+ [ '?foo=&@*' => 'FOO', do { my %h = %all_multi; delete $h{foo}; \%h } ],
+ [ '?:foo=&@*' => { %all_multi, foo => 'FOO' } ],
+ [ '?:@bar=&*' => { %all_single, bar => [ qw(BAR1 BAR2) ] } ],
) {
- my ($spec, $res) = @$win;
+ my ($spec, @res) = @$win;
my $match = $dp->parse_dispatch_specification($spec);
#use Data::Dump::Streamer; warn Dump($match);
is_deeply(
[ $match->({ QUERY_STRING => $q }) ],
- [ {}, $res ],
+ [ {}, @res ],
"${spec} matches correctly"
);
}