Files
modrinth/apps/labrinth/src/search/backend/common.rs
T
aecsocket 158de019a6 fix: search indexing performance and batching (#6521)
* Add consume batching delay

* maybe fix

* max batch size

* more logging

* parallelize remove tasks

* delete/upsert project/version messages

* prepare

* more logging

* log number of docs

* try more targeted, homogenous version change ops

* ensure only necessary fields are serialized into typesense

* disable index background task

* wip: script changes

* don't facet by project id

* fix

* fix clippy

* batch by document and dedup loaders

* fix test

* wip: projects/versions collections

* clean up SearchBackend interface

* cleanup pass

* cleanup pass 2

* standardise fn names

* cleanup pass

* fix compile

* factor out filter rewriting

* query perf

* put categories into the project doc

* wip: search filter AST

* doc comment

* more AST normalization

* (temp) convert search request errors into internal errors

* implement unary NOT

* tombi fmt

* fix tests

* revert request error

* try increase stack size
2026-07-26 14:55:00 +00:00

107 lines
3.0 KiB
Rust

use crate::routes::ApiError;
use crate::search::SearchRequest;
use crate::util::error::Context;
use eyre::eyre;
use std::borrow::Cow;
pub struct ParsedSearchRequest<'a> {
pub offset: usize,
pub hits_per_page: usize,
pub page: usize,
pub index: &'a str,
pub query: &'a str,
}
pub fn parse_search_request(
info: &SearchRequest,
) -> Result<ParsedSearchRequest<'_>, ApiError> {
let offset = info
.offset
.as_deref()
.unwrap_or("0")
.parse::<usize>()
.wrap_request_err("invalid offset")?;
let limit = info
.limit
.as_deref()
.unwrap_or("10")
.parse::<usize>()
.wrap_request_err("invalid limit")?
.min(100);
let hits_per_page = if limit == 0 { 1 } else { limit };
Ok(ParsedSearchRequest {
offset,
hits_per_page,
page: offset / hits_per_page + 1,
index: info.index.as_deref().unwrap_or("relevance"),
query: info.query.as_deref().unwrap_or_default(),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SearchIndex {
Relevance,
Downloads,
Follows,
Updated,
Newest,
MinecraftJavaServerVerifiedPlays2w,
MinecraftJavaServerPlayersOnline,
}
pub struct SearchSort {
pub index: SearchIndex,
}
pub fn parse_search_index(
index: &str,
new_filters: Option<&str>,
) -> Result<SearchSort, ApiError> {
// TODO: this is a dumb hack, the frontend should pass the project type it's filtering directly
let is_server = new_filters
.is_some_and(|f| f.contains("project_types = minecraft_java_server"));
Ok(match index {
"relevance" => SearchSort {
index: if is_server {
SearchIndex::MinecraftJavaServerVerifiedPlays2w
} else {
SearchIndex::Relevance
},
},
"downloads" => SearchSort {
index: SearchIndex::Downloads,
},
"follows" => SearchSort {
index: SearchIndex::Follows,
},
"updated" | "date_modified" => SearchSort {
index: SearchIndex::Updated,
},
"newest" | "date_created" => SearchSort {
index: SearchIndex::Newest,
},
"minecraft_java_server.verified_plays_2w" => SearchSort {
index: SearchIndex::MinecraftJavaServerVerifiedPlays2w,
},
"minecraft_java_server.ping.data.players_online" => SearchSort {
index: SearchIndex::MinecraftJavaServerPlayersOnline,
},
i => return Err(ApiError::Request(eyre!("invalid index '{i}'"))),
})
}
pub fn combined_search_filters(info: &SearchRequest) -> Option<Cow<'_, str>> {
if let Some(filters) = info.new_filters.as_deref() {
return Some(filters.into());
}
match (info.filters.as_deref(), info.version.as_deref()) {
(Some(f), Some(v)) => Some(format!("({f}) AND ({v})").into()),
(Some(f), None) => Some(f.into()),
(None, Some(v)) => Some(v.into()),
(None, None) => None,
}
}