<|start_filename|>algolia/personalization/utils.go<|end_filename|>
package personalization

import (
	"fmt"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/call"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/region"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/transport"
)

func defaultHosts(r region.Region) (hosts []*transport.StatefulHost) {
	switch r {
	case region.EU, region.US:
		hosts = append(hosts, transport.NewStatefulHost(fmt.Sprintf("recommendation.%s.algolia.com", r), call.IsReadWrite))
	default:
		hosts = append(hosts, transport.NewStatefulHost("recommendation.us.algolia.com", call.IsReadWrite))
	}
	return
}


<|start_filename|>algolia/search/rendering_content.go<|end_filename|>
package search

// Content defining how the search interface should be rendered.
// This is set via the settings for a default value and can be overridden via rules
type RenderingContent struct {
	FacetOrdering *FacetOrdering `json:"facetOrdering"`
}

// Facets and facets values ordering rules container
type FacetOrdering struct {
	// The ordering of facets.
	Facets *FacetsOrder `json:"facets"`

	// The ordering of facet values, within an individual list.
	Values map[string]FacetValuesOrder `json:"values"`
}

// Facets ordering rule container
type FacetsOrder struct {
	// Pinned order of facet lists.
	Order []string `json:"order"`
}

// Facet values ordering rule container
type FacetValuesOrder struct {
	// Pinned order of facet values.
	Order []string `json:"order"`

	// How to display the remaining items.
	SortRemainingBy *SortRule `json:"sortRemainingBy"`
}

func NewFacetValuesOrder(order []string, sortRemainingBy SortRule) FacetValuesOrder {
	return FacetValuesOrder{Order: order, SortRemainingBy: &sortRemainingBy}
}

// Rule defining the sort order of facet values.
type SortRule string

const (
	// alphabetical (ascending)
	Alpha SortRule = "alpha"

	// facet count (descending)
	Count SortRule = "count"

	// hidden (show only pinned values)
	Hidden SortRule = "hidden"
)


<|start_filename|>algolia/search/rendering_content_test.go<|end_filename|>
package search

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestUnmarshalRenderingContent(t *testing.T) {

	payload := `{
		"facetOrdering": {
			"facets": {
				"order": ["brand", "size", "color"]
			},
			"values": {
				"brand": {
					"order": ["Apple", "Sony", "Samsung"],
					"sortRemainingBy": "alpha"
				},
				"color": {
					"order": ["red", "green"]
				}
			}
		}
	}`
	var r RenderingContent
	err := json.Unmarshal([]byte(payload), &r)
	require.NoError(t, err)
	require.Equal(t, r.FacetOrdering.Facets.Order, []string{"brand", "size", "color"})
	require.Equal(t, r.FacetOrdering.Values["brand"].Order, []string{"Apple", "Sony", "Samsung"})
	require.Equal(t, *r.FacetOrdering.Values["brand"].SortRemainingBy, Alpha)
	require.Equal(t, r.FacetOrdering.Values["color"].Order, []string{"red", "green"})
	require.Nil(t, r.FacetOrdering.Values["color"].SortRemainingBy)
}

func TestBuildRenderingContent(t *testing.T) {

	var r = RenderingContent{FacetOrdering: &FacetOrdering{
		Facets: &FacetsOrder{Order: []string{"size", "brand"}},
		Values: map[string]FacetValuesOrder{
			"brand": NewFacetValuesOrder([]string{"Uniqlo"}, Count),
			"size":  NewFacetValuesOrder([]string{"S", "M", "L"}, Hidden),
		},
	}}

	require.Equal(t, r.FacetOrdering.Facets.Order, []string{"size", "brand"})
	require.Equal(t, r.FacetOrdering.Values["brand"].Order, []string{"Uniqlo"})
	require.Equal(t, *r.FacetOrdering.Values["brand"].SortRemainingBy, Count)
	require.Equal(t, r.FacetOrdering.Values["size"].Order, []string{"S", "M", "L"})
	require.Equal(t, *r.FacetOrdering.Values["size"].SortRemainingBy, Hidden)

}


<|start_filename|>cts/testing_utils.go<|end_filename|>
package cts

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"math/rand"
	"os"
	"os/user"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/analytics"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/compression"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/insights"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/personalization"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/region"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/search"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/suggestions"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/transport"
)

