Skip to content

Commit faabd1b

Browse files
committed
feat(api): add stemming dictionaries support
- add `StemmingInterface` with dictionary management methods - implement dictionary create, retrieve and list operations - add integration tests for stemming dictionary operations - register stemming client in the main `Client` struct
1 parent 44318eb commit faabd1b

File tree

5 files changed

+192
-0
lines changed

5 files changed

+192
-0
lines changed

Diff for: typesense/client.go

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ func (c *Client) Analytics() AnalyticsInterface {
4848
return &analytics{apiClient: c.apiClient}
4949
}
5050

51+
func (c *Client) Stemming() StemmingInterface {
52+
return &stemming{apiClient: c.apiClient}
53+
}
54+
5155
func (c *Client) Conversations() ConversationsInterface {
5256
return &conversations{apiClient: c.apiClient}
5357
}

Diff for: typesense/stemming.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package typesense
2+
3+
type StemmingInterface interface {
4+
Dictionaries() StemmingDictionariesInterface
5+
Dictionary(dictionaryId string) StemmingDictionaryInterface
6+
}
7+
8+
type stemming struct {
9+
apiClient APIClientInterface
10+
}
11+
12+
func (s *stemming) Dictionaries() StemmingDictionariesInterface {
13+
return &stemmingDictionaries{apiClient: s.apiClient}
14+
}
15+
16+
func (s *stemming) Dictionary(dictionaryId string) StemmingDictionaryInterface {
17+
return &stemmingDictionary{apiClient: s.apiClient, dictionaryId: dictionaryId}
18+
}

Diff for: typesense/stemming_dictionaries.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package typesense
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"io"
8+
"net/http"
9+
10+
"github.com/typesense/typesense-go/v3/typesense/api"
11+
)
12+
13+
type StemmingDictionariesInterface interface {
14+
Upsert(ctx context.Context, dictionaryId string, wordRootCombinations []api.StemmingDictionaryWord) ([]*api.StemmingDictionaryWord, error)
15+
UpsertJsonl(ctx context.Context, dictionaryId string, body io.Reader) (io.ReadCloser, error)
16+
Retrieve(ctx context.Context) (*api.ListStemmingDictionariesResponse, error)
17+
}
18+
19+
type stemmingDictionaries struct {
20+
apiClient APIClientInterface
21+
}
22+
23+
func (s *stemmingDictionaries) Upsert(ctx context.Context, dictionaryId string, wordRootCombinations []api.StemmingDictionaryWord) ([]*api.StemmingDictionaryWord, error) {
24+
var buf bytes.Buffer
25+
jsonEncoder := json.NewEncoder(&buf)
26+
for _, combo := range wordRootCombinations {
27+
if err := jsonEncoder.Encode(combo); err != nil {
28+
return nil, err
29+
}
30+
}
31+
32+
response, err := s.UpsertJsonl(ctx, dictionaryId, &buf)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
var result []*api.StemmingDictionaryWord
38+
jsonDecoder := json.NewDecoder(response)
39+
for jsonDecoder.More() {
40+
var docResult *api.StemmingDictionaryWord
41+
if err := jsonDecoder.Decode(&docResult); err != nil {
42+
return result, err
43+
}
44+
result = append(result, docResult)
45+
}
46+
47+
return result, nil
48+
}
49+
50+
func (s *stemmingDictionaries) UpsertJsonl(ctx context.Context, dictionaryId string, body io.Reader) (io.ReadCloser, error) {
51+
params := &api.ImportStemmingDictionaryParams{
52+
Id: dictionaryId,
53+
}
54+
55+
response, err := s.apiClient.ImportStemmingDictionaryWithBody(ctx,
56+
params, "application/octet-stream", body)
57+
if err != nil {
58+
return nil, err
59+
}
60+
if response.StatusCode != http.StatusOK {
61+
defer response.Body.Close()
62+
body, _ := io.ReadAll(response.Body)
63+
return nil, &HTTPError{Status: response.StatusCode, Body: body}
64+
}
65+
return response.Body, nil
66+
}
67+
68+
func (s *stemmingDictionaries) Retrieve(ctx context.Context) (*api.ListStemmingDictionariesResponse, error) {
69+
response, err := s.apiClient.ListStemmingDictionariesWithResponse(ctx)
70+
if err != nil {
71+
return nil, err
72+
}
73+
if response.JSON200 == nil {
74+
emptySlice := make([]string, 0)
75+
return &api.ListStemmingDictionariesResponse{
76+
JSON200: &struct {
77+
Dictionaries *[]string `json:"dictionaries,omitempty"`
78+
}{
79+
Dictionaries: &emptySlice,
80+
},
81+
}, nil
82+
}
83+
return response, nil
84+
}

Diff for: typesense/stemming_dictionary.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package typesense
2+
3+
import (
4+
"context"
5+
6+
"github.com/typesense/typesense-go/v3/typesense/api"
7+
)
8+
9+
type StemmingDictionaryInterface interface {
10+
Retrieve(ctx context.Context) (*api.StemmingDictionary, error)
11+
}
12+
13+
type stemmingDictionary struct {
14+
apiClient APIClientInterface
15+
dictionaryId string
16+
}
17+
18+
func (s *stemmingDictionary) Retrieve(ctx context.Context) (*api.StemmingDictionary, error) {
19+
response, err := s.apiClient.GetStemmingDictionaryWithResponse(ctx, s.dictionaryId)
20+
if err != nil {
21+
return nil, err
22+
}
23+
if response.JSON200 == nil {
24+
return nil, &HTTPError{Status: response.StatusCode(), Body: response.Body}
25+
}
26+
return response.JSON200, nil
27+
}

Diff for: typesense/test/stemming_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package test
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/require"
13+
"github.com/typesense/typesense-go/v3/typesense/api"
14+
)
15+
16+
func TestStemmingDictionary(t *testing.T) {
17+
dictionaryId := fmt.Sprintf("dictionary_%d", time.Now().UnixNano())
18+
words := []api.StemmingDictionaryWord{
19+
{
20+
Root: "exampleRoot1",
21+
Word: "exampleWord1",
22+
},
23+
{
24+
Root: "exampleRoot2",
25+
Word: "exampleWord2",
26+
},
27+
}
28+
29+
t.Run("Upsert", func(t *testing.T) {
30+
result, err := typesenseClient.Stemming().Dictionaries().Upsert(
31+
context.Background(),
32+
dictionaryId,
33+
words,
34+
)
35+
require.NoError(t, err)
36+
require.Len(t, result, len(words))
37+
})
38+
39+
t.Run("Retrieve Single", func(t *testing.T) {
40+
result, err := typesenseClient.Stemming().Dictionary(dictionaryId).Retrieve(context.Background())
41+
require.NoError(t, err)
42+
require.Equal(t, dictionaryId, result.Id)
43+
// Convert result.Words to the same type for comparison
44+
retrievedWords := make([]api.StemmingDictionaryWord, len(result.Words))
45+
for i, w := range result.Words {
46+
retrievedWords[i] = api.StemmingDictionaryWord{
47+
Root: w.Root,
48+
Word: w.Word,
49+
}
50+
}
51+
require.Equal(t, words, retrievedWords)
52+
})
53+
54+
t.Run("List All", func(t *testing.T) {
55+
result, err := typesenseClient.Stemming().Dictionaries().Retrieve(context.Background())
56+
require.NoError(t, err)
57+
require.Contains(t, *result.JSON200.Dictionaries, dictionaryId)
58+
})
59+
}

0 commit comments

Comments
 (0)