diff --git a/code/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java b/code/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java index b0e85202..44445204 100644 --- a/code/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java +++ b/code/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java @@ -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); diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java index 9b90ddd9..61904fc3 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java @@ -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 eval = searchUnitConversionService.tryEval(ctx, userParams.humanQuery()); + Future eval = searchUnitConversionService.tryEval(ctx, userParams.query()); var queryParams = paramFactory.forRegularSearch(userParams); var queryResponse = queryClient.search(ctx, queryParams); diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java index 4c1b45b4..2e5e00a6 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java @@ -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, diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java index b9cfc852..bf72dbd6 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java @@ -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ā„¢ */ ); } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java index 3e2304b7..d543aa98 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java @@ -6,5 +6,5 @@ import nu.marginalia.client.Context; import java.util.Optional; public interface SearchCommandInterface { - Optional process(Context ctx, SearchParameters parameters, String query); + Optional process(Context ctx, SearchParameters parameters); } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java index 58c64dd6..b222e0d4 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java @@ -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; } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java index 5153dea8..0701031f 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java @@ -23,36 +23,75 @@ public class BangCommand implements SearchCommandInterface { } @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { + public Optional 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 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)); + } + } + } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java index 3124c599..47512195 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java @@ -56,14 +56,14 @@ public class BrowseCommand implements SearchCommandInterface { } @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { - if (!queryPatternPredicate.test(query)) { + public Optional 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()))); } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java index d022efac..3b434865 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java @@ -24,12 +24,16 @@ public class ConvertCommand implements SearchCommandInterface { } @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { - var conversion = searchUnitConversionService.tryConversion(ctx, query); + public Optional 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())) + ); } } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java index efd99447..a9b401a6 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java @@ -38,14 +38,17 @@ public class DefinitionCommand implements SearchCommandInterface { } @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { - if (!queryPatternPredicate.test(query.trim())) { + public Optional 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()) + )); } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java index 115c5566..2bc97ad2 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java @@ -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 process(Context ctx, SearchParameters parameters, String query) { - UserSearchParameters params = new UserSearchParameters(query, parameters.profile(), parameters.js()); - - DecoratedSearchResults results = searchOperator.doSearch(ctx, params); + public Optional process(Context ctx, SearchParameters parameters) { + DecoratedSearchResults results = searchOperator.doSearch(ctx, parameters); return Optional.of(searchResultsRenderer.render(results)); } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java index a42c7345..6278a344 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java @@ -51,12 +51,12 @@ public class SiteListCommand implements SearchCommandInterface { } @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { - if (!queryPatternPredicate.test(query)) { + public Optional 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 resultSet; @@ -81,7 +81,7 @@ public class SiteListCommand implements SearchCommandInterface { Map 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); diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java index 6871e274..c994e75b 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java @@ -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 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; } } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/model/UserSearchParameters.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/model/UserSearchParameters.java deleted file mode 100644 index 278f8562..00000000 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/model/UserSearchParameters.java +++ /dev/null @@ -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) { -} diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java index 9fdb5446..e4accf0c 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java @@ -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);