func InitSearchClient1AndIndex(t *testing.T) (*search.Client, *search.Index, string) {
	c := InitSearchClient1(t)
	indexName := GenerateIndexName(t)
	i := c.InitIndex(indexName)
	return c, i, indexName
}

func GenerateIndexName(t *testing.T) string {
	indexName := GenerateCanonicalPrefixName() + "_" + t.Name()
	indexName = cleanIndexName(indexName)
	return indexName
}

func cleanIndexName(indexName string) string {
	for _, char := range []string{
		"/",
		"-",
		".",
		":",
		" ",
		"%",
		"+",
		"^",
	} {
		indexName = strings.Replace(indexName, char, "_", -1) //nolint:gocritic
	}
	return indexName
}

func InitSearchClient1(t *testing.T) *search.Client {
	return initSearchClientWith(t, "ALGOLIA_APPLICATION_ID_1", "ALGOLIA_ADMIN_KEY_1")
}

func InitSearchClient2(t *testing.T) *search.Client {
	return initSearchClientWith(t, "ALGOLIA_APPLICATION_ID_2", "ALGOLIA_ADMIN_KEY_2")
}

func InitSearchClientMCM(t *testing.T) *search.Client {
	return initSearchClientWith(t, "ALGOLIA_APPLICATION_ID_MCM", "ALGOLIA_ADMIN_KEY_MCM")
}

func InitAnalyticsClient1(t *testing.T) *analytics.Client {
	return initAnalyticsClientWith(t, "ALGOLIA_APPLICATION_ID_1", "ALGOLIA_ADMIN_KEY_1")
}

func InitInsightsClient(t *testing.T) *insights.Client {
	return initInsightsClientWith(t, "ALGOLIA_APPLICATION_ID_1", "ALGOLIA_ADMIN_KEY_1")
}

func InitPersonalizationClient(t *testing.T) *personalization.Client {
	return initPersonalizationClientWith(t, "ALGOLIA_APPLICATION_ID_1", "ALGOLIA_ADMIN_KEY_1")
}

func InitQuerySuggestionsClient1(t *testing.T) *suggestions.Client {
	return initQuerySuggestionsClientWith(t, "ALGOLIA_APPLICATION_ID_1", "ALGOLIA_ADMIN_KEY_1")
}

func initInsightsClientWith(t *testing.T, appIDEnvVar, apiKeyEnvVar string) *insights.Client {
	appID, key := GetTestingCredentials(t, appIDEnvVar, apiKeyEnvVar)
	c := insights.NewClient(appID, key)
	return c
}

func initSearchClientWith(t *testing.T, appIDEnvVar, apiKeyEnvVar string) *search.Client {
	appID, key := GetTestingCredentials(t, appIDEnvVar, apiKeyEnvVar)
	var c *search.Client
	if appIDEnvVar == "ALGOLIA_APPLICATION_ID_2" { // TODO: temporary workaround while GZIP is not enabled on this cluster
		c = search.NewClientWithConfig(search.Configuration{AppID: appID, APIKey: key, Compression: compression.None})
	} else {
		c = search.NewClient(appID, key)
	}
	return c
}

func initAnalyticsClientWith(t *testing.T, appIDEnvVar, apiKeyEnvVar string) *analytics.Client {
	appID, key := GetTestingCredentials(t, appIDEnvVar, apiKeyEnvVar)
	c := analytics.NewClient(appID, key)
	return c
}

func initPersonalizationClientWith(t *testing.T, appIDEnvVar, apiKeyEnvVar string) *personalization.Client {
	appID, key := GetTestingCredentials(t, appIDEnvVar, apiKeyEnvVar)
	c := personalization.NewClient(appID, key, region.US)
	return c
}

func initQuerySuggestionsClientWith(t *testing.T, appIDEnvVar, apiKeyEnvVar string) *suggestions.Client {
	appID, key := GetTestingCredentials(t, appIDEnvVar, apiKeyEnvVar)
	c := suggestions.NewClient(appID, key)
	return c
}

func GetTestingCredentials(t *testing.T, appIDEnvVar, apiKeyEnvVar string) (string, string) {
	appID := os.Getenv(appIDEnvVar)
	key := os.Getenv(apiKeyEnvVar)
	require.NotEmpty(t, appID)
	require.NotEmpty(t, key)
	return appID, key
}

