Add fields option to search

Change-Id: I2c6a5b34d2b75fa12013cd4bd0e253ed9c7ce4e4
diff --git a/Changes b/Changes
index 7df990f..00165a7 100644
--- a/Changes
+++ b/Changes
@@ -12,6 +12,8 @@
         - Added category to Piwik calls. (diewald)
         - Add SpaCy with STTS to annotation assistant. (diewald)
         - Support field objects in search responses. (diewald)
+        - Add fields to search API (necessary
+          for future Kustvakt releases). (diewald)
 
 0.54 2024-06-10
         - Remove deprecated 'matchInfo' API path. (diewald, margaretha)
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index 5edd2ae..47d2b1e 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -6,6 +6,8 @@
 use Mojo::JSON;
 use POSIX 'ceil';
 
+our @search_fields = qw!ID UID textSigle layerInfo title subTitle pubDate author availability snippet!;
+
 # TODO:
 #   Support server timing API
 #
@@ -20,6 +22,9 @@
 #
 # TODO:
 #   set caches with timing like '120min'
+#
+# TODO:
+#  Use nested fields
 
 # Query endpoint
 sub query {
@@ -121,6 +126,10 @@
   # From Mojolicious::Plugin::Search::Index
   $query{offset} = $v->param('o') || ((($page // 1) - 1) * ($items_per_page || 1));
 
+
+  # Add requested fields
+  $query{fields} = join ',', @search_fields;
+
   # Create remote request URL
   my $url = Mojo::URL->new($c->korap->api);
   $url->path('search');
diff --git a/t/fixtures.t b/t/fixtures.t
index 76cefa6..c3fbfdc 100644
--- a/t/fixtures.t
+++ b/t/fixtures.t
@@ -45,6 +45,16 @@
   ;
 is(defined $err ? $err->text : '', '');
 
+$err = $t->get_ok('/v1.0/search?q=baum&ql=poliqarp&offset=0&count=25&fields=textSigle')
+  ->status_is(200)
+  ->json_is('/meta/count', 25)
+  ->json_is('/meta/serialQuery', "tokens:s:Baum")
+  ->json_hasnt('/matches/0/docSigle')
+  ->json_is('/matches/0/textSigle', "GOE/AGI/00000")
+  ->tx->res->dom->at('#error')
+  ;
+is(defined $err ? $err->text : '', '');
+
 
 $t->get_ok('/v1.0/corpus/WPD15/232/39681/p2133-2134?spans=false&foundry=*')
   ->status_is(200)
diff --git a/t/fixtures/response_query_baum_o0_c25_ftextsigle.json b/t/fixtures/response_query_baum_o0_c25_ftextsigle.json
new file mode 100644
index 0000000..802a637
--- /dev/null
+++ b/t/fixtures/response_query_baum_o0_c25_ftextsigle.json
@@ -0,0 +1,58 @@
+{
+  "status" : 200,
+  "json" : {
+    "@context" : "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+    "meta" :  {
+      "count" : 25,
+      "startIndex" : 0,
+      "authorized" : null,
+      "timeout" : 120000,
+      "context" : {
+        "left" : ["token",40],
+        "right" : ["token",40]
+      },
+      "fields" : ["textSigle"],
+      "version" : "0.55.7",
+      "benchmark" : "0.120577834 s",
+      "totalResults" : 51,
+      "serialQuery" : "tokens:s:Baum",
+      "itemsPerPage" : 25
+    },
+    "query" : {
+      "@type" : "koral:token",
+      "wrap" : {
+        "@type" : "koral:term",
+        "layer" : "orth",
+        "key" : "Baum",
+        "match" : "match:eq",
+        "foundry" : "opennlp",
+        "rewrites" : [
+          {
+            "@type" : "koral:rewrite",
+            "src" : "Kustvakt",
+            "operation" : "operation:injection",
+            "scope" : "foundry"
+          }
+        ]
+      }
+    },
+    "matches" : [
+      {
+        "field" : "tokens",
+        "textSigle" : "GOE/AGI/00000",
+        "startMore" : true,
+        "endMore" : true,
+        "snippet" : "<span class=\"context-left\"><span class=\"more\"></span>sie etwas bedeuten zu wollen und machte mit der Oberlippe eine fatale Miene. ich sprach sehr viel mit ihr durch, sie war überall zu Hause und merkte gut auf die Gegenstände. so fragte sie mich einmal, was das für ein </span><span class=\"match\"><mark>Baum</mark></span><span class=\"context-right\"> sei. es war ein schöner großer Ahorn, der erste, der mir auf der ganzen Reise zu Gesichte kam. den hatte sie doch gleich bemerkt und freute sich, da mehrere nach und nach erschienen, daß sie auch diesen Baum unterscheiden könne<span class=\"more\"></span></span>",
+        "matchID" : "match-GOE/AGI/00000-p2030-2031"
+      },
+      {
+        "field" : "tokens",
+        "textSigle" : "GOE/AGI/00001",
+        "startMore" : true,
+        "endMore" : true,
+        "snippet" : "<span class=\"context-left\"><span class=\"more\"></span>für ein Baum sei. es war ein schöner großer Ahorn, der erste, der mir auf der ganzen Reise zu Gesichte kam. den hatte sie doch gleich bemerkt und freute sich, da mehrere nach und nach erschienen, daß sie auch diesen </span><span class=\"match\"><mark>Baum</mark></span><span class=\"context-right\"> unterscheiden könne. sie gehe, sagte sie, nach Bozen auf die Messe, wo ich doch wahrscheinlich auch hinzöge. wenn sie mich dort anträfe, müsse ich ihr einen Jahrmarkt kaufen, welches ich ihr denn auch versprach. dort wollte sie auch ihre neue<span class=\"more\"></span></span>",
+        "matchID" : "match-GOE/AGI/00000-p2068-2069"
+      }
+    ]
+  }
+}
diff --git a/t/server/mock.pl b/t/server/mock.pl
index 88ec023..e9e91d4 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -7,6 +7,9 @@
 use warnings;
 use Mojo::File qw/path/;
 use Mojo::Util qw/slugify/;
+use Kalamar::Controller::Search;
+
+our @default_search_fields = @Kalamar::Controller::Search::search_fields;
 
 # This is an API fake server with fixtures
 
@@ -139,6 +142,7 @@
   $v->optional('context');
   $v->optional('offset');
   $v->optional('pipes');
+  $v->optional('fields');
   $v->optional('cutoff')->in(qw/true false/);
 
   $c->app->log->debug('Receive request');
@@ -171,6 +175,10 @@
   push @slug_base, 'cq' if defined $v->param('cq');
   push @slug_base, 'p' . $v->param('pipes') if defined $v->param('pipes');
 
+  if (defined $v->param('fields') && ($v->param('fields') ne join(',', @default_search_fields))) {
+    push @slug_base, 'f' .join('-', split(',', $v->param('fields')));
+  };
+
   # Get response based on query parameter
   my $response = $c->load_response('query_' . slugify(join('_', @slug_base)));