(search) Refactor search parameters to include query

This commit is contained in:
Viktor Lofgren 2023-11-10 16:20:48 +01:00
parent 01621c6344
commit a258f0af7a
15 changed files with 104 additions and 64 deletions

View File

@ -109,12 +109,14 @@ public class AbstractClientTest {
assertError(client.post(Context.internal(), 0,"/post", "test"));
}
@Test
public void testGet404() {
testServer.get(this::error404);
assertError(client.get(Context.internal(), 0,"/get"));
}
@Test
public void testDelete404() {
testServer.delete(this::error404);

View File

@ -9,10 +9,11 @@ import nu.marginalia.model.EdgeDomain;
import nu.marginalia.db.DbDomainQueries;
import nu.marginalia.query.client.QueryClient;
import nu.marginalia.query.model.QueryResponse;
import nu.marginalia.search.command.SearchParameters;
import nu.marginalia.search.model.SearchProfile;
import nu.marginalia.search.model.UrlDetails;
import nu.marginalia.client.Context;
import nu.marginalia.search.model.DecoratedSearchResults;
import nu.marginalia.search.model.UserSearchParameters;
import nu.marginalia.search.svc.SearchQueryIndexService;
import nu.marginalia.search.svc.SearchUnitConversionService;
import org.apache.logging.log4j.util.Strings;
@ -70,9 +71,9 @@ public class SearchOperator {
return searchQueryService.getResultsFromQuery(queryResponse);
}
public DecoratedSearchResults doSearch(Context ctx, UserSearchParameters userParams) {
public DecoratedSearchResults doSearch(Context ctx, SearchParameters userParams) {
Future<String> eval = searchUnitConversionService.tryEval(ctx, userParams.humanQuery());
Future<String> eval = searchUnitConversionService.tryEval(ctx, userParams.query());
var queryParams = paramFactory.forRegularSearch(userParams);
var queryResponse = queryClient.search(ctx, queryParams);

View File

@ -5,20 +5,20 @@ import nu.marginalia.index.client.model.query.SearchSubquery;
import nu.marginalia.index.query.limit.QueryLimits;
import nu.marginalia.index.query.limit.SpecificationLimit;
import nu.marginalia.query.model.QueryParams;
import nu.marginalia.search.model.UserSearchParameters;
import nu.marginalia.search.command.SearchParameters;
import java.util.List;
public class SearchQueryParamFactory {
public QueryParams forRegularSearch(UserSearchParameters userParams) {
public QueryParams forRegularSearch(SearchParameters userParams) {
SearchSubquery prototype = new SearchSubquery();
var profile = userParams.profile();
profile.addTacitTerms(prototype);
userParams.jsSetting().addTacitTerms(prototype);
userParams.js().addTacitTerms(prototype);
return new QueryParams(
userParams.humanQuery(),
userParams.query(),
null,
prototype.searchTermsInclude,
prototype.searchTermsExclude,

View File

@ -30,16 +30,16 @@ public class CommandEvaluator {
defaultCommand = search;
}
public Object eval(Context ctx, SearchParameters parameters, String query) {
public Object eval(Context ctx, SearchParameters parameters) {
for (var cmd : specialCommands) {
var ret = cmd.process(ctx, parameters, query);
var ret = cmd.process(ctx, parameters);
if (ret.isPresent()) {
return ret.get();
}
}
// Always process the search command last
return defaultCommand.process(ctx, parameters, query)
return defaultCommand.process(ctx, parameters)
.orElseThrow(() -> new IllegalStateException("Search Command returned Optional.empty()!") /* This Should Not be Possible™ */ );
}

View File

@ -6,5 +6,5 @@ import nu.marginalia.client.Context;
import java.util.Optional;
public interface SearchCommandInterface {
Optional<Object> process(Context ctx, SearchParameters parameters, String query);
Optional<Object> process(Context ctx, SearchParameters parameters);
}

View File

@ -2,7 +2,7 @@ package nu.marginalia.search.command;
import nu.marginalia.search.model.SearchProfile;
public record SearchParameters(SearchProfile profile, SearchJsParameter js, boolean detailedResults) {
public record SearchParameters(String query, SearchProfile profile, SearchJsParameter js) {
public String profileStr() {
return profile.name;
}

View File

@ -23,36 +23,75 @@ public class BangCommand implements SearchCommandInterface {
}
@Override
public Optional<Object> process(Context ctx, SearchParameters parameters, String query) {
public Optional<Object> process(Context ctx, SearchParameters parameters) {
for (var entry : bangsToPattern.entrySet()) {
matchBangPattern(query, entry.getKey(), entry.getValue());
String bangPattern = entry.getKey();
String redirectPattern = entry.getValue();
var match = matchBangPattern(parameters.query(), bangPattern);
if (match.isPresent()) {
var url = String.format(redirectPattern, URLEncoder.encode(match.get(), StandardCharsets.UTF_8));
throw new RedirectException(url);
}
}
return Optional.empty();
}
private void matchBangPattern(String query, String bangKey, String urlPattern) {
for (int idx = query.indexOf(bangKey); idx >= 0; idx = query.indexOf(bangKey, idx + 1)) {
private Optional<String> matchBangPattern(String query, String bangKey) {
var bm = new BangMatcher(query);
if (idx > 0) { // Don't match "search term!b", require either "!b term" or "search term !b"
if (!Character.isSpaceChar(query.charAt(idx-1))) {
continue;
}
}
int nextIdx = idx + bangKey.length();
while (bm.findNext(bangKey)) {
if (nextIdx >= query.length()) { // allow "search term !b"
redirect(urlPattern, query.substring(0, idx));
}
else if (Character.isSpaceChar(query.charAt(nextIdx))) { // skip matches on pattern "!bsearch term" for !b
redirect(urlPattern, query.substring(0, idx).stripTrailing() + " " + query.substring(nextIdx).stripLeading());
}
if (bm.isRelativeSpaceOrInvalid(-1))
continue;
if (bm.isRelativeSpaceOrInvalid(bangKey.length()))
continue;
String queryWithoutBang = bm.prefix().trim() + " " + bm.suffix(bangKey.length()).trim();
return Optional.of(queryWithoutBang);
}
return Optional.empty();
}
private void redirect(String pattern, String terms) {
var url = String.format(pattern, URLEncoder.encode(terms.trim(), StandardCharsets.UTF_8));
throw new RedirectException(url);
private static class BangMatcher {
private final String str;
private int pos;
public String prefix() {
return str.substring(0, pos);
}
public String suffix(int offset) {
if (pos+offset < str.length())
return str.substring(pos + offset);
return "";
}
public BangMatcher(String str) {
this.str = str;
this.pos = -1;
}
public boolean findNext(String pattern) {
if (pos + 1 >= str.length())
return false;
return (pos = str.indexOf(pattern, pos + 1)) >= 0;
}
public boolean isRelativeSpaceOrInvalid(int offset) {
if (offset + pos < 0)
return true;
if (offset + pos >= str.length())
return true;
return Character.isSpaceChar(str.charAt(offset + pos));
}
}
}

View File

@ -56,14 +56,14 @@ public class BrowseCommand implements SearchCommandInterface {
}
@Override
public Optional<Object> process(Context ctx, SearchParameters parameters, String query) {
if (!queryPatternPredicate.test(query)) {
public Optional<Object> process(Context ctx, SearchParameters parameters) {
if (!queryPatternPredicate.test(parameters.query())) {
return Optional.empty();
}
return Optional.ofNullable(browseSite(ctx, query))
return Optional.ofNullable(browseSite(ctx, parameters.query()))
.map(results -> browseResultsRenderer.render(results,
Map.of("query", query,
Map.of("query", parameters.query(),
"profile", parameters.profileStr(),
"focusDomain", results.focusDomain())));
}

View File

@ -24,12 +24,16 @@ public class ConvertCommand implements SearchCommandInterface {
}
@Override
public Optional<Object> process(Context ctx, SearchParameters parameters, String query) {
var conversion = searchUnitConversionService.tryConversion(ctx, query);
public Optional<Object> process(Context ctx, SearchParameters parameters) {
var conversion = searchUnitConversionService.tryConversion(ctx, parameters.query());
if (conversion.isEmpty()) {
return Optional.empty();
}
return Optional.of(conversionRenderer.render(Map.of("query", query, "result", conversion.get(), "profile", parameters.profileStr())));
return Optional.of(conversionRenderer.render(Map.of(
"query", parameters.query(),
"result", conversion.get(),
"profile", parameters.profileStr()))
);
}
}

View File

@ -38,14 +38,17 @@ public class DefinitionCommand implements SearchCommandInterface {
}
@Override
public Optional<Object> process(Context ctx, SearchParameters parameters, String query) {
if (!queryPatternPredicate.test(query.trim())) {
public Optional<Object> process(Context ctx, SearchParameters parameters) {
if (!queryPatternPredicate.test(parameters.query())) {
return Optional.empty();
}
var results = lookupDefinition(ctx, query);
var results = lookupDefinition(ctx, parameters.query());
return Optional.of(dictionaryRenderer.render(results, Map.of("query", query, "profile", parameters.profileStr())));
return Optional.of(dictionaryRenderer.render(results,
Map.of("query", parameters.query(),
"profile", parameters.profileStr())
));
}

View File

@ -8,7 +8,6 @@ import nu.marginalia.search.command.SearchCommandInterface;
import nu.marginalia.search.command.SearchParameters;
import nu.marginalia.search.model.DecoratedSearchResults;
import nu.marginalia.search.model.UrlDetails;
import nu.marginalia.search.model.UserSearchParameters;
import nu.marginalia.renderer.MustacheRenderer;
import nu.marginalia.renderer.RendererFactory;
@ -33,10 +32,8 @@ public class SearchCommand implements SearchCommandInterface {
}
@Override
public Optional<Object> process(Context ctx, SearchParameters parameters, String query) {
UserSearchParameters params = new UserSearchParameters(query, parameters.profile(), parameters.js());
DecoratedSearchResults results = searchOperator.doSearch(ctx, params);
public Optional<Object> process(Context ctx, SearchParameters parameters) {
DecoratedSearchResults results = searchOperator.doSearch(ctx, parameters);
return Optional.of(searchResultsRenderer.render(results));
}

View File

@ -51,12 +51,12 @@ public class SiteListCommand implements SearchCommandInterface {
}
@Override
public Optional<Object> process(Context ctx, SearchParameters parameters, String query) {
if (!queryPatternPredicate.test(query)) {
public Optional<Object> process(Context ctx, SearchParameters parameters) {
if (!queryPatternPredicate.test(parameters.query())) {
return Optional.empty();
}
var results = siteInfo(ctx, query);
var results = siteInfo(ctx, parameters.query());
var domain = results.getDomain();
List<UrlDetails> resultSet;
@ -81,7 +81,7 @@ public class SiteListCommand implements SearchCommandInterface {
Map<String, Object> renderObject = new HashMap<>(10);
renderObject.put("query", query);
renderObject.put("query", parameters.query());
renderObject.put("hideRanking", true);
renderObject.put("profile", parameters.profileStr());
renderObject.put("results", resultSet);

View File

@ -3,12 +3,13 @@ package nu.marginalia.search.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import nu.marginalia.search.command.SearchParameters;
import java.util.List;
@AllArgsConstructor @Getter @Builder
public class DecoratedSearchResults {
private final UserSearchParameters params;
private final SearchParameters params;
private final List<String> problems;
private final String evalResult;
@ -18,12 +19,12 @@ public class DecoratedSearchResults {
private final int focusDomainId;
public String getQuery() {
return params.humanQuery();
return params.query();
}
public String getProfile() {
return params.profile().name;
}
public String getJs() {
return params.jsSetting().value;
return params.js().value;
}
}

View File

@ -1,7 +0,0 @@
package nu.marginalia.search.model;
import nu.marginalia.search.command.SearchJsParameter;
import nu.marginalia.search.model.SearchProfile;
public record UserSearchParameters(String humanQuery, SearchProfile profile, SearchJsParameter jsSetting) {
}

View File

@ -48,13 +48,13 @@ public class SearchQueryService {
final String humanQuery = queryParam.trim();
var params = new SearchParameters(
humanQuery,
SearchProfile.getSearchProfile(profileStr),
SearchJsParameter.parse(request.queryParams("js")),
Boolean.parseBoolean(request.queryParams("detailed"))
SearchJsParameter.parse(request.queryParams("js"))
);
try {
return searchCommandEvaulator.eval(ctx, params, humanQuery);
return searchCommandEvaulator.eval(ctx, params);
}
catch (RedirectException ex) {
response.redirect(ex.newUrl);