func GenerateCanonicalPrefixName() string {
	var instanceName string

	if build := os.Getenv("CIRCLE_BUILD_NUM"); build != "" {
		instanceName = build
	} else {
		if user, err := user.Current(); err == nil {
			instanceName = user.Username
		} else {
			instanceName = "unknown"
		}
	}

	return fmt.Sprintf(
		"go_%s_%s",
		TodayDateTime(),
		instanceName,
	)
}

func Retry(shouldStopFunc func() bool) {
	for !shouldStopFunc() {
		time.Sleep(100 * time.Millisecond)
	}
}

func TodayDate() string {
	return time.Now().Format("2006_01_02")
}

func TodayDateTime() string {
	return time.Now().Format("2006_01_02_15_04_05")
}

func GenerateSecuredAPIKeyWithArbitraryParameters(
	t *testing.T,
	apiKey string,
	params map[string]interface{},
) string {
	h := hmac.New(sha256.New, []byte(apiKey))

	message := transport.URLEncode(params)
	_, err := h.Write([]byte(message))
	require.NoError(t, err)

	checksum := hex.EncodeToString(h.Sum(nil))
	return base64.StdEncoding.EncodeToString([]byte(checksum + message))
}

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func GenerateRandomString(length int) string {
	b := make([]rune, length)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}


<|start_filename|>algolia/recommendation/client.go<|end_filename|>
package recommendation

import (
	"github.com/algolia/algoliasearch-client-go/v3/algolia/call"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/compression"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/region"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/transport"
)

// Deprecated: use personalization.Client instead
// Client provides methods to interact with the Algolia Recommendation API.
type Client struct {
	transport *transport.Transport
}

// Deprecated: use personalization.NewClient() instead
// NewClient instantiates a new client able to interact with the Algolia
// Recommendation API.
func NewClient(appID, apiKey string, region region.Region) *Client {
	return NewClientWithConfig(
		Configuration{
			AppID:  appID,
			APIKey: apiKey,
			Region: region,
		},
	)
}

// Deprecated: use personalization.NewClientWithConfig() instead
// NewClientWithConfig instantiates a new client able to interact with the
// Recommendation API.
func NewClientWithConfig(config Configuration) *Client {
	var hosts []*transport.StatefulHost

	if config.Hosts == nil {
		hosts = defaultHosts(config.Region)
	} else {
		for _, h := range config.Hosts {
			hosts = append(hosts, transport.NewStatefulHost(h, call.IsReadWrite))
		}
	}

	return &Client{
		transport: transport.New(
			hosts,
			config.Requester,
			config.AppID,
			config.APIKey,
			config.ReadTimeout,
			config.WriteTimeout,
			config.Headers,
			config.ExtraUserAgent,
			compression.None,
		),
	}
}


<|start_filename|>algolia/personalization/client_personalization.go<|end_filename|>
package personalization

import (
	"fmt"
	"net/http"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/call"
)

func (c *Client) GetPersonalizationStrategy(opts ...interface{}) (strategy Strategy, err error) {
	path := c.pathPersonalization("")
	err = c.transport.Request(&strategy, http.MethodGet, path, nil, call.Read)
	return
}

func (c *Client) SetPersonalizationStrategy(strategy Strategy, opts ...interface{}) (res SetPersonalizationStrategyRes, err error) {
	path := c.pathPersonalization("")
	err = c.transport.Request(&strategy, http.MethodPost, path, strategy, call.Write)
	return
}

func (c *Client) pathPersonalization(format string, a ...interface{}) string {
	return "/1/strategies/personalization" + fmt.Sprintf(format, a...)
}


<|start_filename|>algolia/search/rule_consequence_params.go<|end_filename|>
package search

import (
	"encoding/json"
	"fmt"
)

type RuleParams struct {
	Query                         *RuleQuery             `json:"query,omitempty"`
	AutomaticFacetFilters         []AutomaticFacetFilter `json:"automaticFacetFilters,omitempty"`
	AutomaticOptionalFacetFilters []AutomaticFacetFilter `json:"automaticOptionalFacetFilters,omitempty"`
	RenderingContent              *RenderingContent      `json:"renderingContent,omitempty"`
	QueryParams
}

type RuleQuery struct {
	simpleQuery string
	objectQuery *RuleQueryObjectQuery
}

func NewRuleQuerySimple(query string) *RuleQuery {
	return &RuleQuery{simpleQuery: query}
}

func NewRuleQueryObject(object RuleQueryObjectQuery) *RuleQuery {
	return &RuleQuery{objectQuery: &object}
}

