QUERY in Dancer2

3 min read
← Back to Blogs
QUERY in Dancer2

DISCLAIMER: Image is generated using ChatGPT.



Few months ago, I posted this in my LinkedIn profile.

You can find the technical details in the official page: https://www.rfc-editor.org/info/rfc10008

Following this,D Ruth Holloway, a very good friend of mine, created an issue #1794 in the official GitHub repository for Dancer2.

Ever since, I wanted to see the support for QUERY method in Dancer2.

Five years ago, I created a GitHub repository that demonstrate the power of Dancer2. In the repository, I showed all supported HTTP methods with simple to use example.

Looking for something to distract my mind, I decided to jump in and look for possibilities. Idea was to see, if it is easy to add the support without making any critical changes.

Surprisingly, it didn’t trouble me a lot.

This is the status after my experiments:

Make sure, test suite is not complaining after my experiment.

By now, you must be wondering what exactly has changed.

Let me share the diff one by one.

#1:    lib/Dancer2/Core/DSL.pm

#2:    lib/Dancer2/Core/Request.pm

#3:    lib/Dancer2/Core/Types.pm

#4:    lib/Dancer2/Manual/Keywords.pod

I am sure, looking at the above changes, you would say, “Easy peasy lemon squeezy”.

Now, I just need unit test to cover the change introduced in the codebase.

My proposed unit test: t/route_query.t

use strict;
use warnings;
use Test::More;
use Plack::Test;
use HTTP::Request;
# Package 1: Standard app (no serializer) for text & form endpoints
{
    package TestApp::Standard;
    use Dancer2;

    query '/basic' => sub {
        return 'query ok';
    };

    query '/user/:id' => sub {
        return "User ID: " . route_parameters->get('id');
    };

    query '/search' => sub {
        my $q = body_parameters->get('q') // 'none';
        return "Search term: $q";
    };

    query '/query-only' => sub {
        return 'only query allowed';
    };
}
# Package 2: JSON-enabled app specifically for testing serializer behavior
{
    package TestApp::JSON;
    use Dancer2;

    set serializer => 'JSON';

    query '/json-search' => sub {
        my $data = request->data;
        unless ( ref $data eq 'HASH' ) {
            return status 400 => { error => 'Invalid JSON' };
        }
        return { filter => $data->{filter} // 'none' };
    };
}
my $test_std  = Plack::Test->create( TestApp::Standard->to_app );
my $test_json = Plack::Test->create( TestApp::JSON->to_app );
subtest 'Basic QUERY route dispatch' => sub {
    my $req = HTTP::Request->new( QUERY => '/basic' );
    my $res = $test_std->request($req);

    is( $res->code, 200, 'Returns 200 OK' );
    is( $res->content, 'query ok', 'Response body matches' );
};
subtest 'QUERY route with path parameters' => sub {
    my $req = HTTP::Request->new( QUERY => '/user/42' );
    my $res = $test_std->request($req);

    is( $res->code, 200, 'Returns 200 OK' );
    is( $res->content, 'User ID: 42', 'Route parameter correctly extracted' );
};
subtest 'QUERY request with form-encoded body payload' => sub {
    my $req = HTTP::Request->new(
        QUERY => '/search',
        [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
        'q=dancer2+query'
    );
    my $res = $test_std->request($req);

    is( $res->code, 200, 'Returns 200 OK' );
    is( $res->content, 'Search term: dancer2 query', 'Form parameters parsed correctly' );
};
subtest 'QUERY request with JSON payload' => sub {
    my $json_payload = '{"filter":"active_users"}';
    my $req = HTTP::Request->new(
        QUERY => '/json-search',
        [ 'Content-Type' => 'application/json' ],
        $json_payload
    );
    my $res = $test_json->request($req);

    is( $res->code, 200, 'Returns 200 OK' );
    like( $res->content, qr/"filter"\s*:\s*"active_users"/, 'JSON body deserialized into request->data' );
};
subtest 'HTTP method matching isolation' => sub {
    my $req_get = HTTP::Request->new( GET => '/query-only' );
    my $res_get = $test_std->request($req_get);

    is( $res_get->code, 404, 'GET request to query-only route returns 404' );

    my $req_query = HTTP::Request->new( QUERY => '/query-only' );
    my $res_query = $test_std->request($req_query);

    is( $res_query->code, 200, 'QUERY request succeeds on query route' );
};
done_testing;

Now the real test of the above experiment, if I can demo the QUERY method in the repository I mentioned above.

#1:    lib/Schema.pm

#2:    lib/Bookstore.pm

Start the application now:

manwar@manwar:~/github/Dancer2-REST-API$ plackup bin/app.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/

Test the QUERY call:

manwar@manwar:~/github/Dancer2-REST-API$ curl -X QUERY http://localhost:5000/api/books/search \
           -H "Content-Type: application/json" \
           -d '{"pub_date": "2010-01-01", "country": "England"}'
[
   {
      "author" : 1,
      "isbn" : "DMWP-DC",
      "pub_date" : "2010-01-01",
      "title" : "Data Munging with Perl",
      "id" : 1
   }
]


Happy Hacking !!!