Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e121399
feat(client): add TransformationOptions support for ingestion transpo…
MarioAlexandruDan Apr 24, 2026
5860244
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan Apr 24, 2026
154ce9c
fix(go): wire generated files and move manual tests to correct package
MarioAlexandruDan Apr 27, 2026
776cd12
feat(go): implement TransformationOptions for ingestion transporter c…
MarioAlexandruDan Apr 27, 2026
36615ff
feat(client): add TransformationOptions support for API client config…
MarioAlexandruDan Apr 27, 2026
13bffb2
fix(go): wire generated files and move manual tests to correct package
MarioAlexandruDan Apr 27, 2026
a00567b
fix(go): remove accidentally committed test file from wrong package
MarioAlexandruDan Apr 27, 2026
5579d3f
fix(go): generate transformation_options.go from template instead of …
MarioAlexandruDan Apr 27, 2026
d31b2d2
fix(go): update generation config to include Go module and exclude ma…
MarioAlexandruDan Apr 27, 2026
304835d
fix(go): exclude manual test files from generation config
MarioAlexandruDan Apr 27, 2026
1b55c73
fix(go): avoid inline error handling in SetTransformationOptions to s…
MarioAlexandruDan Apr 27, 2026
eebc0bd
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan Apr 29, 2026
9f4f75b
feat(go): introduce ClientOptions struct for configurable ingestion t…
MarioAlexandruDan Apr 29, 2026
ba537dc
feat(go): update transformation options to use ClientOptions for conf…
MarioAlexandruDan Apr 29, 2026
f6c1dba
fix(go): correct syntax for transformation options in createClient.mu…
MarioAlexandruDan Apr 29, 2026
79b02de
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan Apr 30, 2026
750dca2
refactor(go): remove deprecated SetTransformationRegion method and re…
MarioAlexandruDan Apr 30, 2026
f8d55f4
Merge remote-tracking branch 'origin/feat/go-transformation-options' …
MarioAlexandruDan Apr 30, 2026
21d8119
fix(go): correct DefaultHeaders to DefaultHeader in transformation op…
MarioAlexandruDan Apr 30, 2026
fdf3e43
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan Apr 30, 2026
9c6d4a4
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 11, 2026
cc68c68
refactor(go): remove unused import and enhance error handling in tran…
MarioAlexandruDan May 11, 2026
91ddcb4
fix(go): ensure error handling for SetTransformationOptions in create…
MarioAlexandruDan May 11, 2026
7d5d310
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 11, 2026
fba4997
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 12, 2026
1d19eb7
refactor(go): simplify TransformationOptions structure and update rel…
MarioAlexandruDan May 12, 2026
0982e57
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 12, 2026
3f17e4a
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 13, 2026
3837ccf
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 13, 2026
5674b86
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 13, 2026
d6a3794
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 13, 2026
c96159e
feat(go): add TransformationOptions to ingestion client configuration
MarioAlexandruDan May 14, 2026
bb437ac
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 14, 2026
235f9ee
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 18, 2026
ca9fa85
Merge branch 'main' into feat/go-transformation-options
MarioAlexandruDan May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("configuration.mustache", "", "configuration.go"));
supportingFiles.add(new SupportingFile("client.mustache", "", "client.go"));

if ("search".equals(client)) {
supportingFiles.add(new SupportingFile("transformation_options.mustache", "", "transformation_options.go"));
}

Helpers.addCommonSupportingFiles(supportingFiles, "../../");