type RuleQueryObjectQuery struct {
	Edits []QueryEdit `json:"edits"`
}

func (q RuleQuery) Get() (string, *RuleQueryObjectQuery) {
	return q.simpleQuery, q.objectQuery
}

func (q RuleQuery) MarshalJSON() ([]byte, error) {
	if q.objectQuery == nil || len(q.objectQuery.Edits) == 0 {
		return json.Marshal(q.simpleQuery)
	}
	return json.Marshal(q.objectQuery)
}

func (q *RuleQuery) UnmarshalJSON(data []byte) error {
	if string(data) == jsonNull {
		return nil
	}

	var objectQuery RuleQueryObjectQuery
	if err := json.Unmarshal(data, &objectQuery); err == nil {
		q.objectQuery = &objectQuery
		return nil
	}

	// Kept for backward-compatibility only
	var incrementalEdit struct {
		Remove []string `json:"remove"`
	}
	if err := json.Unmarshal(data, &incrementalEdit); err == nil {
		var edits []QueryEdit
		for _, word := range incrementalEdit.Remove {
			edits = append(edits, RemoveEdit(word))
		}
		q.objectQuery = &RuleQueryObjectQuery{Edits: edits}
		return nil
	}

	var simpleQuery string
	if err := json.Unmarshal(data, &simpleQuery); err == nil {
		q.simpleQuery = simpleQuery
		return nil
	}

	return fmt.Errorf("cannot unmarshal query rule's query field")
}

type AutomaticFacetFilter struct {
	Facet       string `json:"facet"`
	Disjunctive bool   `json:"disjunctive"` // Defaults to false
	Score       int    `json:"score"`       // Defaults to 1
}

type QueryEdit struct {
	Type   QueryEditType `json:"type"`
	Delete string        `json:"delete"`
	Insert string        `json:"insert,omitempty"`
}

type QueryEditType string

const (
	Remove  QueryEditType = "remove"
	Replace QueryEditType = "replace"
)

func RemoveEdit(word string) QueryEdit {
	return QueryEdit{
		Type:   Remove,
		Delete: word,
	}
}

func ReplaceEdit(old, new string) QueryEdit {
	return QueryEdit{
		Type:   Replace,
		Delete: old,
		Insert: new,
	}
}


<|start_filename|>algolia/search/extensions_test.go<|end_filename|>
package search

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestUnmarshalRerankingExtension(t *testing.T) {

	payload := `{
		"reranking": {
			"enabled": true,
			"maxNbHits": 100,
			"endpoint": "example.org/rerankingextension"
		}
	}`

	var e Extensions
	err := json.Unmarshal([]byte(payload), &e)
	require.NoError(t, err)
	require.NotNil(t, e.Reranking)
	require.Equal(t, e.Reranking.Enabled, true)
	require.Equal(t, e.Reranking.Endpoint, "example.org/rerankingextension")
	require.Equal(t, e.Reranking.MaxNbHits, 100)

}


<|start_filename|>algolia/search/extensions.go<|end_filename|>
package search

// Index extensions container
type Extensions struct {
	// Reranking extension configuration
	Reranking *Reranking `json:"reranking,omitempty"`
}

// Reranking extension settings
type Reranking struct {
	// Whether the extension is enabled
	Enabled bool `json:"enabled,omitempty"`

	// Amount of hits to rerank
	MaxNbHits int `json:"maxNbHits,omitempty"`

	// URL to target
	Endpoint string `json:"endpoint,omitempty"`
}


<|start_filename|>cts/index/exists_test.go<|end_filename|>
package index

import (
	"testing"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/search"
	"github.com/algolia/algoliasearch-client-go/v3/cts"
	"github.com/stretchr/testify/require"
)

func TestExists(t *testing.T) {
	t.Parallel()
	_, index, _ := cts.InitSearchClient1AndIndex(t)

	ok, err := index.Exists()
	require.False(t, ok)
	require.NoError(t, err)

	res, err := index.SaveObject(map[string]string{"attribute": "value"})
	require.NoError(t, err)
	err = res.Wait()
	require.NoError(t, err)

	ok, _ = index.Exists()
	require.True(t, ok)
	require.NoError(t, err)

	ok, err = search.NewClient(index.GetAppID(), "invalid").InitIndex(index.GetName()).Exists()
	require.False(t, ok)
	require.Error(t, err)
}
