Skip to content

Commit

Permalink
fix(go): use functional options pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
millotp committed Jul 15, 2024
1 parent 74bf49a commit 463eb62
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 75 deletions.
63 changes: 44 additions & 19 deletions clients/algoliasearch-client-go/algolia/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,46 +65,71 @@ func IsNilOrEmpty(i any) bool {
}
}

type IterableOptions[T any] struct {
Aggregator func(*T, error)
Timeout func() time.Duration
IterableErr *IterableError[T]
}

type IterableOption[T any] func(*IterableOptions[T])

func WithAggregator[T any](aggregator func(*T, error)) IterableOption[T] {
return func(options *IterableOptions[T]) {
options.Aggregator = aggregator
}
}

func WithTimeout[T any](timeout func() time.Duration) IterableOption[T] {
return func(options *IterableOptions[T]) {
options.Timeout = timeout
}
}

func WithIterableError[T any](iterableErr *IterableError[T]) IterableOption[T] {
return func(options *IterableOptions[T]) {
options.IterableErr = iterableErr
}
}

type IterableError[T any] struct {
Validate func(*T, error) bool
Message func(*T, error) string
}

func CreateIterable[T any](
execute func(*T, error) (*T, error),
validate func(*T, error) bool,
aggregator func(*T, error),
timeout func() time.Duration,
iterableErr *IterableError[T],
) (*T, error) {
func CreateIterable[T any](execute func(*T, error) (*T, error), validate func(*T, error) bool, opts ...IterableOption[T]) (*T, error) {
options := IterableOptions[T]{
Aggregator: nil,
Timeout: func() time.Duration {
return 1 * time.Second
},
IterableErr: nil,
}

for _, opt := range opts {
opt(&options)
}
var executor func(*T, error) (*T, error)

executor = func(previousResponse *T, previousError error) (*T, error) {
response, responseErr := execute(previousResponse, previousError)

if aggregator != nil {
aggregator(response, responseErr)
if options.Aggregator != nil {
options.Aggregator(response, responseErr)
}

if validate(response, responseErr) {
return response, responseErr
}

if iterableErr != nil && iterableErr.Validate(response, responseErr) {
if iterableErr.Message != nil {
return nil, errs.NewWaitError(iterableErr.Message(response, responseErr))
if options.IterableErr != nil && options.IterableErr.Validate(response, responseErr) {
if options.IterableErr.Message != nil {
return nil, errs.NewWaitError(options.IterableErr.Message(response, responseErr))
}

return nil, errs.NewWaitError("an error occurred")
}

if timeout == nil {
timeout = func() time.Duration {
return 1 * time.Second
}
}

time.Sleep(timeout())
time.Sleep(options.Timeout())

return executor(response, responseErr)
}
Expand Down
33 changes: 10 additions & 23 deletions templates/go/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,18 @@ import (
"github.com/algolia/algoliasearch-client-go/v4/algolia/call"
)

type Option struct {
optionType string
name string
value string
}
type Option func(url.Values, map[string]string)

func QueryParamOption(name string, val any) Option {
return Option{
optionType: "query",
name: queryParameterToString(name),
value: queryParameterToString(val),
}
func WithQueryParam(name string, val any) Option {
return func(queryParams url.Values, _ map[string]string) {
queryParams.Set(queryParameterToString(name), queryParameterToString(val))
}
}

func HeaderParamOption(name string, val any) Option {
return Option{
optionType: "header",
name: name,
value: parameterToString(val),
}
func WithHeaderParam(name string, val any) Option {
return func(_ url.Values, headers map[string]string) {
headers[name] = parameterToString(val)
}
}

{{#operation}}
Expand Down Expand Up @@ -235,12 +227,7 @@ func (c *APIClient) {{nickname}}WithHTTPInfo(ctx context.Context, {{#hasParams}}

// optional params if any
for _, opt := range opts {
switch opt.optionType {
case "query":
queryParams.Set(opt.name, opt.value)
case "header":
headers[opt.name] = opt.value
}
opt(queryParams, headers)
}

{{#bodyParams}}
Expand Down
76 changes: 44 additions & 32 deletions templates/go/search_helpers.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -777,24 +777,26 @@ func (c *APIClient) GetSecuredApiKeyRemainingValidity(securedApiKey string) (tim
return time.Until(time.Unix(int64(ts), 0)), nil
}



// Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used under the hood, which creates a `batch` requests with at most 1000 objects in it.
func (c *APIClient) SaveObjects(indexName string, objects []map[string]any) ([]BatchResponse, error) {
return c.ChunkedBatch(indexName, objects, utils.ToPtr(ACTION_ADD_OBJECT), nil, nil)
func (c *APIClient) SaveObjects(indexName string, objects []map[string]any, opts ...ChunkedBatchOption) ([]BatchResponse, error) {
return c.ChunkedBatch(indexName, objects, ACTION_ADD_OBJECT, opts...)
}

// Helper: Deletes every records for the given objectIDs. The `chunkedBatch` helper is used under the hood, which creates a `batch` requests with at most 1000 objectIDs in it.
func (c *APIClient) DeleteObjects(indexName string, objectIDs []string) ([]BatchResponse, error) {
func (c *APIClient) DeleteObjects(indexName string, objectIDs []string, opts ...ChunkedBatchOption) ([]BatchResponse, error) {
objects := make([]map[string]any, 0, len(objectIDs))
for _, id := range objectIDs {
objects = append(objects, map[string]any{"objectID":id})
}

return c.ChunkedBatch(indexName, objects, utils.ToPtr(ACTION_DELETE_OBJECT), nil, nil)
return c.ChunkedBatch(indexName, objects, ACTION_DELETE_OBJECT, opts...)
}

// Helper: Replaces object content of all the given objects according to their respective `objectID` field. The `chunkedBatch` helper is used under the hood, which creates a `batch` requests with at most 1000 objects in it.
func (c *APIClient) PartialUpdateObjects(indexName string, objects []map[string]any, createIfNotExists bool) ([]BatchResponse, error) {
func (c *APIClient) PartialUpdateObjects(indexName string, objects []map[string]any, createIfNotExists bool, opts ...ChunkedBatchOption) ([]BatchResponse, error) {
var action Action
if createIfNotExists {
Expand All @@ -803,47 +805,57 @@ func (c *APIClient) PartialUpdateObjects(indexName string, objects []map[string]
action = ACTION_PARTIAL_UPDATE_OBJECT_NO_CREATE
}

return c.ChunkedBatch(indexName, objects, utils.ToPtr(action), nil, nil)
return c.ChunkedBatch(indexName, objects, action, opts...)
}

// ChunkedBatch chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `batch` requests.
func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, action *Action, waitForTasks *bool, batchSize *int) ([]BatchResponse, error) {
var (
defaultBatchSize = 1000
defaultAction = ACTION_ADD_OBJECT
defaultWaitForTask = false
)
type ChunkedBatchOptions struct {
WaitForTasks bool
BatchSize int
}

if batchSize == nil {
batchSize = &defaultBatchSize
}
type ChunkedBatchOption func(*ChunkedBatchOptions)

if action == nil {
action = &defaultAction
}
func WithChunkedBatchWaitForTasks(waitForTasks bool) ChunkedBatchOption {
return func(o *ChunkedBatchOptions) {
o.WaitForTasks = waitForTasks
}
}

if waitForTasks == nil {
waitForTasks = &defaultWaitForTask
}
func WithChunkedBatchBatchSize(batchSize int) ChunkedBatchOption {
return func(o *ChunkedBatchOptions) {
o.BatchSize = batchSize
}
}

// ChunkedBatch chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `batch` requests.
func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, action Action, opts ...ChunkedBatchOption) ([]BatchResponse, error) {
options := ChunkedBatchOptions{
WaitForTasks: false,
BatchSize: 1000,
}

for _, opt := range opts {
opt(&options)
}

requests := make([]BatchRequest, 0, len(objects)%*batchSize)
responses := make([]BatchResponse, 0, len(objects)%*batchSize)
requests := make([]BatchRequest, 0, len(objects)%options.BatchSize)
responses := make([]BatchResponse, 0, len(objects)%options.BatchSize)

for i, obj := range objects {
requests = append(requests, *NewBatchRequest(*action, obj))
requests = append(requests, *NewBatchRequest(action, obj))
if len(requests) == *batchSize || i == len(objects)-1 {
if len(requests) == options.BatchSize || i == len(objects)-1 {
resp, err := c.Batch(c.NewApiBatchRequest(indexName, NewBatchWriteParams(requests)))
if err != nil {
return nil, err
}

responses = append(responses, *resp)
requests = make([]BatchRequest, 0, len(objects)%*batchSize)
requests = make([]BatchRequest, 0, len(objects)%options.BatchSize)
}
}

if *waitForTasks {
if options.WaitForTasks {
for _, resp := range responses {
_, err := c.WaitForTask(indexName, resp.TaskID, nil, nil)
if err != nil {
Expand All @@ -857,17 +869,17 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act

// ReplaceAllObjects replaces all objects (records) in the given `indexName` with the given `objects`. A temporary index is created during this process in order to backup your data.
// See https://api-clients-automation.netlify.app/docs/contributing/add-new-api-client#5-helpers for implementation details.
func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any, batchSize *int) (*ReplaceAllObjectsResponse, error) {
func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any, opts ...ChunkedBatchOption) (*ReplaceAllObjectsResponse, error) {
tmpIndexName := fmt.Sprintf("%s_tmp_%d", indexName, time.Now().UnixNano())
copyResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope([]ScopeType{SCOPE_TYPE_SETTINGS, SCOPE_TYPE_RULES, SCOPE_TYPE_SYNONYMS}))))
if err != nil {
return nil, err
}

waitForTask := true
opts = append(opts, WithChunkedBatchWaitForTasks(true))

batchResp, err := c.ChunkedBatch(tmpIndexName, objects, nil, &waitForTask, batchSize)
batchResp, err := c.ChunkedBatch(tmpIndexName, objects, ACTION_ADD_OBJECT, opts...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -902,4 +914,4 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any
BatchResponses: batchResp,
MoveOperationResponse: *moveResp,
}, nil
}
}
2 changes: 1 addition & 1 deletion templates/go/tests/generateInnerParams.mustache
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{{#isNull}}{{#inClientTest}}tests.ZeroValue[{{#isNullObject}}*{{clientPrefix}}.{{/isNullObject}}{{objectName}}](){{/inClientTest}}{{^inClientTest}}nil{{/inClientTest}}{{/isNull}}{{#isString}}"{{{value}}}"{{/isString}}{{#isInteger}}{{#isHelper}}{{^required}}{{^useAnonymousKey}}utils.ToPtr({{/useAnonymousKey}}{{/required}}{{/isHelper}}{{{value}}}{{#isHelper}}{{^required}}{{^useAnonymousKey}}){{/useAnonymousKey}}{{/required}}{{/isHelper}}{{/isInteger}}{{#isLong}}{{{value}}}{{/isLong}}{{#isDouble}}{{{value}}}{{/isDouble}}{{#isBoolean}}{{{value}}}{{/isBoolean}}{{#isEnum}}{{clientPrefix}}.{{objectName}}("{{{value}}}"){{/isEnum}}{{#isArray}}
{{#isNull}}{{#inClientTest}}tests.ZeroValue[{{#isNullObject}}*{{clientPrefix}}.{{/isNullObject}}{{objectName}}](){{/inClientTest}}{{^inClientTest}}nil{{/inClientTest}}{{/isNull}}{{#isString}}"{{{value}}}"{{/isString}}{{#isInteger}}{{#isHelper}}{{^required}}{{^useAnonymousKey}}search.WithChunkedBatchBatchSize({{/useAnonymousKey}}{{/required}}{{/isHelper}}{{{value}}}{{#isHelper}}{{^required}}{{^useAnonymousKey}}){{/useAnonymousKey}}{{/required}}{{/isHelper}}{{/isInteger}}{{#isLong}}{{{value}}}{{/isLong}}{{#isDouble}}{{{value}}}{{/isDouble}}{{#isBoolean}}{{{value}}}{{/isBoolean}}{{#isEnum}}{{clientPrefix}}.{{objectName}}("{{{value}}}"){{/isEnum}}{{#isArray}}
{{> tests/arrayType}}{{^value.isEmpty}}{ {{#value}}{{#isObject}}*{{/isObject}}{{#oneOfModel}}{{^isObject}}*{{/isObject}}{{/oneOfModel}}{{> tests/generateParams}},{{/value}} }{{/value.isEmpty}}{{/isArray}}{{#isObject}}
{{clientPrefix}}.NewEmpty{{objectName}}(){{#value}}{{#isAdditionalProperty}}.SetAdditionalProperty("{{{key}}}", {{> tests/generateParams}}){{/isAdditionalProperty}}{{^isAdditionalProperty}}.Set{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/isAdditionalProperty}}{{/value}}{{/isObject}}{{#isFreeFormObject}}{{#isAnyType}}map[string]any{ {{#value}}{{#entrySet}}"{{{key}}}": "{{{value}}}",{{/entrySet}}{{/value}} }{{/isAnyType}}{{^isAnyType}}{{> tests/mapType}}{ {{#value}}"{{{key}}}": {{#oneOfModel}}{{^isObject}}*{{/isObject}}{{/oneOfModel}}{{#isObject}}*{{/isObject}}{{> tests/generateParams}},{{/value}} }{{/isAnyType}}{{/isFreeFormObject}}

0 comments on commit 463eb62

Please sign in to comment.