try {
Expand Down
85 changes: 61 additions & 24 deletions templates/go/client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import (

"github.com/algolia/algoliasearch-client-go/v4/algolia/call"
"github.com/algolia/algoliasearch-client-go/v4/algolia/compression"
"github.com/algolia/algoliasearch-client-go/v4/algolia/transport"
{{#isSearchClient}}"github.com/algolia/algoliasearch-client-go/v4/algolia/ingestion"
{{/isSearchClient}}"github.com/algolia/algoliasearch-client-go/v4/algolia/transport"
)

// APIClient manages communication with the {{appName}} API v{{version}}
Expand All @@ -34,8 +35,8 @@ type APIClient struct {
}

// NewClient creates a new API client with {{#hasRegionalHost}}appID, apiKey and region.{{/hasRegionalHost}}{{^hasRegionalHost}}appID and apiKey.{{/hasRegionalHost}}
func NewClient(appID, apiKey string{{#hasRegionalHost}}, region Region{{/hasRegionalHost}}) (*APIClient, error) {
return NewClientWithConfig({{#lambda.titlecase}}{{#lambda.camelcase}}{{client}}{{/lambda.camelcase}}{{/lambda.titlecase}}Configuration{
func NewClient(appID, apiKey string{{#hasRegionalHost}}, region Region{{/hasRegionalHost}}{{#isSearchClient}}, opts ...ClientOption{{/isSearchClient}}) (*APIClient, error) {
cfg := {{#lambda.titlecase}}{{#lambda.camelcase}}{{client}}{{/lambda.camelcase}}{{/lambda.titlecase}}Configuration{
Configuration: transport.Configuration{
AppID: appID,
ApiKey: apiKey,
Expand All @@ -44,7 +45,13 @@ return NewClientWithConfig({{#lambda.titlecase}}{{#lambda.camelcase}}{{client}}{
Requester: transport.NewDefaultRequester(nil),
},{{#hasRegionalHost}}
Region: region,{{/hasRegionalHost}}
})
}
{{#isSearchClient}}
for _, opt := range opts {
opt(&cfg)
}
{{/isSearchClient}}
return NewClientWithConfig(cfg)
}

// NewClientWithConfig creates a new API client with the given configuration to fully customize the client behaviour.
Expand Down Expand Up @@ -83,26 +90,11 @@ func NewClientWithConfig(cfg {{#lambda.titlecase}}{{#lambda.camelcase}}{{client}
}

{{#isSearchClient}}
if cfg.Transformation != nil && cfg.Transformation.Region != "" {
ingestionConfig := ingestion.IngestionConfiguration{
Configuration: transport.Configuration{
AppID: cfg.AppID,
ApiKey: cfg.ApiKey,
},
Region: cfg.Transformation.Region,
if cfg.TransformationOptions != nil {
if err := apiClient.SetTransformationOptions(*cfg.TransformationOptions); err != nil {
return nil, err
}

if len(cfg.Hosts) > 0 {
ingestionConfig.Hosts = cfg.Hosts
}

ingestionClient, err := ingestion.NewClientWithConfig(ingestionConfig)
if err != nil {
return nil, err //nolint:wrapcheck
}

apiClient.ingestionTransporter = ingestionClient
}
}
{{/isSearchClient}}

return &apiClient, nil
Expand Down Expand Up @@ -165,12 +157,57 @@ func (c *APIClient) SetClientApiKey(apiKey string) error {
if c.cfg == nil {
return errors.New("client config is not set")
}

c.cfg.ApiKey = apiKey

return nil
}

{{#isSearchClient}}
// SetTransformationOptions sets (or replaces) the ingestion transporter used by *WithTransformation helpers.
// The transporter uses Ingestion API defaults (25s timeouts); only fields set in opts override those defaults.
// See https://www.algolia.com/doc/libraries/sdk/methods/ingestion
func (c *APIClient) SetTransformationOptions(opts TransformationOptions) error {
err := opts.validate()
if err != nil {
return err
}
ingestionConfig := ingestion.IngestionConfiguration{
Configuration: transport.Configuration{
AppID: c.cfg.AppID,
ApiKey: c.cfg.ApiKey,
},
Region: opts.Region,
}
if opts.ReadTimeout != 0 {
ingestionConfig.ReadTimeout = opts.ReadTimeout
}
if opts.WriteTimeout != 0 {
ingestionConfig.WriteTimeout = opts.WriteTimeout
}
if opts.ConnectTimeout != 0 {
ingestionConfig.ConnectTimeout = opts.ConnectTimeout
}
if opts.Compression != 0 {
ingestionConfig.Compression = opts.Compression
}
if len(opts.Hosts) > 0 {
ingestionConfig.Hosts = opts.Hosts
}
if opts.DefaultHeader != nil {
ingestionConfig.DefaultHeader = opts.DefaultHeader
}
ingestionClient, err := ingestion.NewClientWithConfig(ingestionConfig)
if err != nil {
return err //nolint:wrapcheck
}
c.ingestionTransporter = ingestionClient
c.cfg.TransformationOptions = &opts
return nil
Comment thread
MarioAlexandruDan marked this conversation as resolved.
}

{{/isSearchClient}}

// callAPI do the request.
func (c *APIClient) callAPI(request *http.Request, useReadTransporter bool, requestConfiguration transport.RequestConfiguration) (*http.Response, []byte, error) {
callKind := call.Write
Expand Down
18 changes: 4 additions & 14 deletions templates/go/configuration.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,12 @@ const (
// {{#lambda.titlecase}}{{#lambda.camelcase}}{{client}}{{/lambda.camelcase}}{{/lambda.titlecase}}Configuration stores the configuration of the API client.
type {{#lambda.titlecase}}{{#lambda.camelcase}}{{client}}{{/lambda.camelcase}}{{/lambda.titlecase}}Configuration struct {
transport.Configuration

{{#hasRegionalHost}}Region Region{{/hasRegionalHost}}
{{#isSearchClient}}
Transformation *TransformationConfiguration
// TransformationOptions configures the ingestion transporter used by *WithTransformation helpers.
// When set, the transporter is eagerly created using Ingestion API defaults.
TransformationOptions *TransformationOptions
{{/isSearchClient}}
}

{{#isSearchClient}}
type TransformationConfiguration struct {
Region ingestion.Region
}

// SetTransformationRegion sets the region of the current algolia application to the configuration, this is required to be called if you wish to leverage the transformation pipeline (via the *WithTransformation methods).
func (s *SearchConfiguration) SetTransformationRegion(region ingestion.Region) {
s.Transformation = &TransformationConfiguration{
Region: region,
}
}
{{/isSearchClient}}
12 changes: 6 additions & 6 deletions templates/go/search_helpers.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act
}

/*
ReplaceAllObjectsWithTransformation is similar to the `replaceAllObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. The `region` must have been passed to the client instantiation method.
ReplaceAllObjectsWithTransformation is similar to the `replaceAllObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. TransformationOptions must have been passed to the client constructor or set via SetTransformationOptions.
See https://api-clients-automation.netlify.app/docs/custom-helpers/#replaceallobjects for implementation details.

@param indexName string - the index name to replace objects into.
Expand All @@ -611,7 +611,7 @@ See https://api-clients-automation.netlify.app/docs/custom-helpers/#replaceallob
*/
func (c *APIClient) ReplaceAllObjectsWithTransformation(indexName string, objects []map[string]any, opts ...ReplaceAllObjectsOption) (*ReplaceAllObjectsWithTransformationResponse, error) {
if c.ingestionTransporter == nil {
return nil, reportError("`region` must be provided at client instantiation before calling this method.")
return nil, reportError("TransformationOptions must be set in the client config before calling this method. It defaults to the Ingestion API defaults. See https://www.algolia.com/doc/libraries/sdk/methods/ingestion")
}

tmpIndexName := fmt.Sprintf("%s_tmp_%d", indexName, time.Now().UnixNano())
Expand Down Expand Up @@ -789,7 +789,7 @@ func (c *APIClient) IndexExists(indexName string) (bool, error) {
}

/*
Helper: Similar to the `SaveObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. The `region` must've been passed to the client's config at instantiation.
Helper: Similar to the `SaveObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. TransformationOptions must have been passed to the client constructor or set via SetTransformationOptions.

@param indexName string - the index name to save objects into.
@param objects []map[string]any - List of objects to save.
Expand All @@ -799,14 +799,14 @@ Helper: Similar to the `SaveObjects` method but requires a Push connector (https
*/
func (c *APIClient) SaveObjectsWithTransformation(indexName string, objects []map[string]any, opts ...ChunkedBatchOption) ([]ingestion.WatchResponse, error) {
if c.ingestionTransporter == nil {
return nil, reportError("`region` must be provided at client instantiation before calling this method.")
return nil, reportError("TransformationOptions must be set in the client config before calling this method. It defaults to the Ingestion API defaults. See https://www.algolia.com/doc/libraries/sdk/methods/ingestion")
}

return c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(ACTION_ADD_OBJECT), nil, toIngestionChunkedBatchOptions(opts)...) //nolint:wrapcheck
}

/*
Helper: Similar to the `PartialUpdateObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. The `region` must've been passed to the client instantiation method.
Helper: Similar to the `PartialUpdateObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. TransformationOptions must have been passed to the client constructor or set via SetTransformationOptions.

@param indexName string - the index name to save objects into.
@param objects []map[string]any - List of objects to save.
Expand All @@ -816,7 +816,7 @@ Helper: Similar to the `PartialUpdateObjects` method but requires a Push connect
*/
func (c *APIClient) PartialUpdateObjectsWithTransformation(indexName string, objects []map[string]any, opts ...PartialUpdateObjectsOption) ([]ingestion.WatchResponse, error) {
if c.ingestionTransporter == nil {
return nil, reportError("`region` must be provided at client instantiation before calling this method.")
return nil, reportError("TransformationOptions must be set in the client config before calling this method. It defaults to the Ingestion API defaults. See https://www.algolia.com/doc/libraries/sdk/methods/ingestion")
}

conf := config{
Expand Down
13 changes: 9 additions & 4 deletions templates/go/tests/client/createClient.mustache
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{{#hasTransformationOptions}}
transformationOptions := search.TransformationOptions{Region: "{{{transformationRegion}}}"{{#hasTransformationCustomHosts}}, Hosts: []transport.StatefulHost{ {{#transformationCustomHosts}}transport.NewStatefulHost("http", tests.GetLocalhost() + ":{{port}}", call.IsReadWrite),{{/transformationCustomHosts}} }{{/hasTransformationCustomHosts}}}
{{/hasTransformationOptions}}
cfg = {{clientPrefix}}.{{clientName}}Configuration{
Configuration: transport.Configuration{
AppID: "{{parametersWithDataTypeMap.appId.value}}",
Expand All @@ -13,8 +16,10 @@ cfg = {{clientPrefix}}.{{clientName}}Configuration{
{{/gzipEncoding}}
},{{#hasRegionalHost}}{{#parametersWithDataTypeMap.region.value}}
Region: {{clientPrefix}}.Region("{{parametersWithDataTypeMap.region.value}}"),{{/parametersWithDataTypeMap.region.value}}{{/hasRegionalHost}}
{{#hasTransformationRegion}}
Transformation: &search.TransformationConfiguration{ Region: "{{{transformationRegion}}}" },
{{/hasTransformationRegion}}
}
client, err = {{clientPrefix}}.NewClientWithConfig(cfg)
client, err = {{clientPrefix}}.NewClientWithConfig(cfg)
{{#hasTransformationOptions}}
require.NoError(t, err)
err = client.SetTransformationOptions(transformationOptions)
require.NoError(t, err)
{{/hasTransformationOptions}}
43 changes: 43 additions & 0 deletions templates/go/transformation_options.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// {{{generationBanner}}}
package {{packageName}}

import (
"errors"
"time"

"github.com/algolia/algoliasearch-client-go/v4/algolia/compression"
"github.com/algolia/algoliasearch-client-go/v4/algolia/ingestion"
"github.com/algolia/algoliasearch-client-go/v4/algolia/transport"
)

// TransformationOptions configures the ingestion transporter used by *WithTransformation helpers.
// When passed to NewClient or set via SetTransformationOptions, the transporter is eagerly created
// using Ingestion API defaults (25s timeouts, no compression). Only fields explicitly set here override
// those defaults. The parent search config is NOT forwarded to the ingestion transporter.
// See https://www.algolia.com/doc/libraries/sdk/methods/ingestion
type TransformationOptions struct {
Region ingestion.Region // required
ReadTimeout time.Duration
WriteTimeout time.Duration
ConnectTimeout time.Duration
Compression compression.Compression
Hosts []transport.StatefulHost
DefaultHeader map[string]string
}

func (o TransformationOptions) validate() error {
if o.Region == "" {
return errors.New("Region is required in TransformationOptions. See https://www.algolia.com/doc/libraries/sdk/methods/ingestion")
}
return nil
}

// ClientOption configures the search client at construction time.
type ClientOption func(*SearchConfiguration)

// WithTransformationOptions returns a ClientOption that configures the ingestion transporter.
func WithTransformationOptions(opts TransformationOptions) ClientOption {
return func(cfg *SearchConfiguration) {
cfg.TransformationOptions = &opts
}
}
82 changes: 82 additions & 0 deletions tests/output/go/tests/manual/transformation_options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package manual

import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)

func TestTransformationOptionsRegionRequired(t *testing.T) {
client, err := search.NewClient("appID", "apiKey")
require.NoError(t, err)
err = client.SetTransformationOptions(search.TransformationOptions{})
require.ErrorContains(t, err, "Region is required in TransformationOptions")
}

func TestTransformationOptionsRegionRequiredAtConstruction(t *testing.T) {
_, err := search.NewClient("appID", "apiKey",
search.WithTransformationOptions(search.TransformationOptions{}),
)
require.ErrorContains(t, err, "Region is required in TransformationOptions")
}

func TestTransformationOptionsRegionOnlyHasZeroOverrides(t *testing.T) {
opts := search.TransformationOptions{Region: "us"}
require.Equal(t, "us", string(opts.Region))
require.Zero(t, opts.ReadTimeout)
}

func TestTransformationOptionsWithOverrides(t *testing.T) {
opts := search.TransformationOptions{Region: "eu", ReadTimeout: 50 * time.Second}
require.Equal(t, "eu", string(opts.Region))
require.Equal(t, 50*time.Second, opts.ReadTimeout)
}

func TestTransformationOptionsWithRegionOnly(t *testing.T) {
client, err := search.NewClient("appID", "apiKey")
require.NoError(t, err)
err = client.SetTransformationOptions(search.TransformationOptions{Region: "us"})
require.NoError(t, err)
}

func TestIngestionTransporterNilBeforeSet(t *testing.T) {
client, err := search.NewClient("appID", "apiKey")
require.NoError(t, err)
_, err = client.SaveObjectsWithTransformation("index", []map[string]any{{"objectID": "1"}})
require.ErrorContains(t, err, "TransformationOptions must be set")
}

func TestSetTransformationOptionsCreatesTransporter(t *testing.T) {
client, err := search.NewClient("appID", "apiKey")
require.NoError(t, err)
_, err = client.SaveObjectsWithTransformation("index", []map[string]any{{"objectID": "1"}})
require.ErrorContains(t, err, "TransformationOptions must be set")
require.NoError(t, client.SetTransformationOptions(search.TransformationOptions{Region: "us"}))
_, err = client.SaveObjectsWithTransformation("index", []map[string]any{{"objectID": "1"}})
require.Error(t, err)
require.NotContains(t, err.Error(), "TransformationOptions must be set")
Comment thread
MarioAlexandruDan marked this conversation as resolved.
}

func TestWithTransformationOptionsFunctionalOption(t *testing.T) {
client, err := search.NewClient("appID", "apiKey",
search.WithTransformationOptions(search.TransformationOptions{
Region: "us",
ReadTimeout: 50 * time.Second,
}),
)
require.NoError(t, err)
_ = client
}

func TestSetTransformationOptionsReplacesTransporter(t *testing.T) {
client, err := search.NewClient("appID", "apiKey",
search.WithTransformationOptions(search.TransformationOptions{Region: "us"}),
)
require.NoError(t, err)
// Replace with a different region — must not error
err = client.SetTransformationOptions(search.TransformationOptions{Region: "eu"})
require.NoError(t, err)
}
Loading