@@ -5,6 +5,7 @@ package meilisearch
55
66import (
77 "context"
8+ "errors"
89 "strconv"
910 "strings"
1011
@@ -16,12 +17,15 @@ import (
1617)
1718
1819const (
19- issueIndexerLatestVersion = 2
20+ issueIndexerLatestVersion = 3
2021
2122 // TODO: make this configurable if necessary
2223 maxTotalHits = 10000
2324)
2425
26+ // ErrMalformedResponse is never expected as we initialize the indexer ourself and so define the types.
27+ var ErrMalformedResponse = errors .New ("meilisearch returned unexpected malformed content" )
28+
2529var _ internal.Indexer = & Indexer {}
2630
2731// Indexer implements Indexer interface
@@ -47,6 +51,9 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer {
4751 },
4852 DisplayedAttributes : []string {
4953 "id" ,
54+ "title" ,
55+ "content" ,
56+ "comments" ,
5057 },
5158 FilterableAttributes : []string {
5259 "repo_id" ,
@@ -221,11 +228,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
221228 return nil , err
222229 }
223230
224- hits := make ([]internal.Match , 0 , len (searchRes .Hits ))
225- for _ , hit := range searchRes .Hits {
226- hits = append (hits , internal.Match {
227- ID : int64 (hit .(map [string ]any )["id" ].(float64 )),
228- })
231+ hits , err := nonFuzzyWorkaround (searchRes , options .Keyword , options .IsFuzzyKeyword )
232+ if err != nil {
233+ return nil , err
229234 }
230235
231236 return & internal.SearchResult {
@@ -241,3 +246,77 @@ func parseSortBy(sortBy internal.SortBy) string {
241246 }
242247 return field + ":asc"
243248}
249+
250+ // nonFuzzyWorkaround is needed as meilisearch does not have an exact search
251+ // and you can only change "typo tolerance" per index. So we have to post-filter the results
252+ // https://www.meilisearch.com/docs/learn/configuration/typo_tolerance#configuring-typo-tolerance
253+ // TODO: remove once https://github.com/orgs/meilisearch/discussions/377 is addressed
254+ func nonFuzzyWorkaround (searchRes * meilisearch.SearchResponse , keyword string , isFuzzy bool ) ([]internal.Match , error ) {
255+ hits := make ([]internal.Match , 0 , len (searchRes .Hits ))
256+ for _ , hit := range searchRes .Hits {
257+ hit , ok := hit .(map [string ]any )
258+ if ! ok {
259+ return nil , ErrMalformedResponse
260+ }
261+
262+ if ! isFuzzy {
263+ keyword = strings .ToLower (keyword )
264+
265+ // declare a anon func to check if the title, content or at least one comment contains the keyword
266+ found , err := func () (bool , error ) {
267+ // check if title match first
268+ title , ok := hit ["title" ].(string )
269+ if ! ok {
270+ return false , ErrMalformedResponse
271+ } else if strings .Contains (strings .ToLower (title ), keyword ) {
272+ return true , nil
273+ }
274+
275+ // check if content has a match
276+ content , ok := hit ["content" ].(string )
277+ if ! ok {
278+ return false , ErrMalformedResponse
279+ } else if strings .Contains (strings .ToLower (content ), keyword ) {
280+ return true , nil
281+ }
282+
283+ // now check for each comment if one has a match
284+ // so we first try to cast and skip if there are no comments
285+ comments , ok := hit ["comments" ].([]any )
286+ if ! ok {
287+ return false , ErrMalformedResponse
288+ } else if len (comments ) == 0 {
289+ return false , nil
290+ }
291+
292+ // now we iterate over all and report as soon as we detect one match
293+ for i := range comments {
294+ comment , ok := comments [i ].(string )
295+ if ! ok {
296+ return false , ErrMalformedResponse
297+ }
298+ if strings .Contains (strings .ToLower (comment ), keyword ) {
299+ return true , nil
300+ }
301+ }
302+
303+ // we got no match
304+ return false , nil
305+ }()
306+
307+ if err != nil {
308+ return nil , err
309+ } else if ! found {
310+ continue
311+ }
312+ }
313+ issueID , ok := hit ["id" ].(float64 )
314+ if ! ok {
315+ return nil , ErrMalformedResponse
316+ }
317+ hits = append (hits , internal.Match {
318+ ID : int64 (issueID ),
319+ })
320+ }
321+ return hits , nil
322+ }
0 commit comments