Add query parameter validation

Change-Id: Ib0fa76faf0c70bb8373bf723f238c9e88b5b890a
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index 2359274..e8f3dff 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -2,6 +2,7 @@
 use Mojo::Base 'Mojolicious::Controller';
 use Mojo::Collection 'c';
 use Mojo::ByteStream 'b';
+use Mojo::Util qw/quote/;
 use POSIX 'ceil';
 
 has no_cache => 0;
@@ -32,21 +33,18 @@
   $v->optional('q', 'trim');
   $v->optional('ql')->in(qw/poliqarp cosmas2 annis cql fcsql/);
   $v->optional('collection', 'trim'); # Legacy
-  $v->optional('cq', 'trim');
-  # $v->optional('action'); # action 'inspect' is no longer valid
-  # $v->optional('snippet');
+  $v->optional('cq', 'trim');         # New
   $v->optional('cutoff')->in(qw/true false/);
   $v->optional('count')->num(1, undef);
   $v->optional('p', 'trim')->num(1, undef); # Start page
   $v->optional('o', 'trim')->num(1, undef); # Offset
   $v->optional('context');
+  # $v->optional('action'); # action 'inspect' is no longer valid
+  # $v->optional('snippet');
 
   # Get query
   my $query = $v->param('q');
 
-  # TODO:
-  #   Check for validation errors!
-
   # No query
   unless ($query) {
     return $c->render($c->loc('Template_intro', 'intro'));
@@ -64,9 +62,22 @@
   # Start page
   my $page = $v->param('p') // 1;
 
-  $c->stash(query => $query);
+  $c->stash(q => $query);
   $c->stash(ql => $query{ql});
 
+  # Check validation
+  if ($v->has_error) {
+
+    # Create error notifications
+    foreach my $failed_field (@{$v->failed}) {
+      $c->notify(error => 'Parameter ' . quote($failed_field) . ' invalid');
+    };
+    return $c->render(
+      status => 400,
+      template => 'failure'
+    );
+  };
+
   my $items_per_page = $c->items_per_page;
 
   # Set count
@@ -205,8 +216,6 @@
 
       # Render result
       return $c->render(
-        q => $c->stash('query'),
-        ql => $c->stash('ql'),
         start_page => $page,
         start_index => $json->{meta}->{startIndex},
         results => _map_matches($json->{matches}),
@@ -227,8 +236,6 @@
 
       # $c->_notify_on_errors(shift);
       return $c->render(
-        q => $c->stash('query'),
-        ql => $c->stash('ql'),
         template => 'failure'
       );
     }
@@ -360,6 +367,19 @@
   $v->optional('layer');
   $v->optional('spans')->in(qw/true false/);
 
+  # Check validation
+  if ($v->has_error) {
+
+    # Create error notifications
+    foreach my $failed_field (@{$v->failed}) {
+      $c->notify(error => 'Parameter ' . quote($failed_field) . ' invalid');
+    };
+    return $c->render(
+      status => 400,
+      json => $c->notifications('json')
+    );
+  };
+
   # Old API foundry/layer usage
   my $foundry = '*';
   my %query = (foundry => '*');
diff --git a/t/match_info.t b/t/match_info.t
index 7403d52..5d23887 100644
--- a/t/match_info.t
+++ b/t/match_info.t
@@ -98,6 +98,11 @@
   ->header_is('X-Kalamar-Cache', 'true')
   ;
 
+# Check for validation error
+$t->get_ok('/corpus/WPD15/232/39681/p2133-2134?spans=no')
+  ->status_is(400)
+  ->json_is('/notifications/0/1', 'Parameter "spans" invalid')
+  ;
 
 done_testing;
 __END__
diff --git a/t/query.t b/t/query.t
index 933276f..59d8fc9 100644
--- a/t/query.t
+++ b/t/query.t
@@ -157,7 +157,22 @@
   ->content_like(qr!\"cutOff":true!)
   ;
 
-
+# Query with failing parameters
+$t->get_ok('/?q=fantastisch&ql=Fabelsprache')
+  ->status_is(400)
+  ->text_is('noscript div.notify-error', 'Parameter "ql" invalid')
+  ->element_count_is('noscript div.notify-error', 1)
+  ;
+$t->get_ok('/?q=fantastisch&cutoff=no')
+  ->status_is(400)
+  ->text_is('noscript div.notify-error', 'Parameter "cutoff" invalid')
+  ->element_count_is('noscript div.notify-error', 1)
+  ;
+$t->get_ok('/?q=fantastisch&p=hui&o=hui&count=-8')
+  ->status_is(400)
+  ->text_like('noscript div.notify-error', qr!Parameter ".+?" invalid!)
+  ->element_count_is('noscript div.notify-error', 3)
+  ;
 
 
 done_testing;