Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: a038aa5b1c
Fetching contributors…

Cannot retrieve contributors at this time

293 lines (202 sloc) 8.259 kb
package Dancer::Plugin::Params::Normalization;
# ABSTRACT: A plugin for normalizing query parameters in Dancer
use Dancer ':syntax';
use Dancer::Plugin;
my $conf = plugin_setting;
# method that does nothing. It's optimized to nothing at compile time
my $void = sub(){};
# set the params_filter
my $params_filter = sub () { 1; };
if (defined $conf->{params_filter}) {
my $re = $conf->{params_filter};
$params_filter = sub {
return scalar($_[0] =~ /$re/) };
# method that loops on a hashref and apply a given method on its keys
my $apply_on_keys = sub {
my ($h, $func) = @_;
my $new_h = {};
while (my ($k, $v) = each (%$h)) {
my $new_k = $params_filter->($k) ? $func->($k) : $k;
exists $new_h->{$new_k} && ! ($conf->{no_conflict_warn} || 0)
and warn "paramater names conflict while doing normalization of parameters '$k' : it produces '$new_k', which alreay exists.";
$new_h->{$new_k} = $v;
return $new_h;
# default normalization method is passthrough (do nothing)
my $normalization_fonction = $void;
if (defined $conf->{method} && $conf->{method} ne 'passthrough') {
my $method;
if ($conf->{method} eq 'lowercase') {
$method = sub { my ($h) = @_; $apply_on_keys->($h, sub { lc($_[0]) } ) };
} elsif ($conf->{method} eq 'uppercase') {
$method = sub { my ($h) = @_; $apply_on_keys->($h, sub { uc($_[0]) } ) };
} elsif ($conf->{method} eq 'ucfirst') {
$method = sub { my ($h) = @_; $apply_on_keys->($h, sub { ucfirst($_[0]) } ) };
} else {
my $class = $conf->{method};
my $class_name = $class;
$class_name =~ s!::|'!/!g;
$class_name .= '.pm';
if ( ! $class->can('new') ) {
eval { require $class_name };
$@ and die "error while requiring custom normalization class '$class' : $@";
my $abstract_classname = __PACKAGE__ . '::Abstract';
$class->isa(__PACKAGE__ . '::Abstract')
or die "custom normalization class '$class' doesn't inherit from '$abstract_classname'";
my $instance = $class->new();
# using a custom normalization is incompatible with params filters
defined $conf->{params_filter}
and die "your configuration contains a 'params_filter' fields, and a custom 'method' normalization class name. The two fields are incompatible";
# todo : use *method = \&{$class->normalize} or somethin'
$method = sub { $instance->normalize($_[0]) };
my $params_types = $conf->{params_types};
# default value
defined $params_types
or $params_types = [ qw(query body) ];
ref $params_types eq 'ARRAY'
or die "configuration field 'params_types' should be an array";
my %params_types = map { $_ => 1 } @$params_types;
my $params_type_query = delete $params_types{query};
my $params_type_body = delete $params_types{body};
my $params_type_route = delete $params_types{route};
keys %params_types
and die "your configuration contains '" . join(', ', keys %params_types) .
"' as 'params_types' field(s), but only these are allowed : 'query', 'body', 'route'";
$normalization_fonction = sub {
my ($new_query_params,
$new_route_params) = map { scalar(params($_)) } qw(query body route);
$params_type_query and $new_query_params = $method->($new_query_params);
$params_type_body and $new_body_params = $method->($new_body_params);
$params_type_route and $new_route_params = $method->($new_route_params);
request->{params} = {};
if (defined $conf->{general_rule}) {
$conf->{general_rule} =~ /^always$|^ondemand$/
or die 'configuration field general_rule must be one of : always, ondemand';
if ($conf->{general_rule} eq 'ondemand') {
register normalize => sub{ $normalization_fonction->() };
} else {
hook before => $normalization_fonction;
} else {
hook before => $normalization_fonction;
In configuration file :
method: lowercase
In your Dancer App :
package MyWebService;
use Dancer;
use Dancer::Plugin::Params::Normalization;
get '/hello' => sub {
'Hello ' . params->{name};
# This will work, as NAME will be lowercased to name
curl http://mywebservice/test?NAME=John
This plugin helps you normalize the query parameters in Dancer.
The behaviour of this plugin is primarily setup in the configuration file, in
your main config.yml or environment config file.
# Example 1 : always lowercase all parameters
method: lowercase
# Example 2 : always uppercase all parameters
method: uppercase
# Example 3 : on-demand uppercase parameters that starts with 'a'
general_rule: ondemand
method: uppercase
params_filter: ^[aA]
Here is a list of configuration fields:
=head2 general_rule
This field specifies if the normalization should always happen, or on demand.
Value can be of:
=item always
Parameters will be normalized behind the scene, automatically.
=item ondemand
Parameters are not normalized by default. The code in the route definition
needs to call normalize_params to have the parameters normalized
B<Default value>: C<always>
=head2 method
This field specifies what kind of normalization to do.
Value can be of:
=item lowercase
parameters names are lowercased
=item uppercase
parameters names are uppercased
=item ucfirst
parameters names are ucfirst'ed
=item Custom::Class::Name
Used to execute a custom normalization method.
The given class should inherit
L<Dancer::Plugin::Params::Normalization::Abstract> and implement the method
C<normalize>. this method takes in argument a hashref of the parameters, and
returns a hashrefs of the normalized parameters. It can have an C<init> method
if it requires initialization.
Using a custom normalization is incompatible with C<params_filter> (see below).
=item passthrough
Doesn't do any normalization. Useful to disable the normalization without to
change the code
B<Default value>: C<passthrough>
=head2 params_types
Optional, used to specify on which parameters types the normalization should
apply. The value is an array, that can contain any combination of these
=item query
If present in the array, the parameters from the query string will be normalized
=item body
If present in the array, the parameters from the request's body will be normalized
=item route
If present in the array, the parameters from the route definition will be normalized
B<Default value>: [ 'query', 'body']
=head2 params_filter
Optional, used to filters which parameters the normalization should apply to.
The value is a regexp string that will be evaluated against the parameter names.
=head2 no_conflict_warn
Optional, if set to a true value, the plugin won't issue a warning when parameters name
conflict happens. See L<PARAMETERS NAMES CONFLICT>.
=head2 normalize
The general usage of this plugin is to enable normalization automatically in the configuration.
However, If the configuration field C<general_rule> is set to C<ondemand>, then
the normalization doesn't happen automatically. The C<normalize> keyword can
then be used to normalize the parameters on demand.
All you have to do is add
to your route code
if two normalized parameters names clash, a warning is issued. Example, if
while lowercasing parameters the route receives two params : C<param> and
C<Param>, they will be both normalized to C<param>, which leads to a conflict.
You can avoid the warning being issued by adding the configuration key
C<no_conflict_warn> to a true value.
=head1 SEE ALSO
Jump to Line
Something went wrong with that request. Please try again.