Acme-CPANModulesBundle-Import-PerlDancerAdvent-2018
view release on metacpan or search on metacpan
devdata/http_advent.perldancer.org_2018_22 view on Meta::CPAN
# optional
my $action = query_parameters->{'action'};
unless ( defined $action && length $action ) {
send_error 'Bad Action' => 400;
}
# use $id and maybe $action
};</pre>
<p>The more parameters we have, the more annoying it is to write these
tests.</p>
<p>But what's more revealing here is that this validation code is not
actually part of our web code. It's input validation <i>for</i> our web
code.</p>
<h2><a name="a_different_perspective"></a>A different perspective</h2>
<p>What if - instead of having to write all of this code - we maintained
the Dancer2 spirit and allowed you to <b>declare</b> what your validation
rules are, and have Dancer2 do the work for you?</p>
<p>Lucky you! We have done just that with <a href="https://metacpan.org/module/Dancer2::Plugin::ParamTypes">Dancer2::Plugin::ParamTypes</a>!</p>
<h3><a name="register_your_own_types"></a>Register your own types</h3>
<p>There are normally two options that type check syntax give you:</p>
<blockquote>
<p>We didn't want to create our own type system or type validations. There
are already plenty of good ones.</p>
<blockquote>
<p>And we didn't want to tie the plugin with a specific type system
because it might not suit you.</p>
</blockquote>
<p>Instead, we picked a third option: Allowing you to connect it with
whatever you want.</p>
<pre class="prettyprint">use Dancer2;
use Dancer2::Plugin::ParamTypes;
# register an 'Int'
register_type_check 'Int' => sub { $_[0] =~ /^[0-9]+$/ };</pre>
<p>You could also register existing type systems:</p>
<pre class="prettyprint">use MooX::Types::MooseLike::Base qw< Int >;
reigster_type_checks(
'Int' => sub { Int()->( $_[0] ) },
);</pre>
</blockquote>
<h3><a name="using_your_now_available_types"></a>Using your now-available types</h3>
<p>Once you register all the types you want, you could use them in your
code with a simple stanza.</p>
<pre class="prettyprint">use Dancer2::Plugin::ParamTypes;
register_type_check(...);
# Indented to make it more readable
get '/:id' => with_types [
[ 'route', 'id', 'Int' ],
'optional => [ 'query', 'action', 'Str' ],
] => sub {
my $id = route_parameters->{'id'};
# do something with $id because we know it exists and validated
if ( my $action = query_parameters->{'action'} ) {
# if it exists, we know it's validated
}
};</pre>
<h2><a name="reusability__reusability__reusability"></a>Reusability, reusability, reusability</h2>
<p><a href="https://metacpan.org/module/Dancer2::Plugin::ParamTypes">Dancer2::Plugin::ParamTypes</a> was built with a company code-abase in
mind, where you would like to have common types available. You could
easily accomplish that by subclassing it.</p>
<pre class="prettyprint">package Dancer2::Plugin::MyCommonTypes;
use Dancer2::Plugin;
# Subclass the main plugin
extends('Dancer2::Plugin::ParamTypes');
# Provide your own 'with_types' keyword
plugin_keywords('with_types');
# Make our keyword call the parent plugin
sub with_types {
my $self = shift;
return $self->SUPER::with_types(@_);
}
# Register all of our own type checks at build time
sub BUILD {
my $self = shift;
$self->register_type_check 'Str' => sub {...};
$self->register_type_check 'ShortStr' => sub {...};
$self->register_type_check 'PositiveInt' => sub {...};
$self->register_type_check 'SHA1' => sub {...};
# Maybe more?
...
}</pre>
<p>Now we have our own plugin that also provides <code>with_types</code> which uses
the original plugin with a set of registered type checks.</p>
<pre class="prettyprint">package MyApp;
use Dancer2;
use Dancer2::Plugin::MyCommonTypes;
post '/:entity/update/:id' => with_types [
[ 'route', 'entity', 'Str' ],
[ 'route', 'id', 'PositiveInt' ],
[ 'body', 'message', 'Str' ],
'optional' => [ 'body', 'sid', 'SHA1' ],
] => sub {
my ( $entity, $id ) = @{ route_parameters() }{qw< id entity >};
my $message = body_parameters->{'message'};
my $sid = body_parameters->{'sid'} || '';
# everything is validated and required parameters are checked
...
};</pre>
<h2><a name="could_i_do_more_with_it"></a>Could I do more with it?</h2>
<p>Absolutely!</p>
<h3><a name="handle_multiple_sources"></a>Handle multiple sources</h3>
<p>Normally, you would dictate to a user how they should send their
paramters (in the query, in the body, or as part of the path - in the
route), but sometimes you cannot control this. Maybe you're maintaining
an old interface or supporting outdated APIs.</p>
<p><a href="https://metacpan.org/module/Dancer2::Plugin::ParamTypes">Dancer2::Plugin::ParamTypes</a> is flexible enough to support multiple
sources for an argument:</p>
<pre class="prettyprint">any [ 'get', 'post' ] => '/:entity/:id' => with_types [
[ 'route', 'entity', 'Str' ],
[ 'route', 'id', 'PositiveInt' ],
[ [ 'query', 'body' ], 'format', 'Str' ],
'optional' => [ 'body', 'sid', 'SHA1' ],
] => sub {...};</pre>
<p>In this form, the parameter <code>format</code> can be provided either in the
query string or in the body, because your route might be either a
<b>GET</b> or a <b>POST</b>.</p>
<h3><a name="register_type_actions"></a>Register type actions</h3>
<p>Type checking itself is the main role of this plugin, but you can also
control how it behaves.</p>
<p>The default action to perform when a type check fails is to error out,
but you can decide to act differently by registering a different
action.</p>
<pre class="prettyprint">register_type_action 'SoftError' => sub {
my ( $self, $details ) = @_;
warning "Parameter $details->{'name'} from $details->{'source'} "
. "failed checking for type $details->{'type'}, called "
. "action $details->{'action'}";
return;
};
get '/:id' => with_types [
[ 'query', 'age', 'Int', 'SoftError',
] => sub {...};</pre>
<p>On a bad <code>age</code> parameter, it will print out the following warning:</p>
<pre class="prettyprint">Parameter age from query failed checking for type Int, called action SoftError</pre>
<p>This means you can also register a set of actions that you want to call
in different cases.</p>
<h2><a name="conclusion"></a>Conclusion</h2>
<p><a href="https://metacpan.org/module/Dancer2::Plugin::ParamTypes">Dancer2::Plugin::ParamTypes</a> allows you to define your own types and
your own actions, to create your own plugin that helps you maintain
reusability and consistency across your application with fewer code
duplication and less effort.</p>
<h2><a name="author"></a>Author</h2>
<p>This article has been written by Sawyer X for the Perl
( run in 0.538 second using v1.01-cache-2.11-cpan-39bf76dae61 )