blob: 59ea869121fd9904ec08c437829f633e682e1d15 [file] [log] [blame]
package Korap::API;
use Mojo::Base 'Mojolicious::Plugin';
use Scalar::Util 'blessed';
use strict;
use warnings;
# KorAP Search engine for Mojolicious::Plugin::Search
# Todo: Add fixtures
# Todo: Support search in corpus and virtualcollection
# Register the plugin
sub register {
my ($plugin, $mojo, $index_class, $param) = @_;
$param ||= {};
# Add attributes to the index class
$index_class->attr(api => $param->{api});
$index_class->attr([qw/cutoff
query_language
time_exceeded
api_request
_api_cache
api_response
benchmark
query_jsonld/]);
$index_class->attr(no_cache => 0);
};
# Search the index
sub search {
my $self = shift;
my $index = shift;
my $c = $index->controller;
# No query defined
return unless $index->query;
# If there is a callback, do async
my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
my %param = @_;
# Set cutoff from param
$index->cutoff(delete $param{cutoff});
# Set query language
$index->query_language(delete $param{query_language} // 'poliqarp');
# TODO: This should also be a query parameter
$index->no_cache(1) if $param{no_cache} or $c->param('no_cache');
my %query;
$query{q} = $index->query;
$query{ql} = $index->query_language;
$query{page} = $index->start_page;
$query{count} = $index->items_per_page;
$query{cutoff} = 'true' if $index->cutoff;
# Todo: support corpus and collection
# Create query url
my $url = Mojo::URL->new($index->api);
$url->path('search');
$url->query(\%query);
# Cache based on URL
$index->_api_cache('total-' . $url->to_string);
# Set context based on parameter
$url->query({ context => $c->param('context') // 'paragraph' });
# Check cache for total results
my $total_results;
if (!$index->no_cache &&
defined ($total_results = $c->chi->get($index->_api_cache))) {
# Set total results from cache
$index->total_results($total_results);
$c->app->log->debug('Get total result from cache');
# Set cutoff unless already set
$url->query({cutoff => 'true'}) unless defined $index->cutoff;
};
# Set api request for debugging
$index->api_request($url->to_string);
# Create new user agent and set timeout to 2 minutes
my $ua = Mojo::UserAgent->new;
$ua->inactivity_timeout(120);
# Denugging
$c->app->log->debug('Search for ' . $index->api_request);
# Search non-blocking
if ($cb) {
# Non-blocking request
$ua->get(
$url->to_string => sub {
my ($ua, $tx) = @_;
unless ($tx->success) {
if (my $e = $tx->error) {
warn 'Problem: ' . $e->{message};
return $cb->($index);
};
};
$self->_process_response($index, pop);
return $cb->($index);
});
Mojo::IOLoop->wait unless Mojo::IOLoop->is_running;
}
# Search blocking
else {
my $tx = $ua->get($url);
return $self->_process_response($index, $tx);
};
};
sub _process_response {
my ($self, $index, $tx) = @_;
my $c = $index->controller;
# An error has occurded
if (my $e = $tx->error) {
$c->notify(
error =>
($e->{code} ? $e->{code} . ': ' : '') .
$e->{message} . ' (remote)'
);
return;
};
# Response was fine
if (my $res = $tx->success) {
# Set api response for debugging
$index->api_response($res->body) if $c->korap_test_port;
# Json failure
my $json;
unless ($json = $res->json) {
$c->notify(error => 'JSON response is invalid');
return;
};
# Reformat benchmark counter
my $benchmark = $json->{benchmark};
if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
$benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
};
# Set benchmark
$index->benchmark($benchmark);
# Set time exceeded
if ($json->{timeExceeded} && $json->{timeExceeded} eq Mojo::JSON::true) {
$index->time_exceeded(1);
};
# Set result values
$index->items_per_page($json->{itemsPerPage});
$index->query_jsonld($json->{request}->{query});
$index->results(_map_matches($json->{matches}));
# Total results not set by stash
if ($index->total_results == -1) {
if ($json->{totalResults} && $json->{totalResults} > -1) {
$c->app->log->debug('Cache total result');
$c->chi->set($index->_api_cache => $json->{totalResults}, '120min');
$index->total_results($json->{totalResults});
};
};
# Add warnings (Legacy)
if ($json->{warning}) {
$json->{warning} =~ s/;\s+null$//;
$c->notify(warn => $json->{warning});
};
$self->_notify_on_error($c, 0, $json);
}
# Request failed
else {
$self->_notify_on_error($c, 1, $tx->res);
};
return 1;
};
sub _notify_on_error {
my ($self, $c, $failure, $res) = @_;
my $json = $res;
my $log = $c->app->log;
if (blessed $res) {
if (blessed $res ne 'Mojo::JSON') {
$json = $res->json;
};
};
if ($json) {
if ($json->{error}) {
# Temp
$json->{error} =~ s/;\s+null$//;
$c->notify(error => $json->{error});
return;
}
# New error messages
elsif ($json->{errstr}) {
# Temp
$json->{errstr} =~ s/;\s+null$//;
$c->notify(error => $json->{errstr});
return;
}
# policy service error messages
elsif ($json->{status}) {
$c->notify(error => 'Middleware error ' . $json->{status});
return;
};
};
if ($failure) {
$c->notify(error => (
($res->{code} ? $res->{code} . ': ' : '') .
($res->{message} ? $res->{message} : 'Unknown error') .
' (remote)'
));
};
};
sub _map_matches {
return () unless $_[0];
map {
$_->{ID} =~ s/^match\-[^!]+![^-]+-//;
$_->{docID} =~ s/^[^_]+_//;
$_;
} @{ shift() };
};
1;
__END__
=pod
Additionally supported query parameters:
- query_language
- cutoff
- no_cache
Additional index attributes:
- api
- time_exceeded
- api_request
- api_response
- benchmark
- query_jsonld