From 56a2513367a711818ee361d554fd0ddd4510e32a Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Fri, 9 May 2025 09:56:37 +0200 Subject: [PATCH 01/14] feat(clients): flatten body parameters --- clients/algoliasearch-client-go/README.md | 19 +- .../algolia/codegen/AlgoliaGoGenerator.java | 127 ++++++- .../codegen/AlgoliaJavascriptGenerator.java | 11 +- .../cts/tests/ParametersWithDataType.java | 43 ++- .../algolia/codegen/cts/tests/Snippet.java | 2 + .../codegen/cts/tests/TestsClient.java | 1 + .../codegen/cts/tests/TestsGenerator.java | 30 +- .../codegen/cts/tests/TestsRequest.java | 1 + playground/go/analytics.go | 4 +- playground/go/ingestion.go | 4 +- playground/go/insights.go | 8 +- playground/go/personalization.go | 5 +- playground/go/query-suggestions.go | 3 +- playground/go/recommend.go | 26 +- playground/go/search.go | 31 +- specs/search/paths/search/browse.yml | 29 +- specs/search/paths/search/search.yml | 4 +- templates/go/api.mustache | 291 +++++++++------- templates/go/client.mustache | 3 +- templates/go/configuration.mustache | 3 +- templates/go/model.mustache | 3 +- templates/go/operation_description.mustache | 23 -- templates/go/param_name.mustache | 1 + templates/go/search_helpers.mustache | 325 +++++++----------- templates/go/snippets/method.mustache | 3 +- templates/go/tests/client/benchmark.mustache | 3 +- templates/go/tests/client/client.mustache | 3 +- templates/go/tests/e2e/e2e.mustache | 3 +- templates/go/tests/method.mustache | 3 +- 29 files changed, 547 insertions(+), 465 deletions(-) delete mode 100644 templates/go/operation_description.mustache create mode 100644 templates/go/param_name.mustache diff --git a/clients/algoliasearch-client-go/README.md b/clients/algoliasearch-client-go/README.md index 89cb9f895cd..7d301253739 100644 --- a/clients/algoliasearch-client-go/README.md +++ b/clients/algoliasearch-client-go/README.md @@ -46,30 +46,25 @@ import "github.com/algolia/algoliasearch-client-go/v4/algolia/search" client, err := search.NewClient("YOUR_APP_ID", "YOUR_API_KEY") // Add a new record to your Algolia index -response, err := client.SaveObject(client.NewApiSaveObjectRequest( - "", map[string]any{"objectID": "id", "test": "val"}, -)) +saveResponse, err := client.SaveObject("", map[string]any{"objectID": "id", "test": "val"}) if err != nil { // handle the eventual error panic(err) } // use the model directly -print(response) +print(saveResponse) // Poll the task status to know when it has been indexed -taskResponse, err := searchClient.WaitForTask("", response.TaskID, nil, nil, nil) +_, err = client.WaitForTask("", saveResponse.TaskID) if err != nil { panic(err) } // Fetch search results, with typo tolerance -response, err := client.Search(client.NewApiSearchRequest( - - search.NewEmptySearchMethodParams().SetRequests( - []search.SearchQuery{*search.SearchForHitsAsSearchQuery( - search.NewEmptySearchForHits().SetIndexName("").SetQuery("").SetHitsPerPage(50))}), -)) +response, err := client.Search([]search.SearchQuery{ + *search.SearchForHitsAsSearchQuery(search.NewSearchForHits("").SetQuery("").SetHitsPerPage(50)), +}, nil) if err != nil { // handle the eventual error panic(err) @@ -77,6 +72,8 @@ if err != nil { // use the model directly print(response) + +return 0 ``` For full documentation, visit the **[Algolia Go API Client](https://www.algolia.com/doc/libraries/go/)**. diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index 788cba5aba3..c55ed5dbf93 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.models.servers.Server; import java.io.File; import java.util.*; +import java.util.stream.Collectors; import org.openapitools.codegen.*; import org.openapitools.codegen.languages.GoClientCodegen; import org.openapitools.codegen.model.ModelMap; @@ -78,6 +79,10 @@ public void processOpenAPI(OpenAPI openAPI) { super.processOpenAPI(openAPI); Helpers.generateServers(super.fromServers(openAPI.getServers()), additionalProperties); Timeouts.enrichBundle(openAPI, additionalProperties); + additionalProperties.put( + "appDescription", + Arrays.stream(openAPI.getInfo().getDescription().split("\n")).map(line -> "// " + line).collect(Collectors.joining("\n")).trim() + ); } @Override @@ -140,7 +145,127 @@ public Map postProcessAllModels(Map objs) @Override public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List models) { OperationsMap operations = super.postProcessOperationsWithModels(objs, models); - ModelPruner.removeOrphanModelFiles(this, operations, models); + + // Flatten body params to remove the wrapping object + for (CodegenOperation ope : operations.getOperations().getOperation()) { + // clean up the description + String[] lines = ope.unescapedNotes.split("\n"); + ope.notes = (lines[0] + "\n" + Arrays.stream(lines).skip(1).map(line -> "// " + line).collect(Collectors.joining("\n"))).trim(); + + for (CodegenParameter param : ope.optionalParams) { + param.nameInPascalCase = Helpers.capitalize(param.baseName); + } + + CodegenParameter bodyParam = ope.bodyParam; + if (bodyParam == null) { + continue; + } + bodyParam.nameInPascalCase = Helpers.capitalize(bodyParam.baseName); + if (!bodyParam.isModel) { + continue; + } + + // check for colision with other params + boolean hasCollision = false; + for (CodegenProperty prop : bodyParam.getVars()) { + for (CodegenParameter param : ope.allParams) { + if (param.paramName.equals(prop.baseName)) { + hasCollision = true; + break; + } + } + } + if (hasCollision) { + System.out.println("Operation " + ope.operationId + " has a body param with the same name as another param, skipping flattening"); + continue; + } + + if (ope.operationId.equals("Browse")) { + System.out.println( + ope.allParams.size() + + " params " + + ope.requiredParams.size() + + " required params " + + ope.optionalParams.size() + + " optional params" + ); + } + + bodyParam.vendorExtensions.put("x-flat-body", bodyParam.getVars().size() > 0); + + if (bodyParam.getVars().size() > 0) { + ope.allParams.removeIf(param -> param.isBodyParam); + ope.requiredParams.removeIf(param -> param.isBodyParam); + ope.optionalParams.removeIf(param -> param.isBodyParam); + } + + for (CodegenProperty prop : bodyParam.getVars()) { + // there is no easy way to convert a prop to a param, we need to copy all the fields + CodegenParameter param = new CodegenParameter(); + + prop.nameInLowerCase = toParamName(prop.baseName); + param.nameInPascalCase = Helpers.capitalize(prop.baseName); + param.paramName = toParamName(prop.baseName); + param.baseName = prop.baseName; + param.baseType = prop.baseType; + param.dataType = prop.dataType; + param.datatypeWithEnum = prop.datatypeWithEnum; + param.description = prop.description; + param.example = prop.example; + param.isModel = prop.isModel; + param.isArray = prop.isArray; + param.isContainer = prop.isContainer; + param.isMap = prop.isMap; + param.isEnum = prop.isEnum; + param.isEnumRef = prop.isEnumRef; + param.isPrimitiveType = prop.isPrimitiveType; + param.isString = prop.isString; + param.isNumeric = prop.isNumeric; + param.isBoolean = prop.isBoolean; + param.isDate = prop.isDate; + param.isDateTime = prop.isDateTime; + param.isFreeFormObject = prop.isFreeFormObject; + param.isNullable = prop.isNullable; + param.jsonSchema = prop.jsonSchema; + param.required = prop.required; + param.vendorExtensions = prop.vendorExtensions; + param.allowableValues = prop.allowableValues; + + if (prop.required) { + ope.requiredParams.add(param); + ope.hasRequiredParams = true; + } else { + ope.optionalParams.add(param); + ope.hasOptionalParams = true; + } + ope.allParams.add(param); + } + + System.out.println( + ope.operationId + + " has " + + ope.requiredParams.size() + + " required params and " + + ope.optionalParams.size() + + " optional params " + + bodyParam.getVars().size() + + " body params " + ); + + // If the optional param struct only has 1 param, we can remove the wrapper + if (ope.optionalParams.size() == 1) { + CodegenParameter param = ope.optionalParams.get(0); + + // move it to required, it's easier to handle im mustache + ope.hasOptionalParams = false; + ope.optionalParams.clear(); + + ope.hasRequiredParams = true; + ope.requiredParams.add(param); + } + } + + ModelPruner.removeOrphans(this, operations, models); Helpers.removeHelpers(operations); GenericPropagator.propagateGenericsToOperations(operations, models); return operations; diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java index 700c106f0f5..2e5b9ff8d79 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java @@ -236,20 +236,13 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List parameters, Map parameters, Map param : parameters.entrySet()) { - CodegenParameter specParam = null; + IJsonSchemaValidationProperties specParam = null; if (operation != null) { for (CodegenParameter sp : operation.allParams) { if (sp.paramName.equals(param.getKey())) { @@ -83,9 +83,42 @@ public void enhanceParameters(Map parameters, Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); - parametersWithDataType.add(paramWithType); - parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + // for go, we flatten the body params + if ( + language.equals("go") && + specParam != null && + ((CodegenParameter) specParam).isBodyParam && + operation != null && + operation.bodyParam != null && + operation.bodyParam.isModel && + operation.bodyParam.required + ) { + // check for colision with other params + boolean hasCollision = false; + for (CodegenProperty prop : operation.bodyParam.getVars()) { + for (CodegenParameter otherParam : operation.allParams) { + if (otherParam.paramName.equals(prop.baseName)) { + hasCollision = true; + break; + } + } + } + if (!hasCollision) { + // flatten the body params by skipping one level + Map bodyParams = (Map) param.getValue(); + for (CodegenProperty prop : operation.bodyParam.getVars()) { + Object nestedParam = bodyParams.get(prop.baseName); + + Map paramWithType = traverseParams(prop.baseName, nestedParam, prop, "", 0, false); + parametersWithDataType.add(paramWithType); + parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + } + } + } else { + Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); + parametersWithDataType.add(paramWithType); + parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + } } } } else { diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java index c4be9988341..d1d9078fbdf 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java @@ -86,6 +86,8 @@ public void addMethodCall(Map context, ParametersWithDataType pa } } + TestsGenerator.setOptionalParameters(ope, context); + paramsType.enhanceParameters(parameters, context, ope); } catch (CTSException e) { e.setTestName((String) context.get("testName")); diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java index 6bb67dfd7eb..230dce51580 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java @@ -158,6 +158,7 @@ public void run(Map models, Map // default to true because most api calls are asynchronous testOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); + setOptionalParameters(ope, stepOut); addRequestOptions(paramsType, step.requestOptions, stepOut); methodCount++; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index eab9caf43a3..361ac8e3762 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -11,9 +11,7 @@ import java.nio.file.Paths; import java.util.*; import org.apache.commons.lang3.ArrayUtils; -import org.openapitools.codegen.CodegenModel; -import org.openapitools.codegen.CodegenOperation; -import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.*; public abstract class TestsGenerator { @@ -130,4 +128,30 @@ protected void addRequestOptions(ParametersWithDataType paramsType, RequestOptio output.put("requestOptions", requestOptions); } } + + public static void setOptionalParameters(CodegenOperation ope, Map test) { + long bodyPropsOptional = 0; + if (ope.bodyParam != null) { + if (ope.bodyParam.isModel && ope.bodyParam.required) { + // check for colision with other params + boolean hasCollision = false; + for (CodegenProperty prop : ope.bodyParam.getVars()) { + for (CodegenParameter param : ope.allParams) { + if (param.paramName.equals(prop.baseName)) { + hasCollision = true; + break; + } + } + } + + if (!hasCollision) { + bodyPropsOptional = ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); + } + } + } + + // hasOptionalWrapper if there is more that one optional param, after the body has been + // flattened, only relevant for go + test.put("hasOptionalWrapper", ope.optionalParams.size() + bodyPropsOptional > 1); + } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java index cbb20c25b6d..15fcd41fccd 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java @@ -171,6 +171,7 @@ public void run(Map models, Map test.put("hasParams", ope.getHasParams()); test.put("isHelper", isHelper); + setOptionalParameters(ope, test); addRequestOptions(paramsType, req.requestOptions, test); // Determines whether the endpoint is expected to return a response payload deserialized diff --git a/playground/go/analytics.go b/playground/go/analytics.go index 4f18dc73272..d6c78605049 100644 --- a/playground/go/analytics.go +++ b/playground/go/analytics.go @@ -13,9 +13,7 @@ func testAnalytics(appID, apiKey string) int { panic(err) } - getTopFilterForAttributeResponse, err := analyticsClient.GetTopFilterForAttribute( - analyticsClient.NewApiGetTopFilterForAttributeRequest("myAttribute1,myAttribute2", indexName), - ) + getTopFilterForAttributeResponse, err := analyticsClient.GetTopFilterForAttribute("myAttribute1,myAttribute2", indexName, nil) if err != nil { fmt.Printf("request error with GetTopFilterForAttribute: %v\n", err) return 1 diff --git a/playground/go/ingestion.go b/playground/go/ingestion.go index b72371c0457..d858e804f7b 100644 --- a/playground/go/ingestion.go +++ b/playground/go/ingestion.go @@ -13,14 +13,14 @@ func testIngestion(appID, apiKey string) int { } // another example to generate payload for a request. - _, payload, err := ingestionClient.ListTasksWithHTTPInfo(ingestionClient.NewApiListTasksRequest()) + res, err := ingestionClient.ListTasks(nil) if err != nil { fmt.Printf("request error: %v\n", err) return 1 } - fmt.Println(string(payload)) + fmt.Println(res) return 0 } diff --git a/playground/go/insights.go b/playground/go/insights.go index 6dcebd25bb8..0dca26438a8 100644 --- a/playground/go/insights.go +++ b/playground/go/insights.go @@ -12,17 +12,15 @@ func testInsights(appID, apiKey string) int { panic(err) } - events := insights.NewInsightsEvents([]insights.EventsItems{ + events := []insights.EventsItems{ *insights.ClickedObjectIDsAsEventsItems(insights.NewClickedObjectIDs("myEvent", insights.CLICK_EVENT_CLICK, "test_index", []string{"myObjectID"}, "myToken", insights.WithClickedObjectIDsTimestamp(1234567890))), - }) - eventsResponse, err := insightsClient.PushEvents( - insightsClient.NewApiPushEventsRequest(events), - ) + } + eventsResponse, err := insightsClient.PushEvents(events) if err != nil { fmt.Printf("request error with PushEvents: %v\n", err) return 1 diff --git a/playground/go/personalization.go b/playground/go/personalization.go index 5fa84b4b73d..bdffed33577 100644 --- a/playground/go/personalization.go +++ b/playground/go/personalization.go @@ -17,10 +17,7 @@ func testPersonalization(appID, apiKey string) int { defer cancel() // it will fail expectedly because of the very short timeout to showcase the context usage. - deleteUserProfileResponse, err := personalizationClient.DeleteUserProfile( - personalizationClient.NewApiDeleteUserProfileRequest("userToken"), - personalization.WithContext(ctx), - ) + deleteUserProfileResponse, err := personalizationClient.DeleteUserProfile("userToken", personalization.WithContext(ctx)) if err != nil { fmt.Printf("request error with DeleteUserProfile: %v\n", err) return 1 diff --git a/playground/go/query-suggestions.go b/playground/go/query-suggestions.go index 262ea7b0362..b529c4c1e71 100644 --- a/playground/go/query-suggestions.go +++ b/playground/go/query-suggestions.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/algolia/algoliasearch-client-go/v4/algolia/query-suggestions" + suggestions "github.com/algolia/algoliasearch-client-go/v4/algolia/query-suggestions" ) func testQuerySuggestions(appID, apiKey string) int { @@ -12,7 +12,6 @@ func testQuerySuggestions(appID, apiKey string) int { panic(err) } - // if there is no params for the requests, we don't need to give empty request instance such as `suggestionsClient.NewApiGetAllConfigsRequest()`. querySuggestionsIndex, err := suggestionsClient.GetAllConfigs() if err != nil { fmt.Printf("request error with GetAllConfigs: %v\n", err) diff --git a/playground/go/recommend.go b/playground/go/recommend.go index 75d7a8c6bf6..4085332ba13 100644 --- a/playground/go/recommend.go +++ b/playground/go/recommend.go @@ -13,27 +13,21 @@ func testRecommend(appID, apiKey string) int { } /* - recommend.NewGetRecommendationsParams([]recommend.RecommendationsRequest{ + params := []recommend.RecommendationsRequest{ recommend.RecommendationRequestAsRecommendationsRequest(recommend.NewRecommendationRequest(recommend.RECOMMENDATIONMODELS_BOUGHT_TOGETHER, "test_query", "test_index", 0)), - }) + } */ // alternative way to create the payloads, a similar approach can be used with any of the other clients - params := &recommend.GetRecommendationsParams{ - Requests: []recommend.RecommendationsRequest{ - { - BoughtTogetherQuery: &recommend.BoughtTogetherQuery{ - Model: recommend.FBT_MODEL_BOUGHT_TOGETHER, - ObjectID: "test_query", - IndexName: "test_index", - Threshold: 0, - }, - }, + params := []recommend.RecommendationsRequest{{ + BoughtTogetherQuery: &recommend.BoughtTogetherQuery{ + Model: recommend.FBT_MODEL_BOUGHT_TOGETHER, + ObjectID: "test_query", + IndexName: "test_index", + Threshold: 0, }, - } + }} - searchResponse, err := recommendClient.GetRecommendations( - recommendClient.NewApiGetRecommendationsRequest(params), - ) + searchResponse, err := recommendClient.GetRecommendations(params) if err != nil { fmt.Printf("request error with SearchSingleIndex: %v\n", err) return 1 diff --git a/playground/go/search.go b/playground/go/search.go index e1989e3464e..680b8c66b93 100644 --- a/playground/go/search.go +++ b/playground/go/search.go @@ -24,28 +24,15 @@ func testSearch(appID, apiKey string) int { panic(err) } - // config := search.SearchConfiguration{ - // Configuration: transport.Configuration{ - // AppID: appID, - // ApiKey: apiKey, - // }, - // } - // - // config.WithTransformation("eu") - // - // fmt.Println(config.Transformation.Region) - // - // searchClient, err := search.NewClientWithConfig(config) - // if err != nil { - // panic(err) - // } - // - // watchResponse, err := searchClient.SaveObjectsWithTransformation("foo", []map[string]any{{"objectID": "foobarbaz"}}, search.WithWaitForTasks(true)) - // if err != nil { - // panic(err) - // } - // - // fmt.Printf("%#v\n", watchResponse) + // old way + //searchClient.Search(searchClient.NewApiSearchRequest(search.NewSearchMethodParams([]search.SearchQuery{ + // *search.SearchForHitsAsSearchQuery(search.NewSearchForHits("indexName", search.WithSearchForHitsQuery("foo"))), + //}))) + + // new way + //searchClient.Search([]search.SearchQuery{ + // search.NewSearchForHits("indexName").WithQuery("foo"), + //}, nil) /* response, err := searchClient.AddOrUpdateObject( diff --git a/specs/search/paths/search/browse.yml b/specs/search/paths/search/browse.yml index ddafd05d371..8c185f36926 100644 --- a/specs/search/paths/search/browse.yml +++ b/specs/search/paths/search/browse.yml @@ -13,23 +13,22 @@ post: browsing _just_ returns matching records. This can be useful if you want to export your indices. - - The Analytics API doesn't collect data when using `browse`. - - Records are ranked by attributes and custom ranking. - - There's no ranking for: typo-tolerance, number of matched words, proximity, geo distance. + - The Analytics API doesn't collect data when using `browse`. + - Records are ranked by attributes and custom ranking. + - There's no ranking for: typo-tolerance, number of matched words, proximity, geo distance. Browse requests automatically apply these settings: - - - `advancedSyntax`: `false` - - `attributesToHighlight`: `[]` - - `attributesToSnippet`: `[]` - - `distinct`: `false` - - `enablePersonalization`: `false` - - `enableRules`: `false` - - `facets`: `[]` - - `getRankingInfo`: `false` - - `ignorePlurals`: `false` - - `optionalFilters`: `[]` - - `typoTolerance`: `true` or `false` (`min` and `strict` evaluate to `true`) + - `advancedSyntax`: `false` + - `attributesToHighlight`: `[]` + - `attributesToSnippet`: `[]` + - `distinct`: `false` + - `enablePersonalization`: `false` + - `enableRules`: `false` + - `facets`: `[]` + - `getRankingInfo`: `false` + - `ignorePlurals`: `false` + - `optionalFilters`: `[]` + - `typoTolerance`: `true` or `false` (`min` and `strict` evaluate to `true`) If you send these parameters with your browse requests, they'll be ignored. parameters: diff --git a/specs/search/paths/search/search.yml b/specs/search/paths/search/search.yml index 688232c4948..a817870da4c 100644 --- a/specs/search/paths/search/search.yml +++ b/specs/search/paths/search/search.yml @@ -13,8 +13,8 @@ post: This can be useful in these cases: - - Different indices for different purposes, such as, one index for products, another one for marketing content. - - Multiple searches to the same index—for example, with different filters. + - Different indices for different purposes, such as, one index for products, another one for marketing content. + - Multiple searches to the same index—for example, with different filters. Use the helper `searchForHits` or `searchForFacets` to get the results in a more convenient format, if you already know the return type you want. requestBody: diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 71c03d20ad8..29d19455326 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -1,11 +1,11 @@ -// {{{generationBanner}}} +{{{appDescription}}} package {{packageName}} +// {{{generationBanner}}} + {{#operations}} import ( "context" - "encoding/json" - "fmt" "net/http" "net/url" "strings" @@ -308,113 +308,128 @@ func replaceAllObjectsToChunkBatchOptions(opts []ReplaceAllObjectsOption) []Chun {{/isSearchClient}} {{#operation}} - {{#hasParams}} -func (r *{{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request) UnmarshalJSON(b []byte) error { - req := map[string]json.RawMessage{} - err := json.Unmarshal(b, &req) - if err != nil { - return fmt.Errorf("cannot unmarshal request: %w", err) - } - {{#allParams}} - if v, ok := req["{{#isQueryParam}}{{baseName}}{{/isQueryParam}}{{^isQueryParam}}{{paramName}}{{/isQueryParam}}"]; ok { - err = json.Unmarshal(v, &r.{{paramName}}) - if err != nil { - err = json.Unmarshal(b, &r.{{paramName}}) - if err != nil { - return fmt.Errorf("cannot unmarshal {{paramName}}: %w", err) - } - } - } {{#isBodyParam}}{{#required}}else { - err = json.Unmarshal(b, &r.{{paramName}}) - if err != nil { - return fmt.Errorf("cannot unmarshal body parameter {{paramName}}: %w", err) - } - }{{/required}}{{/isBodyParam}} - {{/allParams}} - - return nil -} - -// {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request represents the request with all the parameters for the API call. -type {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request struct { -{{#allParams}} - {{paramName}} {{^required}}{{^isPathParam}}{{^isFreeFormObject}}{{^isArray}}{{^isMap}}{{^isEnumRef}}*{{/isEnumRef}}{{/isMap}}{{/isArray}}{{/isFreeFormObject}}{{/isPathParam}}{{/required}}{{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{{dataType}}} -{{/allParams}} +{{#hasOptionalParams}} +// {{operationId}}Options represents the optional params for the API call. +type {{operationId}}Options struct { + {{#optionalParams}} + {{nameInPascalCase}} {{^isPathParam}}{{^isFreeFormObject}}{{^isMap}}*{{/isMap}}{{/isFreeFormObject}}{{/isPathParam}}{{{dataType}}} + {{/optionalParams}} } {{#isDeprecated}} -// Deprecated +// Deprecated: {{operationId}}Options is deprecated {{/isDeprecated}} -//New{{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request creates an instance of the {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request to be used for the API call. -func (c *APIClient) NewApi{{{nickname}}}Request({{#requiredParams}} {{paramName}} {{^isPathParam}}{{^isFreeFormObject}}{{^isArray}}{{^isMap}}{{^isPrimitiveType}}{{^isEnumRef}}*{{/isEnumRef}}{{/isPrimitiveType}}{{/isMap}}{{/isArray}}{{/isFreeFormObject}}{{/isPathParam}}{{{dataType}}} {{^-last}},{{/-last}}{{/requiredParams}}) {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request { - return {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request{ - {{#requiredParams}} - {{paramName}}: {{paramName}}, - {{/requiredParams}} - } +// New{{operationId}}Options creates an instance of the {{operationId}}Options used to add optional parameters to {{operationId}}WithOptions. +func New{{{operationId}}}Options() *{{operationId}}Options { + return &{{operationId}}Options{} } -{{#allParams}} -{{^required}} -// With{{#lambda.titlecase}}{{baseName}}{{/lambda.titlecase}} adds the {{paramName}} to the {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request and returns the request for chaining. +{{#optionalParams}} +// With{{#lambda.titlecase}}{{baseName}}{{/lambda.titlecase}} {{#description}}{{{.}}}{{/description}}{{^description}}adds the {{paramName}} to the Api{{operationId}}Request and returns the request for chaining.{{/description}} {{#isDeprecated}} -// Deprecated +// Deprecated: With{{#lambda.titlecase}}{{baseName}}{{/lambda.titlecase}} is deprecated {{/isDeprecated}} -func (r {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request) With{{#lambda.titlecase}}{{baseName}}{{/lambda.titlecase}}({{paramName}} {{^isFreeFormObject}}{{^isArray}}{{^isMap}}{{^isPrimitiveType}}{{^isEnumRef}}*{{/isEnumRef}}{{/isPrimitiveType}}{{/isMap}}{{/isArray}}{{/isFreeFormObject}}{{{dataType}}}) {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request { - r.{{paramName}} = {{#isPrimitiveType}}{{^isMap}}&{{/isMap}}{{/isPrimitiveType}}{{paramName}} - return r +func (o *{{operationId}}Options) With{{#lambda.titlecase}}{{baseName}}{{/lambda.titlecase}}({{paramName}} {{#isModel}}*{{/isModel}}{{{dataType}}}) *{{operationId}}Options { + o.{{nameInPascalCase}} = {{^isModel}}{{^isMap}}&{{/isMap}}{{/isModel}}{{paramName}} + return o } +{{/optionalParams}} -{{/required}} -{{/allParams}} -{{/hasParams}} -/* -{{operationId}} calls the API and returns the raw response from it. - {{#notes}} +{{/hasOptionalParams}} - {{{unescapedNotes}}} - {{/notes}} +// {{operationId}} {{{notes}}} {{#vendorExtensions}} - {{#x-acl.0}} - - Required API Key ACLs:{{/x-acl.0}} - {{#x-acl}} - - {{.}} - {{/x-acl}} + {{#x-acl.0}} +// +// Required API Key ACLs:{{/x-acl.0}} + {{#x-acl}} +// - {{.}} + {{/x-acl}} {{/vendorExtensions}} - - Request can be constructed by NewApi{{operationId}}Request with parameters below. - {{#allParams}} - @param {{paramName}} {{dataType}}{{#description}} - {{{.}}}{{/description}} - {{/allParams}} - @param opts ...RequestOption - Optional parameters for the API call - @return *http.Response - The raw response from the API - @return []byte - The raw response body from the API - @return error - An error if the API call fails + {{#hasParams}} +// Parameters: + {{/hasParams}} + {{#requiredParams}} +// - {{paramName}} {{#description}} - {{{.}}}{{/description}} + {{/requiredParams}} + {{#optionalParams}} +// - {{paramName}} {{#description}} - {{{.}}}{{/description}} (in optionalParams) + {{/optionalParams}} +// - opts - Optional parameters for the API call (e.g. WithContext, WithHeaderParam...) {{#isDeprecated}} +// +// Deprecated: {{operationId}} is deprecated + {{/isDeprecated}} +func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}*{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) ({{#returnType}}{{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}}, {{/returnType}}error) { + {{#returnType}} + var returnValue {{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}} + {{/returnType}} + + res, resBody, err := c.{{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams, {{/hasOptionalParams}}opts...) + if err != nil { + return {{#returnType}}returnValue, {{/returnType}}err + } + if res == nil { + return {{#returnType}}returnValue, {{/returnType}}reportError("res is nil") + } + + if res.StatusCode >= 300 { + return {{#returnType}}returnValue, {{/returnType}}c.decodeError(res, resBody) + } + + {{#returnType}} + err = c.decode(&returnValue, resBody) + if err != nil { + return {{#returnType}}returnValue, {{/returnType}}reportError("cannot decode result: %w", err) + } + {{/returnType}} + return {{#returnType}}returnValue, {{/returnType}}nil +} - Deprecated +// {{operationId}}WithHTTPInfo calls the API and returns the raw response from it. +// {{{notes}}} + {{#vendorExtensions}} + {{#x-acl.0}} +// +// Required API Key ACLs:{{/x-acl.0}} + {{#x-acl}} +// - {{.}} + {{/x-acl}} + {{/vendorExtensions}} + {{#hasParams}} +// +// Parameters: + {{/hasParams}} + {{#requiredParams}} +// - {{paramName}} {{#description}} - {{{.}}}{{/description}} + {{/requiredParams}} + {{#optionalParams}} +// - {{paramName}} {{#description}} - {{{.}}}{{/description}} (in optionalParams) + {{/optionalParams}} +// - opts - Optional parameters for the API call (e.g. WithContext, WithHeaderParam...) + {{#isDeprecated}} +// +// Deprecated: {{operationId}} is deprecated {{/isDeprecated}} - */ -func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request,{{/hasParams}} opts ...RequestOption) (*http.Response, []byte, error) { +func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}*{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) (*http.Response, []byte, error) { {{#vendorExtensions}} requestPath := "{{{path}}}"{{#pathParams}} - requestPath = strings.ReplaceAll(requestPath, {{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, {{#x-is-custom-request}}utils.ParameterToString(r.{{paramName}}){{/x-is-custom-request}}{{^x-is-custom-request}}url.PathEscape(utils.ParameterToString(r.{{paramName}})){{/x-is-custom-request}}){{/pathParams}} + requestPath = strings.ReplaceAll(requestPath, {{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, {{#x-is-custom-request}}utils.ParameterToString({{paramName}}){{/x-is-custom-request}}{{^x-is-custom-request}}url.PathEscape(utils.ParameterToString({{paramName}})){{/x-is-custom-request}}){{/pathParams}} {{/vendorExtensions}} {{#allParams}} {{#required}} {{#isString}} - if r.{{paramName}} == "" { + if {{paramName}} == "" { return nil, nil, reportError("Parameter `{{paramName}}` is required when calling `{{operationId}}`.") }{{/isString}}{{#isContainer}} - if len(r.{{paramName}}) == 0 { + if len({{paramName}}) == 0 { return nil, nil, reportError("Parameter `{{paramName}}` is required when calling `{{operationId}}`.") }{{/isContainer}}{{#isMap}} - if len(r.{{paramName}}) == 0 { + if len({{paramName}}) == 0 { return nil, nil, reportError("Parameter `{{paramName}}` is required when calling `{{operationId}}`.") }{{/isMap}}{{^isPrimitiveType}}{{^isContainer}}{{^isMap}}{{^isEnumRef}} - if r.{{paramName}} == nil { + if {{paramName}} == nil { return nil, nil, reportError("Parameter `{{paramName}}` is required when calling `{{operationId}}`.") }{{/isEnumRef}}{{/isMap}}{{/isContainer}}{{/isPrimitiveType}} {{/required}} @@ -435,8 +450,8 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{ {{#vendorExtensions.x-is-custom-request}} {{#queryParams}} - {{^required}}if !utils.IsNilOrEmpty(r.{{paramName}}) { {{/required}} - for k, v := range r.{{paramName}} { + {{^required}}if {{#hasOptionalParams}}optionalParams != nil && {{/hasOptionalParams}}!utils.IsNilOrEmpty({{> param_name}}) { {{/required}} + for k, v := range {{> param_name}} { conf.queryParams.Set(k, utils.QueryParameterToString(v)) } {{^required}} } {{/required}} @@ -445,22 +460,22 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{ {{^vendorExtensions.x-is-custom-request}} {{#queryParams}} {{#required}} - conf.queryParams.Set("{{baseName}}", utils.QueryParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isPrimitiveType}}{{^isEnumRef}}*{{/isEnumRef}}{{/isPrimitiveType}}{{/isArray}}{{/isFreeFormObject}}r.{{paramName}})) + conf.queryParams.Set("{{baseName}}", utils.QueryParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isPrimitiveType}}{{^isEnumRef}}*{{/isEnumRef}}{{/isPrimitiveType}}{{/isArray}}{{/isFreeFormObject}}{{paramName}})) {{/required}} {{^required}} - if !utils.IsNilOrEmpty(r.{{paramName}}) { - conf.queryParams.Set("{{baseName}}", utils.QueryParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isEnumRef}}*{{/isEnumRef}}{{/isArray}}{{/isFreeFormObject}}r.{{paramName}})) + if {{#hasOptionalParams}}optionalParams != nil && {{/hasOptionalParams}}!utils.IsNilOrEmpty({{> param_name}}) { + conf.queryParams.Set("{{baseName}}", utils.QueryParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isEnumRef}}*{{/isEnumRef}}{{/isArray}}{{/isFreeFormObject}}{{> param_name}})) } {{/required}} {{/queryParams}} {{/vendorExtensions.x-is-custom-request}} {{#headerParams}} {{#required}} - conf.headerParams["{{baseName}}"] = utils.ParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isPrimitiveType}}{{^isEnumRef}}*{{/isEnumRef}}{{/isPrimitiveType}}{{/isArray}}{{/isFreeFormObject}}r.{{paramName}}) + conf.headerParams["{{baseName}}"] = utils.ParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isPrimitiveType}}{{^isEnumRef}}*{{/isEnumRef}}{{/isPrimitiveType}}{{/isArray}}{{/isFreeFormObject}}{{paramName}}) {{/required}} {{^required}} - if !utils.IsNilOrEmpty(r.{{paramName}}) { - conf.headerParams["{{baseName}}"] = utils.ParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isEnumRef}}*{{/isEnumRef}}{{/isArray}}{{/isFreeFormObject}}r.{{paramName}}) + if {{#hasOptionalParams}}optionalParams != nil && {{/hasOptionalParams}}!utils.IsNilOrEmpty({{> param_name}}) { + conf.headerParams["{{baseName}}"] = utils.ParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isEnumRef}}*{{/isEnumRef}}{{/isArray}}{{/isFreeFormObject}}{{> param_name}}) } {{/required}} {{/headerParams}} @@ -470,17 +485,66 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{ opt.apply(&conf) } - var postBody any - {{#bodyParams}} - // body params{{^required}} - if utils.IsNilOrEmpty(r.{{paramName}}) { - postBody = "{}" - } else { {{/required}} - postBody = r.{{paramName}}{{^required}} - } {{/required}} + // body params + {{#required}} + {{#vendorExtensions.x-flat-body}} + postBody := struct{ + {{#vars}} + {{name}} {{^required}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/required}}{{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{{dataType}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"` + {{/vars}} + }{ + {{#vars}} + {{#required}} + {{name}}: {{nameInLowerCase}}, + {{/required}} + {{/vars}} + } + {{#hasOptionalParams}} + if optionalParams != nil { + {{#vars}} + {{^required}} + postBody.{{name}} = optionalParams.{{nameInPascalCase}} + {{/required}} + {{/vars}} + } + {{/hasOptionalParams}} + {{/vendorExtensions.x-flat-body}} + {{^vendorExtensions.x-flat-body}} + postBody := {{^required}}{{#hasOptionalParams}}optionalParams.{{/hasOptionalParams}}{{/required}}{{paramName}} + {{/vendorExtensions.x-flat-body}} + {{/required}} + {{^required}} + {{#vendorExtensions.x-flat-body}} + {{#hasOptionalParams}} + postBody := optionalParams + {{/hasOptionalParams}} + {{^hasOptionalParams}} + postBody := struct{ + {{#vars}} + {{name}} {{^required}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/required}}{{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{{dataType}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"` + {{/vars}} + }{ + {{#vars}} + {{name}}: {{nameInLowerCase}}, + {{/vars}} + } + {{/hasOptionalParams}} + {{/vendorExtensions.x-flat-body}} + {{^vendorExtensions.x-flat-body}} + {{#hasOptionalParams}} + var postBody any + if optionalParams != nil { + postBody = optionalParams.{{nameInPascalCase}} + } + {{/hasOptionalParams}} + {{^hasOptionalParams}} + postBody := {{paramName}} + {{/hasOptionalParams}} + {{/vendorExtensions.x-flat-body}} + {{/required}} {{/bodyParams}} - req, err := c.prepareRequest(conf.context, requestPath, http.Method{{httpMethod}}, postBody, conf.headerParams, conf.queryParams) + req, err := c.prepareRequest(conf.context, requestPath, http.Method{{httpMethod}}, {{^bodyParams}}nil{{/bodyParams}}{{#bodyParams}}postBody{{/bodyParams}}, conf.headerParams, conf.queryParams) if err != nil { return nil, nil, err } @@ -488,37 +552,6 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{ return c.callAPI(req, {{#vendorExtensions}}{{#x-use-read-transporter}}true{{/x-use-read-transporter}}{{^x-use-read-transporter}}false{{/x-use-read-transporter}},{{/vendorExtensions}}conf.timeouts) } -/* -{{operationId}} casts the HTTP response body to a defined struct. -{{> operation_description}} -func (c *APIClient) {{nickname}}({{#hasParams}}r {{#structPrefix}}{{&classname}}{{/structPrefix}}{{^structPrefix}}Api{{/structPrefix}}{{operationId}}Request,{{/hasParams}} opts ...RequestOption) ({{#returnType}}{{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}}, {{/returnType}}error) { - {{#returnType}} - var returnValue {{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}} - {{/returnType}} - - res, resBody, err := c.{{nickname}}WithHTTPInfo({{#hasParams}}r, {{/hasParams}}opts...) - if err != nil { - return {{#returnType}}returnValue, {{/returnType}}err - } - if res == nil { - return {{#returnType}}returnValue, {{/returnType}}reportError("res is nil") - } - defer res.Body.Close() - - if res.StatusCode >= 300 { - return {{#returnType}}returnValue, {{/returnType}}c.decodeError(res, resBody) - } - - {{#returnType}} - err = c.decode(&returnValue, resBody) - if err != nil { - return {{#returnType}}returnValue, {{/returnType}}reportError("cannot decode result: %w", err) - } - - {{/returnType}} - return {{#returnType}}returnValue, {{/returnType}}nil -} - {{/operation}} {{/operations}} diff --git a/templates/go/client.mustache b/templates/go/client.mustache index a2154e74986..a06c7fdd4e1 100644 --- a/templates/go/client.mustache +++ b/templates/go/client.mustache @@ -1,6 +1,7 @@ -// {{{generationBanner}}} package {{packageName}} +// {{{generationBanner}}} + import ( "bytes" "compress/gzip" diff --git a/templates/go/configuration.mustache b/templates/go/configuration.mustache index c301d631451..6d35c4ad09e 100644 --- a/templates/go/configuration.mustache +++ b/templates/go/configuration.mustache @@ -1,6 +1,7 @@ -// {{{generationBanner}}} package {{packageName}} +// {{{generationBanner}}} + import ( "github.com/algolia/algoliasearch-client-go/v4/algolia/transport" ) diff --git a/templates/go/model.mustache b/templates/go/model.mustache index 35ee884d6ec..41fdd31f556 100644 --- a/templates/go/model.mustache +++ b/templates/go/model.mustache @@ -1,6 +1,7 @@ -// {{{generationBanner}}} package {{packageName}} +// {{{generationBanner}}} + {{#models}} import ( "encoding/json" diff --git a/templates/go/operation_description.mustache b/templates/go/operation_description.mustache deleted file mode 100644 index 44166eec5e5..00000000000 --- a/templates/go/operation_description.mustache +++ /dev/null @@ -1,23 +0,0 @@ -{{#notes}} - -{{{unescapedNotes}}} -{{/notes}} -{{#vendorExtensions}} -{{#x-acl.0}} - -Required API Key ACLs:{{/x-acl.0}} -{{#x-acl}} - - {{.}} -{{/x-acl}} -{{/vendorExtensions}} - -Request can be constructed by NewApi{{operationId}}Request with parameters below. - {{#allParams}} - @param {{paramName}} {{dataType}}{{#description}} - {{{.}}}{{/description}} - {{/allParams}} - {{#returnType}}@return {{{.}}}{{/returnType}} -{{#isDeprecated}} - -Deprecated -{{/isDeprecated}} -*/ diff --git a/templates/go/param_name.mustache b/templates/go/param_name.mustache new file mode 100644 index 00000000000..67c301f7399 --- /dev/null +++ b/templates/go/param_name.mustache @@ -0,0 +1 @@ +{{#hasOptionalParams}}optionalParams.{{nameInPascalCase}}{{/hasOptionalParams}}{{^hasOptionalParams}}{{paramName}}{{/hasOptionalParams}} \ No newline at end of file diff --git a/templates/go/search_helpers.mustache b/templates/go/search_helpers.mustache index a297bec029f..b69c595a95c 100644 --- a/templates/go/search_helpers.mustache +++ b/templates/go/search_helpers.mustache @@ -1,14 +1,50 @@ -/* -SearchForHits calls the `search` method but with certainty that we will only request Algolia records (hits) and not facets. -Disclaimer: We don't assert that the parameters you pass to this method only contains `hits` requests to prevent impacting search performances, this helper is purely for typing purposes. - - @param r ApiSearchRequest - Body of the `search` operation. - @param opts ...RequestOption - Optional parameters for the request. - @return []SearchResponse - List of hits. - @return error - Error if any. - */ -func (c *APIClient) SearchForHits(r ApiSearchRequest, opts ...RequestOption) ([]SearchResponse, error) { - res, err := c.Search(r, opts...) +func CreateIterable[T any](execute func(*T, error) (*T, error), validate func(*T, error) (bool, error), opts ...IterableOption) (*T, error) { + conf := config{ + headerParams: map[string]string{}, + maxRetries: -1, + timeout: func(count int) time.Duration { + return 0 * time.Millisecond + }, + } + + for _, opt := range opts { + opt.apply(&conf) + } + + var executor func(*T, error) (*T, error) + + retryCount := 0 + + executor = func(previousResponse *T, previousError error) (*T, error) { + response, responseErr := execute(previousResponse, previousError) + + retryCount++ + + if conf.aggregator != nil { + conf.aggregator(response, responseErr) + } + + canStop, err := validate(response, responseErr) + if canStop || err != nil { + return response, err + } + + if conf.maxRetries >= 0 && retryCount >= conf.maxRetries { + return nil, errs.NewWaitError(fmt.Sprintf("The maximum number of retries exceeded. (%d/%d)", retryCount, conf.maxRetries)) + } + + time.Sleep(conf.timeout(retryCount)) + + return executor(response, responseErr) + } + + return executor(nil, nil) +} + +// SearchForHits calls the `search` method but with certainty that we will only request Algolia records (hits) and not facets. +// Disclaimer: We don't assert that the parameters you pass to this method only contains `hits` requests to prevent impacting search performances, this helper is purely for typing purposes. +func (c *APIClient) SearchForHits(requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchResponse, error) { + res, err := c.Search(requests, strategy, opts...) if err != nil { return nil, err } @@ -24,17 +60,10 @@ func (c *APIClient) SearchForHits(r ApiSearchRequest, opts ...RequestOption) ([] return slices.Clip(hits), nil } -/* -SearchForFacets calls the `search` method but with certainty that we will only request Algolia facets and not records (hits). -Disclaimer: We don't assert that the parameters you pass to this method only contains `facets` requests to prevent impacting search performances, this helper is purely for typing purposes. - - @param r ApiSearchRequest - Body of the `search` operation. - @param opts ...RequestOption - Optional parameters for the request. - @return []SearchForFacetValuesResponse - List of facet hits. - @return error - Error if any. - */ -func (c *APIClient) SearchForFacets(r ApiSearchRequest, opts ...RequestOption) ([]SearchForFacetValuesResponse, error) { - res, err := c.Search(r, opts...) +// SearchForFacets calls the `search` method but with certainty that we will only request Algolia facets and not records (hits). +// Disclaimer: We don't assert that the parameters you pass to this method only contains `facets` requests to prevent impacting search performances, this helper is purely for typing purposes. +func (c *APIClient) SearchForFacets(requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchForFacetValuesResponse, error) { + res, err := c.Search(requests, strategy, opts...) if err != nil { return nil, err } @@ -50,17 +79,9 @@ func (c *APIClient) SearchForFacets(r ApiSearchRequest, opts ...RequestOption) ( return slices.Clip(facetHits), nil } -/* -WaitForTask waits for a task to be published. -It returns the task response if the operation was successful. -It returns an error if the operation failed. - - @param indexName string - Index name. - @param taskID int64 - Task ID. - @param opts ...IterableOption - Optional parameters for the request. - @return *GetTaskResponse - Task response. - @return error - Error if any. -*/ +// WaitForTask waits for a task to be published. +// It returns the task response if the operation was successful. +// It returns an error if the operation failed. func (c *APIClient) WaitForTask( indexName string, taskID int64, @@ -73,7 +94,7 @@ func (c *APIClient) WaitForTask( return CreateIterable( //nolint:wrapcheck func(*GetTaskResponse, error) (*GetTaskResponse, error) { - return c.GetTask(c.NewApiGetTaskRequest(indexName, taskID), toRequestOptions(opts)...) + return c.GetTask(indexName, taskID, toRequestOptions(opts)...) }, func(response *GetTaskResponse, err error) (bool, error) { if err != nil || response == nil { @@ -86,16 +107,9 @@ func (c *APIClient) WaitForTask( ) } -/* -WaitForAppTask waits for an application-level task to be published. -It returns the task response if the operation was successful. -It returns an error if the operation failed. - - @param taskID int64 - Task ID. - @param opts ...IterableOption - Optional parameters for the request. - @return *GetTaskResponse - Task response. - @return error - Error if any. -*/ +// WaitForAppTask waits for an application-level task to be published. +// It returns the task response if the operation was successful. +// It returns an error if the operation failed. func (c *APIClient) WaitForAppTask( taskID int64, opts ...IterableOption, @@ -107,7 +121,7 @@ func (c *APIClient) WaitForAppTask( return CreateIterable( //nolint:wrapcheck func(*GetTaskResponse, error) (*GetTaskResponse, error) { - return c.GetAppTask(c.NewApiGetAppTaskRequest(taskID), toRequestOptions(opts)...) + return c.GetAppTask(taskID, toRequestOptions(opts)...) }, func(response *GetTaskResponse, err error) (bool, error) { if err != nil || response == nil { @@ -138,25 +152,17 @@ func slicesEqualUnordered[T cmp.Ordered](a []T, b []T) bool { return slices.Equal(aCopy, bCopy) } -/* -WaitForApiKey waits for an API key to be created, deleted or updated. -It returns the API key response if the operation was successful. -It returns an error if the operation failed. - -The operation can be one of the following: - - "add": wait for the API key to be created - - "delete": wait for the API key to be deleted - - "update": wait for the API key to be updated - -If the operation is "update", the apiKey parameter must be set. -If the operation is "delete" or "add", the apiKey parameter is not used. - - @param key string - API key. - @param operation ApiKeyOperation - Operation type - add, delete or update. - @param opts ...WaitForApiKeyOption - Optional parameters for the request, you must provide WithApiKey if the operation is "update". - @return *GetApiKeyResponse - API key response. - @return error - Error if any. -*/ +// WaitForApiKey waits for an API key to be created, deleted or updated. +// It returns the API key response if the operation was successful. +// It returns an error if the operation failed. +// +// The operation can be one of the following: +// - "add": wait for the API key to be created +// - "delete": wait for the API key to be deleted +// - "update": wait for the API key to be updated +// +// If the operation is "update", the apiKey parameter must be set. +// If the operation is "delete" or "add", the apiKey parameter is not used. func (c *APIClient) WaitForApiKey( key string, operation ApiKeyOperation, @@ -241,23 +247,15 @@ func (c *APIClient) WaitForApiKey( return CreateIterable( //nolint:wrapcheck func(*GetApiKeyResponse, error) (*GetApiKeyResponse, error) { - return c.GetApiKey(c.NewApiGetApiKeyRequest(key), toRequestOptions(opts)...) + return c.GetApiKey(key, toRequestOptions(opts)...) }, validateFunc, waitForApiKeyToIterableOptions(opts)..., ) } -/* -BrowseObjects allows to aggregate all the hits returned by the API calls. -Use the `WithAggregator` option to collect all the responses. - - @param indexName string - Index name. - @param browseParams BrowseParamsObject - Browse parameters. - @param opts ...IterableOption - Optional parameters for the request. - @return *BrowseResponse - Browse response. - @return error - Error if any. -*/ +// BrowseObjects allows to aggregate all the hits returned by the API calls. +// Use the `WithAggregator` option to collect all the responses. func (c *APIClient) BrowseObjects( indexName string, browseParams BrowseParamsObject, @@ -274,7 +272,7 @@ func (c *APIClient) BrowseObjects( } return c.Browse( - c.NewApiBrowseRequest(indexName).WithBrowseParams(BrowseParamsObjectAsBrowseParams(&browseParams)), + indexName, BrowseParamsObjectAsBrowseParams(&browseParams), toRequestOptions(opts)..., ) }, @@ -287,42 +285,34 @@ func (c *APIClient) BrowseObjects( return err } -/* -BrowseRules allows to aggregate all the rules returned by the API calls. -Use the `WithAggregator` option to collect all the responses. - - @param indexName string - Index name. - @param searchRulesParams SearchRulesParams - Search rules parameters. - @param opts ...IterableOption - Optional parameters for the request. - @return *SearchRulesResponse - Search rules response. - @return error - Error if any. -*/ +// BrowseRules allows to aggregate all the rules returned by the API calls. +// Use the `WithAggregator` option to collect all the responses. func (c *APIClient) BrowseRules( indexName string, - searchRulesParams SearchRulesParams, + optionalParams *SearchRulesOptions, opts ...IterableOption, ) error { hitsPerPage := int32(1000) - if searchRulesParams.HitsPerPage != nil { - hitsPerPage = *searchRulesParams.HitsPerPage + if optionalParams == nil { + optionalParams = NewSearchRulesOptions() + } + if optionalParams.HitsPerPage != nil { + hitsPerPage = *optionalParams.HitsPerPage } _, err := CreateIterable( //nolint:wrapcheck func(previousResponse *SearchRulesResponse, previousErr error) (*SearchRulesResponse, error) { - searchRulesParams.HitsPerPage = &hitsPerPage + optionalParams.HitsPerPage = &hitsPerPage if previousResponse != nil { - searchRulesParams.Page = utils.ToPtr(previousResponse.Page + 1) + optionalParams.Page = utils.ToPtr(previousResponse.Page + 1) } - if searchRulesParams.Page == nil { - searchRulesParams.Page = utils.ToPtr(int32(0)) + if optionalParams.Page == nil { + optionalParams.Page = utils.ToPtr(int32(0)) } - return c.SearchRules( - c.NewApiSearchRulesRequest(indexName).WithSearchRulesParams(&searchRulesParams), - toRequestOptions(opts)..., - ) + return c.SearchRules(indexName, optionalParams, toRequestOptions(opts)...) }, func(response *SearchRulesResponse, err error) (bool, error) { return err != nil || (response != nil && len(response.Hits) < int(hitsPerPage)), err @@ -333,42 +323,34 @@ func (c *APIClient) BrowseRules( return err } -/* -BrowseSynonyms allows to aggregate all the synonyms returned by the API calls. -Use the `WithAggregator` option to collect all the responses. - - @param indexName string - Index name. - @param searchSynonymsParams SearchSynonymsParams - Search synonyms parameters. - @param opts ...IterableOption - Optional parameters for the request. - @return *SearchSynonymsResponse - Search synonyms response. - @return error - Error if any. -*/ +// BrowseSynonyms allows to aggregate all the synonyms returned by the API calls. +// Use the `WithAggregator` option to collect all the responses. func (c *APIClient) BrowseSynonyms( indexName string, - searchSynonymsParams SearchSynonymsParams, + optionalParams *SearchSynonymsOptions, opts ...IterableOption, ) error { hitsPerPage := int32(1000) - if searchSynonymsParams.HitsPerPage != nil { - hitsPerPage = *searchSynonymsParams.HitsPerPage + if optionalParams == nil { + optionalParams = NewSearchSynonymsOptions() + } + if optionalParams.HitsPerPage != nil { + hitsPerPage = *optionalParams.HitsPerPage } - if searchSynonymsParams.Page == nil { - searchSynonymsParams.Page = utils.ToPtr(int32(0)) + if optionalParams.Page == nil { + optionalParams.Page = utils.ToPtr(int32(0)) } _, err := CreateIterable( //nolint:wrapcheck func(previousResponse *SearchSynonymsResponse, previousErr error) (*SearchSynonymsResponse, error) { - searchSynonymsParams.HitsPerPage = &hitsPerPage + optionalParams.HitsPerPage = &hitsPerPage defer func() { - searchSynonymsParams.Page = utils.ToPtr(*searchSynonymsParams.Page + 1) + optionalParams.Page = utils.ToPtr(*optionalParams.Page + 1) }() - return c.SearchSynonyms( - c.NewApiSearchSynonymsRequest(indexName).WithSearchSynonymsParams(&searchSynonymsParams), - toRequestOptions(opts)..., - ) + return c.SearchSynonyms(indexName, optionalParams, toRequestOptions(opts)...) }, func(response *SearchSynonymsResponse, err error) (bool, error) { return err != nil || (response != nil && len(response.Hits) < int(hitsPerPage)), err @@ -427,15 +409,7 @@ func encodeRestrictions(restrictions *SecuredApiKeyRestrictions) (string, error) return strings.Join(queryString, "&"), nil } -/* -GenerateSecuredApiKey generates a public API key intended to restrict access -to certain records. This new key is built upon the existing key named `parentApiKey` and the following options. - - @param parentApiKey string - The parent API key. - @param restrictions *SecuredApiKeyRestrictions - The restrictions to apply to the new key. - @return string - The new secured API key. - @return error - Error if any. -*/ +// GenerateSecuredApiKey generates a public API key intended to restrict access to certain records. This new key is built upon the existing key named `parentApiKey` and the following options. func (c *APIClient) GenerateSecuredApiKey(parentApiKey string, restrictions *SecuredApiKeyRestrictions) (string, error) { h := hmac.New(sha256.New, []byte(parentApiKey)) @@ -454,13 +428,7 @@ func (c *APIClient) GenerateSecuredApiKey(parentApiKey string, restrictions *Sec return key, nil } -/* -GetSecuredApiKeyRemainingValidity retrieves the remaining validity of the previously generated `securedApiKey`, the `ValidUntil` parameter must have been provided. - - @param securedApiKey string - The secured API key. - @return time.Duration - The remaining validity of the secured API key. - @return error - Error if any. -*/ +// GetSecuredApiKeyRemainingValidity retrieves the remaining validity of the previously generated `securedApiKey`, the `ValidUntil` parameter must have been provided. func (c *APIClient) GetSecuredApiKeyRemainingValidity(securedApiKey string) (time.Duration, error) { if len(securedApiKey) == 0 { return 0, fmt.Errorf("given secured API key is empty: %s", securedApiKey) @@ -485,30 +453,12 @@ 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. - - @param indexName string - the index name to save objects into. - @param objects []map[string]any - List of objects to save. - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []BatchResponse - List of batch responses. - @return error - Error if any. -*/ +// 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, 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. - - @param indexName string - the index name to delete objects from. - @param objectIDs []string - List of objectIDs to delete. - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []BatchResponse - List of batch responses. - @return error - Error if any. -*/ +// 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, opts ...ChunkedBatchOption) ([]BatchResponse, error) { objects := make([]map[string]any, 0, len(objectIDs)) @@ -519,15 +469,7 @@ func (c *APIClient) DeleteObjects(indexName string, objectIDs []string, opts ... 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. - - @param indexName string - the index name to save objects into. - @param objects []map[string]any - List of objects to save. - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []BatchResponse - List of batch responses. - @return error - Error if any. -*/ +// 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, opts ...PartialUpdateObjectsOption) ([]BatchResponse, error) { conf := config{ headerParams: map[string]string{}, @@ -549,16 +491,7 @@ func (c *APIClient) PartialUpdateObjects(indexName string, objects []map[string] return c.ChunkedBatch(indexName, objects, action, partialUpdateObjectsToChunkedBatchOptions(opts)...) } -/* -ChunkedBatch chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `batch` requests. - - @param indexName string - the index name to save objects into. - @param objects []map[string]any - List of objects to save. - @param action Action - The action to perform on the objects. - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []BatchResponse - List of batch responses. - @return error - Error if any. -*/ +// 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) { conf := config{ headerParams: map[string]string{}, @@ -577,7 +510,7 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act requests = append(requests, *NewBatchRequest(action, obj)) if len(requests) == conf.batchSize || i == len(objects)-1 { - resp, err := c.Batch(c.NewApiBatchRequest(indexName, NewBatchWriteParams(requests)), toRequestOptions(opts)...) + resp, err := c.Batch(indexName, requests, toRequestOptions(opts)...) if err != nil { return nil, err } @@ -599,16 +532,8 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act return responses, nil } -/* -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. -See https://api-clients-automation.netlify.app/docs/custom-helpers/#replaceallobjects for implementation details. - - @param indexName string - the index name to replace objects into. - @param objects []map[string]any - List of objects to replace. - @param opts ...ReplaceAllObjectsOption - Optional parameters for the request. - @return *ReplaceAllObjectsResponse - The response of the replace all objects operation. - @return error - Error if any. -*/ +// 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. +// See https://api-clients-automation.netlify.app/docs/custom-helpers/#replaceallobjects for implementation details. 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.") @@ -693,16 +618,8 @@ func (c *APIClient) ReplaceAllObjectsWithTransformation(indexName string, object }, nil } -/* -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/custom-helpers/#replaceallobjects for implementation details. - - @param indexName string - the index name to replace objects into. - @param objects []map[string]any - List of objects to replace. - @param opts ...ReplaceAllObjectsOption - Optional parameters for the request. - @return *ReplaceAllObjectsResponse - The response of the replace all objects operation. - @return error - Error if any. -*/ +// 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/add-new-api-client#5-helpers for implementation details. func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any, opts ...ReplaceAllObjectsOption) (*ReplaceAllObjectsResponse, error) { tmpIndexName := fmt.Sprintf("%s_tmp_%d", indexName, time.Now().UnixNano()) @@ -717,49 +634,49 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any opts = append(opts, WithWaitForTasks(true)) - copyResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope(conf.scopes))), toRequestOptions(opts)...) + copyResp, err := c.OperationIndex(indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) if err != nil { return nil, err } batchResp, err := c.ChunkedBatch(tmpIndexName, objects, ACTION_ADD_OBJECT, replaceAllObjectsToChunkBatchOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(tmpIndexName) return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(tmpIndexName) return nil, err } - copyResp, err = c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope(conf.scopes))), toRequestOptions(opts)...) + copyResp, err = c.OperationIndex(indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(tmpIndexName) return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(tmpIndexName) return nil, err } - moveResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(tmpIndexName, NewOperationIndexParams(OPERATION_TYPE_MOVE, indexName)), toRequestOptions(opts)...) + moveResp, err := c.OperationIndex(tmpIndexName, OPERATION_TYPE_MOVE, indexName, nil, toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(tmpIndexName) return nil, err } _, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(tmpIndexName) return nil, err } @@ -775,7 +692,7 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any // error. When encountering a network error, a non-nil error is returned along // with false. func (c *APIClient) IndexExists(indexName string) (bool, error) { - _, err := c.GetSettings(c.NewApiGetSettingsRequest(indexName)) + _, err := c.GetSettings(indexName) if err == nil { return true, nil } diff --git a/templates/go/snippets/method.mustache b/templates/go/snippets/method.mustache index 3788cb4dfef..cc4e4b9dd22 100644 --- a/templates/go/snippets/method.mustache +++ b/templates/go/snippets/method.mustache @@ -1,6 +1,7 @@ -// {{generationBanner}} package snippets +// {{generationBanner}} + // >IMPORT {{> snippets/import}} // IMPORT< diff --git a/templates/go/tests/client/benchmark.mustache b/templates/go/tests/client/benchmark.mustache index 7c1462fe531..9335affa7ce 100644 --- a/templates/go/tests/client/benchmark.mustache +++ b/templates/go/tests/client/benchmark.mustache @@ -1,6 +1,7 @@ -// {{generationBanner}} package benchmark +// {{generationBanner}} + import ( "testing" diff --git a/templates/go/tests/client/client.mustache b/templates/go/tests/client/client.mustache index a597f36543e..d35c38a6b72 100644 --- a/templates/go/tests/client/client.mustache +++ b/templates/go/tests/client/client.mustache @@ -1,6 +1,7 @@ -// {{generationBanner}} package client +// {{generationBanner}} + import ( "encoding/json" "net/url" diff --git a/templates/go/tests/e2e/e2e.mustache b/templates/go/tests/e2e/e2e.mustache index 23552e8e754..94d9f2757fd 100644 --- a/templates/go/tests/e2e/e2e.mustache +++ b/templates/go/tests/e2e/e2e.mustache @@ -1,6 +1,7 @@ -// {{generationBanner}} package e2e +// {{generationBanner}} + import ( "encoding/json" "os" diff --git a/templates/go/tests/method.mustache b/templates/go/tests/method.mustache index d2819805aef..d10c334b641 100644 --- a/templates/go/tests/method.mustache +++ b/templates/go/tests/method.mustache @@ -1,2 +1 @@ -client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#hasParams}}{{^isHelper}}client.NewApi{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Request({{/isHelper}} - {{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} {{^isHelper}}){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{#-last}},{{/-last}}{{/required}}{{/parametersWithDataType}}{{/isHelper}}{{#isHelper}}{{#parametersWithDataType}}{{^required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}}{{/isHelper}}{{/hasParams}}{{#requestOptions}}{{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} {{#timeouts.read}} ,{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), {{/timeouts.read}} {{#timeouts.write}} ,{{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), {{/timeouts.write}} {{#timeouts.connect}} ,{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), {{/timeouts.connect}} {{/requestOptions}}) \ No newline at end of file +client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} {{#hasOptionalWrapper}}{{clientPrefix}}.New{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Options(){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/required}}{{/parametersWithDataType}}{{#requestOptions}},{{/requestOptions}}{{/hasOptionalWrapper}}{{#requestOptions}}{{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} {{#timeouts.read}} ,{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), {{/timeouts.read}} {{#timeouts.write}} {{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), {{/timeouts.write}} {{#timeouts.connect}} ,{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), {{/timeouts.connect}} {{/requestOptions}}) \ No newline at end of file From edaa89f842d099514a69340a6a7668033170fe07 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Fri, 9 May 2025 10:18:29 +0200 Subject: [PATCH 02/14] also flatten when there is a single option --- .../algolia/codegen/AlgoliaGoGenerator.java | 172 ++++++++---------- .../codegen/cts/tests/TestsGenerator.java | 15 ++ templates/go/api.mustache | 4 +- 3 files changed, 96 insertions(+), 95 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index c55ed5dbf93..b0659ec5fcf 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -152,106 +152,16 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List "// " + line).collect(Collectors.joining("\n"))).trim(); + // enrich the params for (CodegenParameter param : ope.optionalParams) { param.nameInPascalCase = Helpers.capitalize(param.baseName); } CodegenParameter bodyParam = ope.bodyParam; - if (bodyParam == null) { - continue; - } - bodyParam.nameInPascalCase = Helpers.capitalize(bodyParam.baseName); - if (!bodyParam.isModel) { - continue; - } - - // check for colision with other params - boolean hasCollision = false; - for (CodegenProperty prop : bodyParam.getVars()) { - for (CodegenParameter param : ope.allParams) { - if (param.paramName.equals(prop.baseName)) { - hasCollision = true; - break; - } - } - } - if (hasCollision) { - System.out.println("Operation " + ope.operationId + " has a body param with the same name as another param, skipping flattening"); - continue; - } - - if (ope.operationId.equals("Browse")) { - System.out.println( - ope.allParams.size() + - " params " + - ope.requiredParams.size() + - " required params " + - ope.optionalParams.size() + - " optional params" - ); - } - - bodyParam.vendorExtensions.put("x-flat-body", bodyParam.getVars().size() > 0); - - if (bodyParam.getVars().size() > 0) { - ope.allParams.removeIf(param -> param.isBodyParam); - ope.requiredParams.removeIf(param -> param.isBodyParam); - ope.optionalParams.removeIf(param -> param.isBodyParam); - } - - for (CodegenProperty prop : bodyParam.getVars()) { - // there is no easy way to convert a prop to a param, we need to copy all the fields - CodegenParameter param = new CodegenParameter(); - - prop.nameInLowerCase = toParamName(prop.baseName); - param.nameInPascalCase = Helpers.capitalize(prop.baseName); - param.paramName = toParamName(prop.baseName); - param.baseName = prop.baseName; - param.baseType = prop.baseType; - param.dataType = prop.dataType; - param.datatypeWithEnum = prop.datatypeWithEnum; - param.description = prop.description; - param.example = prop.example; - param.isModel = prop.isModel; - param.isArray = prop.isArray; - param.isContainer = prop.isContainer; - param.isMap = prop.isMap; - param.isEnum = prop.isEnum; - param.isEnumRef = prop.isEnumRef; - param.isPrimitiveType = prop.isPrimitiveType; - param.isString = prop.isString; - param.isNumeric = prop.isNumeric; - param.isBoolean = prop.isBoolean; - param.isDate = prop.isDate; - param.isDateTime = prop.isDateTime; - param.isFreeFormObject = prop.isFreeFormObject; - param.isNullable = prop.isNullable; - param.jsonSchema = prop.jsonSchema; - param.required = prop.required; - param.vendorExtensions = prop.vendorExtensions; - param.allowableValues = prop.allowableValues; - - if (prop.required) { - ope.requiredParams.add(param); - ope.hasRequiredParams = true; - } else { - ope.optionalParams.add(param); - ope.hasOptionalParams = true; - } - ope.allParams.add(param); + if (bodyParam != null) { + flattenBody(ope); } - System.out.println( - ope.operationId + - " has " + - ope.requiredParams.size() + - " required params and " + - ope.optionalParams.size() + - " optional params " + - bodyParam.getVars().size() + - " body params " - ); - // If the optional param struct only has 1 param, we can remove the wrapper if (ope.optionalParams.size() == 1) { CodegenParameter param = ope.optionalParams.get(0); @@ -270,4 +180,80 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List 0); + + if (bodyParam.getVars().size() > 0) { + ope.allParams.removeIf(param -> param.isBodyParam); + ope.requiredParams.removeIf(param -> param.isBodyParam); + ope.optionalParams.removeIf(param -> param.isBodyParam); + } + + for (CodegenProperty prop : bodyParam.getVars()) { + // there is no easy way to convert a prop to a param, we need to copy all the fields + CodegenParameter param = new CodegenParameter(); + + prop.nameInLowerCase = toParamName(prop.baseName); + param.nameInPascalCase = Helpers.capitalize(prop.baseName); + param.paramName = toParamName(prop.baseName); + param.baseName = prop.baseName; + param.baseType = prop.baseType; + param.dataType = prop.dataType; + param.datatypeWithEnum = prop.datatypeWithEnum; + param.description = prop.description; + param.example = prop.example; + param.isModel = prop.isModel; + param.isArray = prop.isArray; + param.isContainer = prop.isContainer; + param.isMap = prop.isMap; + param.isEnum = prop.isEnum; + param.isEnumRef = prop.isEnumRef; + param.isPrimitiveType = prop.isPrimitiveType; + param.isString = prop.isString; + param.isNumeric = prop.isNumeric; + param.isBoolean = prop.isBoolean; + param.isDate = prop.isDate; + param.isDateTime = prop.isDateTime; + param.isFreeFormObject = prop.isFreeFormObject; + param.isNullable = prop.isNullable; + param.jsonSchema = prop.jsonSchema; + param.required = prop.required; + param.vendorExtensions = prop.vendorExtensions; + param.allowableValues = prop.allowableValues; + + if (prop.required) { + ope.requiredParams.add(param); + ope.hasRequiredParams = true; + } else { + ope.optionalParams.add(param); + ope.hasOptionalParams = true; + } + ope.allParams.add(param); + } + } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index 361ac8e3762..24c5ab8ace7 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -150,6 +150,21 @@ public static void setOptionalParameters(CodegenOperation ope, Map 1) + + " hasOptionalWrapper " + ); + // hasOptionalWrapper if there is more that one optional param, after the body has been // flattened, only relevant for go test.put("hasOptionalWrapper", ope.optionalParams.size() + bodyPropsOptional > 1); diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 29d19455326..76f5d0462af 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -360,7 +360,7 @@ func (o *{{operationId}}Options) With{{#lambda.titlecase}}{{baseName}}{{/lambda. // // Deprecated: {{operationId}} is deprecated {{/isDeprecated}} -func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}*{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) ({{#returnType}}{{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}}, {{/returnType}}error) { +func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}{{^isMap}}*{{/isMap}}{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) ({{#returnType}}{{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}}, {{/returnType}}error) { {{#returnType}} var returnValue {{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}} {{/returnType}} @@ -411,7 +411,7 @@ func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{ // // Deprecated: {{operationId}} is deprecated {{/isDeprecated}} -func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}*{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) (*http.Response, []byte, error) { +func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}{{^isMap}}*{{/isMap}}{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) (*http.Response, []byte, error) { {{#vendorExtensions}} requestPath := "{{{path}}}"{{#pathParams}} requestPath = strings.ReplaceAll(requestPath, {{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, {{#x-is-custom-request}}utils.ParameterToString({{paramName}}){{/x-is-custom-request}}{{^x-is-custom-request}}url.PathEscape(utils.ParameterToString({{paramName}})){{/x-is-custom-request}}){{/pathParams}} From 2b98ad69881310f5cfaebaed0c4f060d38a09ac1 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Mon, 12 May 2025 09:08:01 +0200 Subject: [PATCH 03/14] ingestion cts is good --- .../algolia/codegen/AlgoliaGoGenerator.java | 10 ++- .../codegen/cts/AlgoliaCTSGenerator.java | 1 + .../lambda/ScreamingSnakeCaseLambda.java | 2 +- .../codegen/cts/manager/GoCTSManager.java | 7 ++ .../codegen/cts/manager/JavaCTSManager.java | 2 +- .../cts/tests/ParametersWithDataType.java | 57 +++++++++------ .../algolia/codegen/cts/tests/Snippet.java | 2 +- .../codegen/cts/tests/TestsClient.java | 2 +- .../codegen/cts/tests/TestsGenerator.java | 69 +++++++++++-------- .../codegen/cts/tests/TestsRequest.java | 2 +- templates/go/api.mustache | 37 ++++------ templates/go/model_enum.mustache | 2 +- .../go/tests/generateInnerParams.mustache | 2 +- templates/go/tests/method.mustache | 2 +- .../java/tests/generateInnerParams.mustache | 2 +- 15 files changed, 114 insertions(+), 85 deletions(-) rename generators/src/main/java/com/algolia/codegen/{ => cts}/lambda/ScreamingSnakeCaseLambda.java (91%) diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index b0659ec5fcf..b30cf24fdfd 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -1,7 +1,7 @@ package com.algolia.codegen; +import com.algolia.codegen.cts.lambda.ScreamingSnakeCaseLambda; import com.algolia.codegen.exceptions.*; -import com.algolia.codegen.lambda.ScreamingSnakeCaseLambda; import com.algolia.codegen.utils.*; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -20,6 +20,9 @@ public class AlgoliaGoGenerator extends GoClientCodegen { + // This is used for the CTS generation + private static final AlgoliaGoGenerator INSTANCE = new AlgoliaGoGenerator(); + @Override public String getName() { return "algolia-go"; @@ -30,7 +33,6 @@ public void processOpts() { String client = (String) additionalProperties.get("client"); additionalProperties.put("packageName", client.equals("query-suggestions") ? "suggestions" : Helpers.camelize(client)); - additionalProperties.put("enumClassPrefix", true); additionalProperties.put("is" + Helpers.capitalize(Helpers.camelize(client)) + "Client", true); String outputFolder = "algolia" + File.separator + client; @@ -256,4 +258,8 @@ private void flattenBody(CodegenOperation ope) { ope.allParams.add(param); } } + + public static String toEnum(String value) { + return INSTANCE.toEnumVarName(value, "String"); + } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java index c1533994a28..5ebe252fa1d 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java @@ -102,6 +102,7 @@ protected Builder addMustacheLambdas() { lambdas.put("escapeSlash", new EscapeSlashLambda()); lambdas.put("escapeJSON", new EscapeJSONLambda()); lambdas.put("replaceBacktick", new ReplaceBacktickLambda()); + lambdas.put("screamingSnakeCase", new ScreamingSnakeCaseLambda()); return lambdas; } diff --git a/generators/src/main/java/com/algolia/codegen/lambda/ScreamingSnakeCaseLambda.java b/generators/src/main/java/com/algolia/codegen/cts/lambda/ScreamingSnakeCaseLambda.java similarity index 91% rename from generators/src/main/java/com/algolia/codegen/lambda/ScreamingSnakeCaseLambda.java rename to generators/src/main/java/com/algolia/codegen/cts/lambda/ScreamingSnakeCaseLambda.java index 29b6cd61b8d..d09abaddb4a 100644 --- a/generators/src/main/java/com/algolia/codegen/lambda/ScreamingSnakeCaseLambda.java +++ b/generators/src/main/java/com/algolia/codegen/cts/lambda/ScreamingSnakeCaseLambda.java @@ -1,4 +1,4 @@ -package com.algolia.codegen.lambda; +package com.algolia.codegen.cts.lambda; import com.algolia.codegen.utils.Helpers; import com.samskivert.mustache.Mustache; diff --git a/generators/src/main/java/com/algolia/codegen/cts/manager/GoCTSManager.java b/generators/src/main/java/com/algolia/codegen/cts/manager/GoCTSManager.java index 45090a0da71..4242e5089e1 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/manager/GoCTSManager.java +++ b/generators/src/main/java/com/algolia/codegen/cts/manager/GoCTSManager.java @@ -1,7 +1,9 @@ package com.algolia.codegen.cts.manager; +import com.algolia.codegen.AlgoliaGoGenerator; import com.algolia.codegen.exceptions.GeneratorException; import com.algolia.codegen.utils.*; +import com.samskivert.mustache.Mustache.Lambda; import java.util.*; import org.openapitools.codegen.SupportingFile; @@ -40,4 +42,9 @@ public void addSnippetsSupportingFiles(List supportingFiles, Str supportingFiles.add(new SupportingFile("snippets/.golangci.mustache", output + "/go/.golangci.yml")); supportingFiles.add(new SupportingFile("snippets/go.mod.mustache", output + "/go/go.mod")); } + + @Override + public void addMustacheLambdas(Map lambdas) { + lambdas.put("toEnum", (fragment, writer) -> writer.write(AlgoliaGoGenerator.toEnum(fragment.execute()))); + } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/manager/JavaCTSManager.java b/generators/src/main/java/com/algolia/codegen/cts/manager/JavaCTSManager.java index 42e5aece0bc..886c873d0eb 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/manager/JavaCTSManager.java +++ b/generators/src/main/java/com/algolia/codegen/cts/manager/JavaCTSManager.java @@ -52,6 +52,6 @@ public void addDataToBundle(Map bundle) throws GeneratorExceptio @Override public void addMustacheLambdas(Map lambdas) { - lambdas.put("javaEnum", (fragment, writer) -> writer.write(AlgoliaJavaGenerator.toEnum(fragment.execute()))); + lambdas.put("toEnum", (fragment, writer) -> writer.write(AlgoliaJavaGenerator.toEnum(fragment.execute()))); } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java index 7cc647a1a97..9c438c0451a 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java @@ -91,7 +91,7 @@ public void enhanceParameters(Map parameters, Map 0 ) { // check for colision with other params boolean hasCollision = false; @@ -105,14 +105,19 @@ public void enhanceParameters(Map parameters, Map bodyParams = (Map) param.getValue(); - for (CodegenProperty prop : operation.bodyParam.getVars()) { - Object nestedParam = bodyParams.get(prop.baseName); - - Map paramWithType = traverseParams(prop.baseName, nestedParam, prop, "", 0, false); - parametersWithDataType.add(paramWithType); - parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + for (String nestedParam : bodyParams.keySet()) { + for (CodegenProperty prop : operation.bodyParam.getVars()) { + if (prop.baseName.equals(nestedParam)) { + Map paramWithType = traverseParams(prop.baseName, bodyParams.get(nestedParam), prop, "", 0, false); + parametersWithDataType.add(paramWithType); + parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + break; + } + } } + // sortParameters(operation.bodyParam, parametersWithDataType); } } else { Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); @@ -121,6 +126,20 @@ public void enhanceParameters(Map parameters, Map paramWithType = traverseParams(prop.baseName, parameters.get(nestedParam), prop, "", 0, false); + parametersWithDataType.add(paramWithType); + parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + break; + } + } + } + // sortParameters(operation.bodyParam, parametersWithDataType); } else { Map paramWithType = traverseParams(paramName, parameters, spec, "", 0, false); parametersWithDataType.add(paramWithType); @@ -451,19 +470,7 @@ private void handleModel( } if (language.equals("swift")) { - // Store ordered params from the spec - var orderedParams = spec - .getVars() - .stream() - .map(v -> v.baseName) - .toList(); - - // Create a map to store the indices of each string in orderedParams - Map indexMap = IntStream.range(0, orderedParams.size()) - .boxed() - .collect(Collectors.toMap(orderedParams::get, i -> i)); - - values.sort(Comparator.comparing(value -> indexMap.getOrDefault((String) value.get("key"), Integer.MAX_VALUE))); + sortParameters(spec, values); } var hasAdditionalProperties = values @@ -795,4 +802,14 @@ private boolean couldMatchEnum(Object value, CodegenProperty model) { return ((List) values).contains(value); } + + private void sortParameters(IJsonSchemaValidationProperties spec, List> parameters) { + // Store ordered params from the spec + var orderedParams = spec.getVars().stream().map(v -> v.baseName).toList(); + + // Create a map to store the indices of each string in orderedParams + Map indexMap = IntStream.range(0, orderedParams.size()).boxed().collect(Collectors.toMap(orderedParams::get, i -> i)); + + parameters.sort(Comparator.comparing(param -> indexMap.getOrDefault((String) param.get("key"), Integer.MAX_VALUE))); + } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java index d1d9078fbdf..7c4bad145a8 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java @@ -86,7 +86,7 @@ public void addMethodCall(Map context, ParametersWithDataType pa } } - TestsGenerator.setOptionalParameters(ope, context); + TestsGenerator.setOptionalParameters(ope, context, parameters); paramsType.enhanceParameters(parameters, context, ope); } catch (CTSException e) { diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java index 230dce51580..fd36085bc2e 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java @@ -158,7 +158,7 @@ public void run(Map models, Map // default to true because most api calls are asynchronous testOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); - setOptionalParameters(ope, stepOut); + setOptionalParameters(ope, stepOut, step.parameters); addRequestOptions(paramsType, step.requestOptions, stepOut); methodCount++; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index 24c5ab8ace7..8e9c5c8b8b1 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -129,44 +129,55 @@ protected void addRequestOptions(ParametersWithDataType paramsType, RequestOptio } } - public static void setOptionalParameters(CodegenOperation ope, Map test) { - long bodyPropsOptional = 0; - if (ope.bodyParam != null) { - if (ope.bodyParam.isModel && ope.bodyParam.required) { - // check for colision with other params - boolean hasCollision = false; - for (CodegenProperty prop : ope.bodyParam.getVars()) { - for (CodegenParameter param : ope.allParams) { - if (param.paramName.equals(prop.baseName)) { - hasCollision = true; - break; - } + public static void setOptionalParameters(CodegenOperation ope, Map test, Map parameters) { + int bodyPropsOptional = 0; + boolean actuallyHasOptional = false; + if (ope.bodyParam != null && ope.bodyParam.isModel) { + // check for colision with other params + boolean hasCollision = false; + Map paramBody = (Map) parameters.get(ope.bodyParam.paramName); + if (ope.allParams.size() == 1) { // edge case where the body is already flattened + paramBody = parameters; + } + + System.out.println(ope.bodyParam.paramName + " len " + ope.bodyParam.getVars().size()); + for (CodegenProperty prop : ope.bodyParam.getVars()) { + for (CodegenParameter param : ope.allParams) { + if (param.paramName.equals(prop.baseName)) { + hasCollision = true; } } - if (!hasCollision) { - bodyPropsOptional = ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); + if (paramBody != null) System.out.println( + prop.baseName + " is required " + prop.required + " " + paramBody.containsKey(prop.baseName) + ); + if (!prop.required && paramBody != null && paramBody.containsKey(prop.baseName)) { + actuallyHasOptional = true; } } + + if (!hasCollision) { + bodyPropsOptional = (int) ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); + } } - System.out.println( - ope.operationId + - " has " + - ope.allParams.size() + - " params " + - ope.requiredParams.size() + - " required params " + - ope.optionalParams.size() + - " optional params " + - bodyPropsOptional + - " bodyPropsOptional " + - (ope.optionalParams.size() + bodyPropsOptional > 1) + - " hasOptionalWrapper " - ); + int totalOptional = ope.optionalParams.size() + bodyPropsOptional; + + for (CodegenParameter param : ope.allParams) { + if (!param.required && parameters.containsKey(param.baseName)) { + actuallyHasOptional = true; + break; + } + } // hasOptionalWrapper if there is more that one optional param, after the body has been // flattened, only relevant for go - test.put("hasOptionalWrapper", ope.optionalParams.size() + bodyPropsOptional > 1); + test.put("hasOptionalWrapper", (totalOptional > 1) && actuallyHasOptional); + test.put("hasNilOptional", (totalOptional > 0) && !actuallyHasOptional); + test.put("hasOptionalRequired", (totalOptional == 1) && actuallyHasOptional); + + System.out.println(ope.operationId + " hasOptionalWrapper: " + test.get("hasOptionalWrapper")); + System.out.println("hasNilOptional: " + test.get("hasNilOptional")); + System.out.println("hasOptionalRequired: " + test.get("hasOptionalRequired")); } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java index 15fcd41fccd..bc115254ac1 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java @@ -171,7 +171,7 @@ public void run(Map models, Map test.put("hasParams", ope.getHasParams()); test.put("isHelper", isHelper); - setOptionalParameters(ope, test); + setOptionalParameters(ope, test, req.parameters); addRequestOptions(paramsType, req.requestOptions, test); // Determines whether the endpoint is expected to return a response payload deserialized diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 76f5d0462af..0ff93b7b594 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -312,7 +312,7 @@ func replaceAllObjectsToChunkBatchOptions(opts []ReplaceAllObjectsOption) []Chun // {{operationId}}Options represents the optional params for the API call. type {{operationId}}Options struct { {{#optionalParams}} - {{nameInPascalCase}} {{^isPathParam}}{{^isFreeFormObject}}{{^isMap}}*{{/isMap}}{{/isFreeFormObject}}{{/isPathParam}}{{{dataType}}} + {{nameInPascalCase}} {{^isFreeFormObject}}{{^isMap}}*{{/isMap}}{{/isFreeFormObject}}{{{dataType}}} {{/optionalParams}} } @@ -464,7 +464,7 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{/required}} {{^required}} if {{#hasOptionalParams}}optionalParams != nil && {{/hasOptionalParams}}!utils.IsNilOrEmpty({{> param_name}}) { - conf.queryParams.Set("{{baseName}}", utils.QueryParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isEnumRef}}*{{/isEnumRef}}{{/isArray}}{{/isFreeFormObject}}{{> param_name}})) + conf.queryParams.Set("{{baseName}}", utils.QueryParameterToString({{^isFreeFormObject}}*{{/isFreeFormObject}}{{> param_name}})) } {{/required}} {{/queryParams}} @@ -475,7 +475,7 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{/required}} {{^required}} if {{#hasOptionalParams}}optionalParams != nil && {{/hasOptionalParams}}!utils.IsNilOrEmpty({{> param_name}}) { - conf.headerParams["{{baseName}}"] = utils.ParameterToString({{^isFreeFormObject}}{{^isArray}}{{^isEnumRef}}*{{/isEnumRef}}{{/isArray}}{{/isFreeFormObject}}{{> param_name}}) + conf.headerParams["{{baseName}}"] = utils.ParameterToString({{^isFreeFormObject}}*{{/isFreeFormObject}}{{> param_name}}) } {{/required}} {{/headerParams}} @@ -487,7 +487,6 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{#bodyParams}} // body params - {{#required}} {{#vendorExtensions.x-flat-body}} postBody := struct{ {{#vars}} @@ -498,6 +497,11 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{#required}} {{name}}: {{nameInLowerCase}}, {{/required}} + {{^hasOptionalParams}} + {{^required}} + {{name}}: {{nameInLowerCase}}, + {{/required}} + {{/hasOptionalParams}} {{/vars}} } {{#hasOptionalParams}} @@ -511,29 +515,12 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{/hasOptionalParams}} {{/vendorExtensions.x-flat-body}} {{^vendorExtensions.x-flat-body}} - postBody := {{^required}}{{#hasOptionalParams}}optionalParams.{{/hasOptionalParams}}{{/required}}{{paramName}} - {{/vendorExtensions.x-flat-body}} + {{#required}} + postBody := {{paramName}} {{/required}} {{^required}} - {{#vendorExtensions.x-flat-body}} {{#hasOptionalParams}} - postBody := optionalParams - {{/hasOptionalParams}} - {{^hasOptionalParams}} - postBody := struct{ - {{#vars}} - {{name}} {{^required}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/required}}{{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{{dataType}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"` - {{/vars}} - }{ - {{#vars}} - {{name}}: {{nameInLowerCase}}, - {{/vars}} - } - {{/hasOptionalParams}} - {{/vendorExtensions.x-flat-body}} - {{^vendorExtensions.x-flat-body}} - {{#hasOptionalParams}} - var postBody any + var postBody any = "{}" if optionalParams != nil { postBody = optionalParams.{{nameInPascalCase}} } @@ -541,8 +528,8 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{^hasOptionalParams}} postBody := {{paramName}} {{/hasOptionalParams}} - {{/vendorExtensions.x-flat-body}} {{/required}} + {{/vendorExtensions.x-flat-body}} {{/bodyParams}} req, err := c.prepareRequest(conf.context, requestPath, http.Method{{httpMethod}}, {{^bodyParams}}nil{{/bodyParams}}{{#bodyParams}}postBody{{/bodyParams}}, conf.headerParams, conf.queryParams) if err != nil { diff --git a/templates/go/model_enum.mustache b/templates/go/model_enum.mustache index 7f85e8da1e0..ee679dd1837 100644 --- a/templates/go/model_enum.mustache +++ b/templates/go/model_enum.mustache @@ -7,7 +7,7 @@ const ( {{#enumVars}} {{^-first}} {{/-first}} - {{#enumClassPrefix}}{{#lambda.screamingSnakeCase}}{{{classname}}}{{/lambda.screamingSnakeCase}}_{{/enumClassPrefix}}{{name}} {{{classname}}} = {{{value}}} + {{#lambda.screamingSnakeCase}}{{{classname}}}{{/lambda.screamingSnakeCase}}_{{name}} {{{classname}}} = {{{value}}} {{/enumVars}} {{/allowableValues}} ) diff --git a/templates/go/tests/generateInnerParams.mustache b/templates/go/tests/generateInnerParams.mustache index 7a4ff09f866..47bd315c52f 100644 --- a/templates/go/tests/generateInnerParams.mustache +++ b/templates/go/tests/generateInnerParams.mustache @@ -1,3 +1,3 @@ -{{#isVerbatim}}{{{value}}}{{/isVerbatim}}{{#isHelper}}{{#goFunctionalParam}}{{clientPrefix}}.With{{#lambda.pascalcase}}{{key}}{{/lambda.pascalcase}}({{/goFunctionalParam}}{{/isHelper}}{{#isNull}}{{#inClientTest}}tests.ZeroValue[{{#isNullObject}}*{{clientPrefix}}.{{/isNullObject}}{{objectName}}](){{/inClientTest}}{{^inClientTest}}nil{{/inClientTest}}{{/isNull}}{{#isString}}"{{#lambda.escapeQuotes}}{{{value}}}{{/lambda.escapeQuotes}}"{{/isString}}{{#isInteger}}{{{value}}}{{/isInteger}}{{#isLong}}{{{value}}}{{/isLong}}{{#isDouble}}{{{value}}}{{/isDouble}}{{#isBoolean}}{{{value}}}{{/isBoolean}}{{#isEnum}}{{clientPrefix}}.{{objectName}}("{{{value}}}"){{/isEnum}}{{#isArray}} +{{#isVerbatim}}{{{value}}}{{/isVerbatim}}{{#isHelper}}{{#goFunctionalParam}}{{clientPrefix}}.With{{#lambda.pascalcase}}{{key}}{{/lambda.pascalcase}}({{/goFunctionalParam}}{{/isHelper}}{{#isNull}}{{#inClientTest}}tests.ZeroValue[{{#isNullObject}}*{{clientPrefix}}.{{/isNullObject}}{{objectName}}](){{/inClientTest}}{{^inClientTest}}nil{{/inClientTest}}{{/isNull}}{{#isString}}"{{#lambda.escapeQuotes}}{{{value}}}{{/lambda.escapeQuotes}}"{{/isString}}{{#isInteger}}{{{value}}}{{/isInteger}}{{#isLong}}{{{value}}}{{/isLong}}{{#isDouble}}{{{value}}}{{/isDouble}}{{#isBoolean}}{{{value}}}{{/isBoolean}}{{#isEnum}}{{clientPrefix}}.{{#lambda.screamingSnakeCase}}{{{objectName}}}{{/lambda.screamingSnakeCase}}_{{#lambda.toEnum}}{{value}}{{/lambda.toEnum}}{{/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}}{{#isHelper}}{{#goFunctionalParam}}){{/goFunctionalParam}}{{/isHelper}} \ No newline at end of file diff --git a/templates/go/tests/method.mustache b/templates/go/tests/method.mustache index d10c334b641..87550e6b025 100644 --- a/templates/go/tests/method.mustache +++ b/templates/go/tests/method.mustache @@ -1 +1 @@ -client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} {{#hasOptionalWrapper}}{{clientPrefix}}.New{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Options(){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/required}}{{/parametersWithDataType}}{{#requestOptions}},{{/requestOptions}}{{/hasOptionalWrapper}}{{#requestOptions}}{{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} {{#timeouts.read}} ,{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), {{/timeouts.read}} {{#timeouts.write}} {{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), {{/timeouts.write}} {{#timeouts.connect}} ,{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), {{/timeouts.connect}} {{/requestOptions}}) \ No newline at end of file +client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} {{#hasOptionalWrapper}}{{clientPrefix}}.New{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Options(){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/required}}{{/parametersWithDataType}}{{#requestOptions}},{{/requestOptions}}{{/hasOptionalWrapper}}{{#hasNilOptional}}nil,{{/hasNilOptional}}{{#hasOptionalRequired}}{{#parametersWithDataType}}{{^required}}{{^isFreeFormObject}}utils.ToPtr({{/isFreeFormObject}}{{> tests/generateParams}}{{^isFreeFormObject}}){{/isFreeFormObject}},{{/required}}{{/parametersWithDataType}}{{/hasOptionalRequired}}{{#requestOptions}}{{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} {{#timeouts.read}} ,{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), {{/timeouts.read}} {{#timeouts.write}} {{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), {{/timeouts.write}} {{#timeouts.connect}} ,{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), {{/timeouts.connect}} {{/requestOptions}}) \ No newline at end of file diff --git a/templates/java/tests/generateInnerParams.mustache b/templates/java/tests/generateInnerParams.mustache index 81796c232b2..a6deb95d191 100644 --- a/templates/java/tests/generateInnerParams.mustache +++ b/templates/java/tests/generateInnerParams.mustache @@ -20,7 +20,7 @@ {{{value}}} {{/isBoolean}} {{#isEnum}} - {{{objectName}}}.{{#lambda.javaEnum}}{{{value}}}{{/lambda.javaEnum}} + {{{objectName}}}.{{#lambda.toEnum}}{{{value}}}{{/lambda.toEnum}} {{/isEnum}} {{#isArray}} Arrays.asList({{#value}}{{> tests/generateParams}}{{^-last}},{{/-last}}{{/value}}) From 0d540fafc0d9ea090df2fe1e2b2d23c25ce65987 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 13 May 2025 03:41:59 +0200 Subject: [PATCH 04/14] cts is generating --- .../algolia/codegen/AlgoliaGoGenerator.java | 40 +++++++----- .../cts/tests/ParametersWithDataType.java | 24 +++---- .../algolia/codegen/cts/tests/Snippet.java | 5 +- .../codegen/cts/tests/TestsClient.java | 9 ++- .../codegen/cts/tests/TestsGenerator.java | 65 +++++++++++-------- .../codegen/cts/tests/TestsRequest.java | 2 +- templates/go/client.mustache | 20 +++++- templates/go/tests/inlineOptional.mustache | 1 + templates/go/tests/method.mustache | 2 +- templates/go/tests/nilWrapper.mustache | 1 + templates/go/tests/optionalWrapper.mustache | 1 + templates/go/tests/requestOptions.mustache | 1 + templates/go/tests/requests/requests.mustache | 3 + templates/go/tests/requiredParams.mustache | 1 + tests/CTS/requests/abtesting/addABTests.json | 8 +-- .../requests/abtesting/scheduleABTest.json | 12 ++-- 16 files changed, 119 insertions(+), 76 deletions(-) create mode 100644 templates/go/tests/inlineOptional.mustache create mode 100644 templates/go/tests/nilWrapper.mustache create mode 100644 templates/go/tests/optionalWrapper.mustache create mode 100644 templates/go/tests/requestOptions.mustache create mode 100644 templates/go/tests/requiredParams.mustache diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index b30cf24fdfd..460613e44ee 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -190,22 +190,11 @@ private void flattenBody(CodegenOperation ope) { return; } - // check for colision with other params - for (CodegenProperty prop : bodyParam.getVars()) { - for (CodegenParameter param : ope.allParams) { - if (param.paramName.equals(prop.baseName)) { - System.out.println( - "Operation " + - ope.operationId + - " has body param " + - bodyParam.paramName + - " in colision with param " + - param.paramName + - ", skipping flattening" - ); - return; - } - } + if (!canFlattenBody(ope)) { + System.out.println( + "Operation " + ope.operationId + " has body param " + bodyParam.paramName + " in colision with a parameter, skipping flattening" + ); + return; } bodyParam.vendorExtensions.put("x-flat-body", bodyParam.getVars().size() > 0); @@ -259,6 +248,25 @@ private void flattenBody(CodegenOperation ope) { } } + public static boolean canFlattenBody(CodegenOperation ope) { + if (ope.bodyParam == null || !ope.bodyParam.isModel) { + return false; + } + + if (ope.allParams.size() == 1) { + return true; + } + + for (CodegenProperty prop : ope.bodyParam.getVars()) { + for (CodegenParameter param : ope.allParams) { + if (param.paramName.equals(prop.baseName)) { + return false; + } + } + } + return true; + } + public static String toEnum(String value) { return INSTANCE.toEnumVarName(value, "String"); } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java index 9c438c0451a..de0bfbb4828 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java @@ -1,5 +1,6 @@ package com.algolia.codegen.cts.tests; +import com.algolia.codegen.AlgoliaGoGenerator; import com.algolia.codegen.AlgoliaSwiftGenerator; import com.algolia.codegen.exceptions.*; import com.algolia.codegen.utils.*; @@ -93,19 +94,8 @@ public void enhanceParameters(Map parameters, Map 0 ) { - // check for colision with other params - boolean hasCollision = false; - for (CodegenProperty prop : operation.bodyParam.getVars()) { - for (CodegenParameter otherParam : operation.allParams) { - if (otherParam.paramName.equals(prop.baseName)) { - hasCollision = true; - break; - } - } - } - if (!hasCollision) { + if (AlgoliaGoGenerator.canFlattenBody(operation)) { // flatten the body params by skipping one level - System.out.println("Flatten the body in " + operation.operationId); Map bodyParams = (Map) param.getValue(); for (String nestedParam : bodyParams.keySet()) { for (CodegenProperty prop : operation.bodyParam.getVars()) { @@ -117,7 +107,11 @@ public void enhanceParameters(Map parameters, Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); + parametersWithDataType.add(paramWithType); + parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); } } else { Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); @@ -126,9 +120,8 @@ public void enhanceParameters(Map parameters, Map 0) { // also flatten when the body is the only parameter - System.out.println("Skipping unique body in " + operation.operationId); for (String nestedParam : parameters.keySet()) { for (CodegenProperty prop : operation.bodyParam.getVars()) { if (prop.baseName.equals(nestedParam)) { @@ -139,7 +132,6 @@ public void enhanceParameters(Map parameters, Map paramWithType = traverseParams(paramName, parameters, spec, "", 0, false); parametersWithDataType.add(paramWithType); diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java index 7c4bad145a8..4bcba3b6a47 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java @@ -53,11 +53,12 @@ public void addMethodCall(Map context, ParametersWithDataType pa } try { + boolean isHelper = (boolean) ope.vendorExtensions.getOrDefault("x-helper", false); context.put("isGeneric", (boolean) ope.vendorExtensions.getOrDefault("x-is-generic", false)); context.put("isCustomRequest", Helpers.CUSTOM_METHODS.contains(ope.operationIdOriginal)); context.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); context.put("hasParams", ope.getHasParams()); - context.put("isHelper", (boolean) ope.vendorExtensions.getOrDefault("x-helper", false)); + context.put("isHelper", isHelper); context.put("hasRequestOptions", requestOptions != null); if (requestOptions != null) { @@ -86,7 +87,7 @@ public void addMethodCall(Map context, ParametersWithDataType pa } } - TestsGenerator.setOptionalParameters(ope, context, parameters); + TestsGenerator.setOptionalParameters(ope, context, parameters, isHelper); paramsType.enhanceParameters(parameters, context, ope); } catch (CTSException e) { diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java index fd36085bc2e..fd64ab6a0bb 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java @@ -80,6 +80,12 @@ public void run(Map models, Map testOut.put("autoCreateClient", test.autoCreateClient); testOut.put("useEchoRequester", true); testOut.put("isBenchmark", withBenchmark); + + if (language.equals("go") && "`addApiKey` throws with invalid parameters".equals(test.testName)) { + // skip this test because the body is flattened in go + continue; + } + for (Step step : test.steps) { Map stepOut = new HashMap<>(); if (step.times > 1) stepOut.put("times", step.times); @@ -158,7 +164,7 @@ public void run(Map models, Map // default to true because most api calls are asynchronous testOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); - setOptionalParameters(ope, stepOut, step.parameters); + setOptionalParameters(ope, stepOut, step.parameters, isHelper); addRequestOptions(paramsType, step.requestOptions, stepOut); methodCount++; @@ -180,6 +186,7 @@ public void run(Map models, Map item -> (boolean) item.getOrDefault("isNullObject", false) || (boolean) item.getOrDefault("isNull", false) ); if (isNotTestable) { + System.out.println("Skipping test " + test.testName + " for " + language + " because of nil object"); continue; } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index 8e9c5c8b8b1..605b0996c2d 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -1,5 +1,6 @@ package com.algolia.codegen.cts.tests; +import com.algolia.codegen.AlgoliaGoGenerator; import com.algolia.codegen.cts.manager.CTSManager; import com.algolia.codegen.exceptions.CTSException; import com.algolia.codegen.utils.*; @@ -129,36 +130,29 @@ protected void addRequestOptions(ParametersWithDataType paramsType, RequestOptio } } - public static void setOptionalParameters(CodegenOperation ope, Map test, Map parameters) { + public static void setOptionalParameters( + CodegenOperation ope, + Map test, + Map parameters, + boolean isHelper + ) { int bodyPropsOptional = 0; boolean actuallyHasOptional = false; - if (ope.bodyParam != null && ope.bodyParam.isModel) { - // check for colision with other params - boolean hasCollision = false; - Map paramBody = (Map) parameters.get(ope.bodyParam.paramName); - if (ope.allParams.size() == 1) { // edge case where the body is already flattened - paramBody = parameters; + + if (AlgoliaGoGenerator.canFlattenBody(ope)) { + bodyPropsOptional = (int) ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); + + // edge case where the body is already flattened + Map paramBody = paramBody = parameters; + if (ope.allParams.size() > 1) { + paramBody = (Map) parameters.get(ope.bodyParam.paramName); } - System.out.println(ope.bodyParam.paramName + " len " + ope.bodyParam.getVars().size()); for (CodegenProperty prop : ope.bodyParam.getVars()) { - for (CodegenParameter param : ope.allParams) { - if (param.paramName.equals(prop.baseName)) { - hasCollision = true; - } - } - - if (paramBody != null) System.out.println( - prop.baseName + " is required " + prop.required + " " + paramBody.containsKey(prop.baseName) - ); if (!prop.required && paramBody != null && paramBody.containsKey(prop.baseName)) { actuallyHasOptional = true; } } - - if (!hasCollision) { - bodyPropsOptional = (int) ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); - } } int totalOptional = ope.optionalParams.size() + bodyPropsOptional; @@ -170,14 +164,29 @@ public static void setOptionalParameters(CodegenOperation ope, Map 1) && actuallyHasOptional); - test.put("hasNilOptional", (totalOptional > 0) && !actuallyHasOptional); - test.put("hasOptionalRequired", (totalOptional == 1) && actuallyHasOptional); - - System.out.println(ope.operationId + " hasOptionalWrapper: " + test.get("hasOptionalWrapper")); - System.out.println("hasNilOptional: " + test.get("hasNilOptional")); - System.out.println("hasOptionalRequired: " + test.get("hasOptionalRequired")); + test.put("hasOptionalWrapper", totalOptional > 1 && actuallyHasOptional && !isSFFV); + test.put("hasInlineOptional", (totalOptional == 1 || isSFFV) && actuallyHasOptional); + test.put("hasNilOptional", totalOptional > 0 && !actuallyHasOptional && !isHelper); + + System.out.println( + ope.operationId + + " hasOptionalWrapper: " + + test.get("hasOptionalWrapper") + + " hasNilOptional: " + + test.get("hasNilOptional") + + " hasInlineOptional: " + + test.get("hasInlineOptional") + + " totalOptional: " + + totalOptional + + " actuallyHasOptional: " + + actuallyHasOptional + + " bodyPropsOptional: " + + bodyPropsOptional + ); } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java index bc115254ac1..25b258f58b3 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java @@ -171,7 +171,7 @@ public void run(Map models, Map test.put("hasParams", ope.getHasParams()); test.put("isHelper", isHelper); - setOptionalParameters(ope, test, req.parameters); + setOptionalParameters(ope, test, req.parameters, isHelper); addRequestOptions(paramsType, req.requestOptions, test); // Determines whether the endpoint is expected to return a response payload deserialized diff --git a/templates/go/client.mustache b/templates/go/client.mustache index a06c7fdd4e1..04d91241630 100644 --- a/templates/go/client.mustache +++ b/templates/go/client.mustache @@ -312,7 +312,25 @@ func reportError(format string, a ...any) error { return fmt.Errorf(format, a...) } -// Set request body from an any. +// A wrapper for strict JSON decoding +func newStrictDecoder(data []byte) *json.Decoder { + dec := json.NewDecoder(bytes.NewBuffer(data)) + dec.DisallowUnknownFields() + return dec +} + +// A wrapper for validating a struct, returns nil if value is not a struct +func validateStruct(v any) error { + err := validator.New().Struct(v) + validationErrors, ok := err.(validator.ValidationErrors) + if ok && len(validationErrors) > 0 { + return validationErrors + } + + return nil +} + +// Set request body from an any func setBody(body any, c compression.Compression) (*bytes.Buffer, error) { if body == nil { return nil, nil diff --git a/templates/go/tests/inlineOptional.mustache b/templates/go/tests/inlineOptional.mustache new file mode 100644 index 00000000000..0de14afe81d --- /dev/null +++ b/templates/go/tests/inlineOptional.mustache @@ -0,0 +1 @@ +{{#hasInlineOptional}}{{#parametersWithDataType}}{{^required}}{{^isFreeFormObject}}{{^isObject}}utils.ToPtr({{/isObject}}{{/isFreeFormObject}}{{> tests/generateParams}}{{^isFreeFormObject}}{{^isObject}}){{/isObject}}{{/isFreeFormObject}},{{/required}}{{/parametersWithDataType}}{{/hasInlineOptional}} \ No newline at end of file diff --git a/templates/go/tests/method.mustache b/templates/go/tests/method.mustache index 87550e6b025..f8f06ebbe70 100644 --- a/templates/go/tests/method.mustache +++ b/templates/go/tests/method.mustache @@ -1 +1 @@ -client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} {{#hasOptionalWrapper}}{{clientPrefix}}.New{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Options(){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/required}}{{/parametersWithDataType}}{{#requestOptions}},{{/requestOptions}}{{/hasOptionalWrapper}}{{#hasNilOptional}}nil,{{/hasNilOptional}}{{#hasOptionalRequired}}{{#parametersWithDataType}}{{^required}}{{^isFreeFormObject}}utils.ToPtr({{/isFreeFormObject}}{{> tests/generateParams}}{{^isFreeFormObject}}){{/isFreeFormObject}},{{/required}}{{/parametersWithDataType}}{{/hasOptionalRequired}}{{#requestOptions}}{{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} {{#timeouts.read}} ,{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), {{/timeouts.read}} {{#timeouts.write}} {{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), {{/timeouts.write}} {{#timeouts.connect}} ,{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), {{/timeouts.connect}} {{/requestOptions}}) \ No newline at end of file +client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{> tests/requiredParams}}{{> tests/optionalWrapper}}{{> tests/nilWrapper}}{{> tests/inlineOptional}}{{> tests/requestOptions}}) diff --git a/templates/go/tests/nilWrapper.mustache b/templates/go/tests/nilWrapper.mustache new file mode 100644 index 00000000000..d570014b6e5 --- /dev/null +++ b/templates/go/tests/nilWrapper.mustache @@ -0,0 +1 @@ +{{#hasNilOptional}}nil,{{/hasNilOptional}} \ No newline at end of file diff --git a/templates/go/tests/optionalWrapper.mustache b/templates/go/tests/optionalWrapper.mustache new file mode 100644 index 00000000000..ee1ba5c6315 --- /dev/null +++ b/templates/go/tests/optionalWrapper.mustache @@ -0,0 +1 @@ +{{#hasOptionalWrapper}}{{^isHelper}}{{clientPrefix}}.New{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Options(){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/required}}{{/parametersWithDataType}}{{/isHelper}}{{#isHelper}}{{#parametersWithDataType}}{{^required}}{{> tests/generateParams}}{{^-last}},{{/-last}} {{/required}}{{/parametersWithDataType}}{{/isHelper}}{{#requestOptions}},{{/requestOptions}}{{/hasOptionalWrapper}} \ No newline at end of file diff --git a/templates/go/tests/requestOptions.mustache b/templates/go/tests/requestOptions.mustache new file mode 100644 index 00000000000..ebba80c50fa --- /dev/null +++ b/templates/go/tests/requestOptions.mustache @@ -0,0 +1 @@ +{{#requestOptions}}{{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} {{#timeouts.read}} ,{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), {{/timeouts.read}} {{#timeouts.write}} {{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), {{/timeouts.write}} {{#timeouts.connect}} ,{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), {{/timeouts.connect}} {{/requestOptions}} \ No newline at end of file diff --git a/templates/go/tests/requests/requests.mustache b/templates/go/tests/requests/requests.mustache index d1bb0c837b3..25d1078e52d 100644 --- a/templates/go/tests/requests/requests.mustache +++ b/templates/go/tests/requests/requests.mustache @@ -43,6 +43,9 @@ func Test{{#lambda.titlecase}}{{clientPrefix}}{{/lambda.titlecase}}_{{#lambda.ti {{#tests}} t.Run("{{{testName}}}", func(t *testing.T) { + // hasOptionalWrapper {{hasOptionalWrapper}} + // hasInlineOptional {{hasInlineOptional}} + // hasNilOptional {{hasNilOptional}} {{#hasResponse}}_, {{/hasResponse}}err := {{> tests/method}} require.NoError(t, err) diff --git a/templates/go/tests/requiredParams.mustache b/templates/go/tests/requiredParams.mustache new file mode 100644 index 00000000000..b93d5910466 --- /dev/null +++ b/templates/go/tests/requiredParams.mustache @@ -0,0 +1 @@ +{{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} \ No newline at end of file diff --git a/tests/CTS/requests/abtesting/addABTests.json b/tests/CTS/requests/abtesting/addABTests.json index 2c25924d4bf..3a68a9ed126 100644 --- a/tests/CTS/requests/abtesting/addABTests.json +++ b/tests/CTS/requests/abtesting/addABTests.json @@ -2,7 +2,6 @@ { "testName": "addABTests with minimal parameters", "parameters": { - "endAt": "2022-12-31T00:00:00.000Z", "name": "myABTest", "variants": [ { @@ -13,13 +12,13 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "endAt": "2022-12-31T00:00:00.000Z" }, "request": { "path": "/2/abtests", "method": "POST", "body": { - "endAt": "2022-12-31T00:00:00.000Z", "name": "myABTest", "variants": [ { @@ -30,7 +29,8 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "endAt": "2022-12-31T00:00:00.000Z" } } } diff --git a/tests/CTS/requests/abtesting/scheduleABTest.json b/tests/CTS/requests/abtesting/scheduleABTest.json index 87a1c0bc216..f51988b34bc 100644 --- a/tests/CTS/requests/abtesting/scheduleABTest.json +++ b/tests/CTS/requests/abtesting/scheduleABTest.json @@ -2,8 +2,6 @@ { "testName": "scheduleABTest with minimal parameters", "parameters": { - "endAt": "2022-12-31T00:00:00.000Z", - "scheduledAt": "2022-11-31T00:00:00.000Z", "name": "myABTest", "variants": [ { @@ -14,14 +12,14 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "scheduledAt": "2022-11-31T00:00:00.000Z", + "endAt": "2022-12-31T00:00:00.000Z" }, "request": { "path": "/2/abtests/schedule", "method": "POST", "body": { - "endAt": "2022-12-31T00:00:00.000Z", - "scheduledAt": "2022-11-31T00:00:00.000Z", "name": "myABTest", "variants": [ { @@ -32,7 +30,9 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "scheduledAt": "2022-11-31T00:00:00.000Z", + "endAt": "2022-12-31T00:00:00.000Z" } } } From 40793bb793835516d38d738917f0db7b1912aed5 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 13 May 2025 11:23:55 +0200 Subject: [PATCH 05/14] cts is working --- .../codegen/cts/tests/TestsGenerator.java | 33 ++++++++----------- templates/go/api.mustache | 6 ++-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index 605b0996c2d..40a48607e2a 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -138,13 +138,16 @@ public static void setOptionalParameters( ) { int bodyPropsOptional = 0; boolean actuallyHasOptional = false; + boolean isBodyTooBig = false; + boolean alreadyInlinedBody = ope.allParams.size() == 1 && ope.bodyParam != null && ope.bodyParam.isModel; if (AlgoliaGoGenerator.canFlattenBody(ope)) { bodyPropsOptional = (int) ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); + isBodyTooBig = ope.bodyParam.getVars().size() == 0; // edge case where the body is already flattened Map paramBody = paramBody = parameters; - if (ope.allParams.size() > 1) { + if (!alreadyInlinedBody) { paramBody = (Map) parameters.get(ope.bodyParam.paramName); } @@ -164,29 +167,21 @@ public static void setOptionalParameters( } } + boolean isBodyRequired = (ope.bodyParam != null && ope.bodyParam.required); + // I can't figure out the correct condition for this one so it's harcoded for now boolean isSFFV = ope.operationId.equals("searchForFacetValues") && "composition".equals(ope.tags.get(0).getName()); // hasOptionalWrapper if there is more that one optional param, after the body has been // flattened, only relevant for go test.put("hasOptionalWrapper", totalOptional > 1 && actuallyHasOptional && !isSFFV); - test.put("hasInlineOptional", (totalOptional == 1 || isSFFV) && actuallyHasOptional); - test.put("hasNilOptional", totalOptional > 0 && !actuallyHasOptional && !isHelper); - - System.out.println( - ope.operationId + - " hasOptionalWrapper: " + - test.get("hasOptionalWrapper") + - " hasNilOptional: " + - test.get("hasNilOptional") + - " hasInlineOptional: " + - test.get("hasInlineOptional") + - " totalOptional: " + - totalOptional + - " actuallyHasOptional: " + - actuallyHasOptional + - " bodyPropsOptional: " + - bodyPropsOptional - ); + test.put("hasInlineOptional", ((totalOptional == 1 || isSFFV) && actuallyHasOptional) || isBodyTooBig); + if (isBodyTooBig) { + boolean isBodySet = alreadyInlinedBody ? parameters.size() > 0 : parameters.containsKey(ope.bodyParam.paramName); + System.out.println("isBodySet: " + isBodySet); + test.put("hasNilOptional", isBodyRequired ? totalOptional > 0 && !actuallyHasOptional : !isBodySet); + } else { + test.put("hasNilOptional", totalOptional > 0 && !actuallyHasOptional && !isHelper); + } } } diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 0ff93b7b594..e7cc43163a1 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -519,14 +519,16 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ postBody := {{paramName}} {{/required}} {{^required}} - {{#hasOptionalParams}} var postBody any = "{}" + {{#hasOptionalParams}} if optionalParams != nil { postBody = optionalParams.{{nameInPascalCase}} } {{/hasOptionalParams}} {{^hasOptionalParams}} - postBody := {{paramName}} + if !utils.IsNilOrEmpty({{paramName}}) { + postBody = {{paramName}} + } {{/hasOptionalParams}} {{/required}} {{/vendorExtensions.x-flat-body}} From 442ebf02fdd7a78eca8f35cb9041e38168b28601 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 13 May 2025 12:08:32 +0200 Subject: [PATCH 06/14] only for go --- .../com/algolia/codegen/cts/lambda/DynamicSnippetLambda.java | 4 +++- .../src/main/java/com/algolia/codegen/cts/tests/Snippet.java | 5 +++-- .../com/algolia/codegen/cts/tests/SnippetsGenerator.java | 2 +- .../main/java/com/algolia/codegen/cts/tests/TestsClient.java | 2 +- .../java/com/algolia/codegen/cts/tests/TestsGenerator.java | 4 ++++ .../java/com/algolia/codegen/cts/tests/TestsRequest.java | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/cts/lambda/DynamicSnippetLambda.java b/generators/src/main/java/com/algolia/codegen/cts/lambda/DynamicSnippetLambda.java index e5647d767a9..2609da2254e 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/lambda/DynamicSnippetLambda.java +++ b/generators/src/main/java/com/algolia/codegen/cts/lambda/DynamicSnippetLambda.java @@ -32,6 +32,7 @@ public class DynamicSnippetLambda implements Mustache.Lambda { private final Map operations; private final Map snippets; + private final String language; public DynamicSnippetLambda( DefaultCodegen generator, @@ -41,6 +42,7 @@ public DynamicSnippetLambda( String client ) { this.operations = operations; + this.language = language; this.paramsType = new ParametersWithDataType(models, language, client, true); JsonNode snippetsFile = Helpers.readJsonFile("tests/CTS/guides/" + client + ".json"); @@ -74,7 +76,7 @@ public void execute(Template.Fragment fragment, Writer writer) throws IOExceptio // set the method attributes Map context = (Map) fragment.context(); - snippet.addMethodCall(context, paramsType, operation); + snippet.addMethodCall(language, context, paramsType, operation); writer.write(adaptor.compileTemplate(executor, context, "tests/method.mustache")); } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java index 4bcba3b6a47..adfdcb89ef9 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java @@ -39,7 +39,8 @@ public String toString() { return sb.toString(); } - public void addMethodCall(Map context, ParametersWithDataType paramsType, CodegenOperation ope) throws CTSException { + public void addMethodCall(String language, Map context, ParametersWithDataType paramsType, CodegenOperation ope) + throws CTSException { // for dynamic snippets, we need to reset the context because the order of generation is random context.put("method", method); context.put("returnType", null); @@ -87,7 +88,7 @@ public void addMethodCall(Map context, ParametersWithDataType pa } } - TestsGenerator.setOptionalParameters(ope, context, parameters, isHelper); + TestsGenerator.setOptionalParameters(language, ope, context, parameters, isHelper); paramsType.enhanceParameters(parameters, context, ope); } catch (CTSException e) { diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java index baa7f7cbf29..f4f4e81b297 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java @@ -124,7 +124,7 @@ public void run(Map models, Map test.put("testName", ops.size() > 1 ? name : "default"); test.put("description", name); test.put("testIndex", i == 0 ? "" : i); - snippet.addMethodCall(test, paramsType, ope); + snippet.addMethodCall(language, test, paramsType, ope); addRequestOptions(paramsType, snippet.requestOptions, test); tests.add(test); } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java index fd64ab6a0bb..95d0bc6be18 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java @@ -164,7 +164,7 @@ public void run(Map models, Map // default to true because most api calls are asynchronous testOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); - setOptionalParameters(ope, stepOut, step.parameters, isHelper); + setOptionalParameters(language, ope, stepOut, step.parameters, isHelper); addRequestOptions(paramsType, step.requestOptions, stepOut); methodCount++; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index 40a48607e2a..106fea87e7c 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -131,11 +131,15 @@ protected void addRequestOptions(ParametersWithDataType paramsType, RequestOptio } public static void setOptionalParameters( + String language, CodegenOperation ope, Map test, Map parameters, boolean isHelper ) { + if (!language.equals("go")) { + return; + } int bodyPropsOptional = 0; boolean actuallyHasOptional = false; boolean isBodyTooBig = false; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java index 25b258f58b3..338dca17ea6 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java @@ -171,7 +171,7 @@ public void run(Map models, Map test.put("hasParams", ope.getHasParams()); test.put("isHelper", isHelper); - setOptionalParameters(ope, test, req.parameters, isHelper); + setOptionalParameters(language, ope, test, req.parameters, isHelper); addRequestOptions(paramsType, req.requestOptions, test); // Determines whether the endpoint is expected to return a response payload deserialized From b28ecf5b1be8932112cd431e2d9bd0def4e3a394 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 13 May 2025 13:02:31 +0200 Subject: [PATCH 07/14] suppost verbatim --- .../codegen/cts/tests/TestsGenerator.java | 16 +++++++++++----- templates/go/tests/inlineOptional.mustache | 2 +- templates/go/tests/method.mustache | 2 +- templates/go/tests/requests/requests.mustache | 3 --- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index 106fea87e7c..a6ac9543825 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -143,7 +143,8 @@ public static void setOptionalParameters( int bodyPropsOptional = 0; boolean actuallyHasOptional = false; boolean isBodyTooBig = false; - boolean alreadyInlinedBody = ope.allParams.size() == 1 && ope.bodyParam != null && ope.bodyParam.isModel; + boolean isBodyRequired = (ope.bodyParam != null && ope.bodyParam.required); + boolean alreadyInlinedBody = ope.allParams.size() == 1 && ope.bodyParam != null && !ope.bodyParam.isArray; if (AlgoliaGoGenerator.canFlattenBody(ope)) { bodyPropsOptional = (int) ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); @@ -152,7 +153,14 @@ public static void setOptionalParameters( // edge case where the body is already flattened Map paramBody = paramBody = parameters; if (!alreadyInlinedBody) { - paramBody = (Map) parameters.get(ope.bodyParam.paramName); + Object paramBodyObj = parameters.get(ope.bodyParam.paramName); + if (paramBodyObj instanceof String) { + // this is a verbatim paramater, we use it as is + System.out.println(ope.operationId + " is a verbatim body " + paramBodyObj); + actuallyHasOptional = !isBodyRequired; + } else { + paramBody = (Map) parameters.get(ope.bodyParam.paramName); + } } for (CodegenProperty prop : ope.bodyParam.getVars()) { @@ -171,8 +179,6 @@ public static void setOptionalParameters( } } - boolean isBodyRequired = (ope.bodyParam != null && ope.bodyParam.required); - // I can't figure out the correct condition for this one so it's harcoded for now boolean isSFFV = ope.operationId.equals("searchForFacetValues") && "composition".equals(ope.tags.get(0).getName()); @@ -182,7 +188,7 @@ public static void setOptionalParameters( test.put("hasInlineOptional", ((totalOptional == 1 || isSFFV) && actuallyHasOptional) || isBodyTooBig); if (isBodyTooBig) { boolean isBodySet = alreadyInlinedBody ? parameters.size() > 0 : parameters.containsKey(ope.bodyParam.paramName); - System.out.println("isBodySet: " + isBodySet); + System.out.println(ope.operationId + " isBodySet: " + isBodySet + " isBodyRequired: " + isBodyRequired); test.put("hasNilOptional", isBodyRequired ? totalOptional > 0 && !actuallyHasOptional : !isBodySet); } else { test.put("hasNilOptional", totalOptional > 0 && !actuallyHasOptional && !isHelper); diff --git a/templates/go/tests/inlineOptional.mustache b/templates/go/tests/inlineOptional.mustache index 0de14afe81d..97ac147fe15 100644 --- a/templates/go/tests/inlineOptional.mustache +++ b/templates/go/tests/inlineOptional.mustache @@ -1 +1 @@ -{{#hasInlineOptional}}{{#parametersWithDataType}}{{^required}}{{^isFreeFormObject}}{{^isObject}}utils.ToPtr({{/isObject}}{{/isFreeFormObject}}{{> tests/generateParams}}{{^isFreeFormObject}}{{^isObject}}){{/isObject}}{{/isFreeFormObject}},{{/required}}{{/parametersWithDataType}}{{/hasInlineOptional}} \ No newline at end of file +{{#hasInlineOptional}}{{#parametersWithDataType}}{{^required}}{{^isFreeFormObject}}{{^isObject}}{{^isVerbatim}}utils.ToPtr({{/isVerbatim}}{{/isObject}}{{/isFreeFormObject}}{{> tests/generateParams}}{{^isFreeFormObject}}{{^isObject}}{{^isVerbatim}}){{/isVerbatim}}{{/isObject}}{{/isFreeFormObject}},{{/required}}{{/parametersWithDataType}}{{/hasInlineOptional}} \ No newline at end of file diff --git a/templates/go/tests/method.mustache b/templates/go/tests/method.mustache index f8f06ebbe70..8a4e9bda924 100644 --- a/templates/go/tests/method.mustache +++ b/templates/go/tests/method.mustache @@ -1 +1 @@ -client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{> tests/requiredParams}}{{> tests/optionalWrapper}}{{> tests/nilWrapper}}{{> tests/inlineOptional}}{{> tests/requestOptions}}) +client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{> tests/requiredParams}}{{> tests/optionalWrapper}}{{> tests/nilWrapper}}{{> tests/inlineOptional}}{{> tests/requestOptions}}) \ No newline at end of file diff --git a/templates/go/tests/requests/requests.mustache b/templates/go/tests/requests/requests.mustache index 25d1078e52d..d1bb0c837b3 100644 --- a/templates/go/tests/requests/requests.mustache +++ b/templates/go/tests/requests/requests.mustache @@ -43,9 +43,6 @@ func Test{{#lambda.titlecase}}{{clientPrefix}}{{/lambda.titlecase}}_{{#lambda.ti {{#tests}} t.Run("{{{testName}}}", func(t *testing.T) { - // hasOptionalWrapper {{hasOptionalWrapper}} - // hasInlineOptional {{hasInlineOptional}} - // hasNilOptional {{hasNilOptional}} {{#hasResponse}}_, {{/hasResponse}}err := {{> tests/method}} require.NoError(t, err) From 565545132f81f564ac92e2ac8733ba14379e13e3 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 13 May 2025 14:09:21 +0200 Subject: [PATCH 08/14] self review --- .../algolia/codegen/AlgoliaGoGenerator.java | 33 ++-------- .../cts/tests/ParametersWithDataType.java | 33 ++++------ .../codegen/cts/tests/TestsGenerator.java | 62 +++++++++---------- templates/go/api.mustache | 8 +-- 4 files changed, 54 insertions(+), 82 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index 460613e44ee..5adb90234f3 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -3,6 +3,8 @@ import com.algolia.codegen.cts.lambda.ScreamingSnakeCaseLambda; import com.algolia.codegen.exceptions.*; import com.algolia.codegen.utils.*; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.samskivert.mustache.Mustache; @@ -206,36 +208,13 @@ private void flattenBody(CodegenOperation ope) { } for (CodegenProperty prop : bodyParam.getVars()) { - // there is no easy way to convert a prop to a param, we need to copy all the fields - CodegenParameter param = new CodegenParameter(); - prop.nameInLowerCase = toParamName(prop.baseName); + + CodegenParameter param = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .convertValue(prop, CodegenParameter.class); param.nameInPascalCase = Helpers.capitalize(prop.baseName); param.paramName = toParamName(prop.baseName); - param.baseName = prop.baseName; - param.baseType = prop.baseType; - param.dataType = prop.dataType; - param.datatypeWithEnum = prop.datatypeWithEnum; - param.description = prop.description; - param.example = prop.example; - param.isModel = prop.isModel; - param.isArray = prop.isArray; - param.isContainer = prop.isContainer; - param.isMap = prop.isMap; - param.isEnum = prop.isEnum; - param.isEnumRef = prop.isEnumRef; - param.isPrimitiveType = prop.isPrimitiveType; - param.isString = prop.isString; - param.isNumeric = prop.isNumeric; - param.isBoolean = prop.isBoolean; - param.isDate = prop.isDate; - param.isDateTime = prop.isDateTime; - param.isFreeFormObject = prop.isFreeFormObject; - param.isNullable = prop.isNullable; - param.jsonSchema = prop.jsonSchema; - param.required = prop.required; - param.vendorExtensions = prop.vendorExtensions; - param.allowableValues = prop.allowableValues; if (prop.required) { ope.requiredParams.add(param); diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java index de0bfbb4828..b8d2cb2c03f 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java @@ -72,7 +72,7 @@ public void enhanceParameters(Map parameters, Map param : parameters.entrySet()) { - IJsonSchemaValidationProperties specParam = null; + CodegenParameter specParam = null; if (operation != null) { for (CodegenParameter sp : operation.allParams) { if (sp.paramName.equals(param.getKey())) { @@ -84,34 +84,27 @@ public void enhanceParameters(Map parameters, Map 0 + operation.bodyParam.getVars().size() > 0 && + AlgoliaGoGenerator.canFlattenBody(operation) ) { - if (AlgoliaGoGenerator.canFlattenBody(operation)) { - // flatten the body params by skipping one level - Map bodyParams = (Map) param.getValue(); - for (String nestedParam : bodyParams.keySet()) { - for (CodegenProperty prop : operation.bodyParam.getVars()) { - if (prop.baseName.equals(nestedParam)) { - Map paramWithType = traverseParams(prop.baseName, bodyParams.get(nestedParam), prop, "", 0, false); - parametersWithDataType.add(paramWithType); - parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); - break; - } + // flatten the body params by skipping one level + Map bodyParams = (Map) param.getValue(); + for (String nestedParam : bodyParams.keySet()) { + for (CodegenProperty prop : operation.bodyParam.getVars()) { + if (prop.baseName.equals(nestedParam)) { + Map paramWithType = traverseParams(prop.baseName, bodyParams.get(nestedParam), prop, "", 0, false); + parametersWithDataType.add(paramWithType); + parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); + break; } } - } else { - // use the parameter as is - Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); - parametersWithDataType.add(paramWithType); - parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); } } else { Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index a6ac9543825..da6d9059651 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -137,41 +137,40 @@ public static void setOptionalParameters( Map parameters, boolean isHelper ) { - if (!language.equals("go")) { - return; - } + if (!"go".equals(language)) return; + + boolean isBodyRequired = ope.bodyParam != null && ope.bodyParam.required; + boolean alreadyInlinedBody = ope.allParams.size() == 1 && ope.bodyParam != null && !ope.bodyParam.isArray; + // I can't figure out the correct condition for this one so it's harcoded for now + boolean isSFFV = + "searchForFacetValues".equals(ope.operationId) && !ope.tags.isEmpty() && "composition".equals(ope.tags.get(0).getName()); + int bodyPropsOptional = 0; - boolean actuallyHasOptional = false; boolean isBodyTooBig = false; - boolean isBodyRequired = (ope.bodyParam != null && ope.bodyParam.required); - boolean alreadyInlinedBody = ope.allParams.size() == 1 && ope.bodyParam != null && !ope.bodyParam.isArray; + boolean actuallyHasOptional = false; - if (AlgoliaGoGenerator.canFlattenBody(ope)) { - bodyPropsOptional = (int) ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count(); - isBodyTooBig = ope.bodyParam.getVars().size() == 0; + if (AlgoliaGoGenerator.canFlattenBody(ope) && ope.bodyParam != null) { + List vars = ope.bodyParam.getVars(); + bodyPropsOptional = (int) vars.stream().filter(p -> !p.required).count(); + isBodyTooBig = vars.isEmpty(); - // edge case where the body is already flattened - Map paramBody = paramBody = parameters; + Map paramBody = parameters; if (!alreadyInlinedBody) { - Object paramBodyObj = parameters.get(ope.bodyParam.paramName); - if (paramBodyObj instanceof String) { - // this is a verbatim paramater, we use it as is - System.out.println(ope.operationId + " is a verbatim body " + paramBodyObj); + Object paramObj = parameters.get(ope.bodyParam.paramName); + if (paramObj instanceof String) { actuallyHasOptional = !isBodyRequired; - } else { - paramBody = (Map) parameters.get(ope.bodyParam.paramName); + } else if (paramObj instanceof Map) { + paramBody = (Map) paramObj; } } - for (CodegenProperty prop : ope.bodyParam.getVars()) { + for (CodegenProperty prop : vars) { if (!prop.required && paramBody != null && paramBody.containsKey(prop.baseName)) { actuallyHasOptional = true; } } } - int totalOptional = ope.optionalParams.size() + bodyPropsOptional; - for (CodegenParameter param : ope.allParams) { if (!param.required && parameters.containsKey(param.baseName)) { actuallyHasOptional = true; @@ -179,19 +178,20 @@ public static void setOptionalParameters( } } - // I can't figure out the correct condition for this one so it's harcoded for now - boolean isSFFV = ope.operationId.equals("searchForFacetValues") && "composition".equals(ope.tags.get(0).getName()); + int totalOptional = ope.optionalParams.size() + bodyPropsOptional; // hasOptionalWrapper if there is more that one optional param, after the body has been - // flattened, only relevant for go - test.put("hasOptionalWrapper", totalOptional > 1 && actuallyHasOptional && !isSFFV); - test.put("hasInlineOptional", ((totalOptional == 1 || isSFFV) && actuallyHasOptional) || isBodyTooBig); - if (isBodyTooBig) { - boolean isBodySet = alreadyInlinedBody ? parameters.size() > 0 : parameters.containsKey(ope.bodyParam.paramName); - System.out.println(ope.operationId + " isBodySet: " + isBodySet + " isBodyRequired: " + isBodyRequired); - test.put("hasNilOptional", isBodyRequired ? totalOptional > 0 && !actuallyHasOptional : !isBodySet); - } else { - test.put("hasNilOptional", totalOptional > 0 && !actuallyHasOptional && !isHelper); + // flattened. + boolean hasOptionalWrapper = totalOptional > 1 && actuallyHasOptional && !isSFFV; + boolean hasInlineOptional = ((totalOptional == 1 || isSFFV) && actuallyHasOptional) || isBodyTooBig; + boolean hasNilOptional = totalOptional > 0 && !actuallyHasOptional && !isHelper; + if (isBodyTooBig && !isBodyRequired) { + boolean isBodySet = alreadyInlinedBody ? !parameters.isEmpty() : parameters.containsKey(ope.bodyParam.paramName); + hasNilOptional = !isBodySet; } + + test.put("hasOptionalWrapper", hasOptionalWrapper); + test.put("hasInlineOptional", hasInlineOptional); + test.put("hasNilOptional", hasNilOptional); } } diff --git a/templates/go/api.mustache b/templates/go/api.mustache index e7cc43163a1..9c71e9e0325 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -350,10 +350,10 @@ func (o *{{operationId}}Options) With{{#lambda.titlecase}}{{baseName}}{{/lambda. // Parameters: {{/hasParams}} {{#requiredParams}} -// - {{paramName}} {{#description}} - {{{.}}}{{/description}} +// - {{paramName}} {{#description}}- {{{.}}}{{/description}} {{/requiredParams}} {{#optionalParams}} -// - {{paramName}} {{#description}} - {{{.}}}{{/description}} (in optionalParams) +// - {{paramName}} {{#description}}- {{{.}}}{{/description}} (in optionalParams) {{/optionalParams}} // - opts - Optional parameters for the API call (e.g. WithContext, WithHeaderParam...) {{#isDeprecated}} @@ -401,10 +401,10 @@ func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{ // Parameters: {{/hasParams}} {{#requiredParams}} -// - {{paramName}} {{#description}} - {{{.}}}{{/description}} +// - {{paramName}} {{#description}}- {{{.}}}{{/description}} {{/requiredParams}} {{#optionalParams}} -// - {{paramName}} {{#description}} - {{{.}}}{{/description}} (in optionalParams) +// - {{paramName}} {{#description}}- {{{.}}}{{/description}} (in optionalParams) {{/optionalParams}} // - opts - Optional parameters for the API call (e.g. WithContext, WithHeaderParam...) {{#isDeprecated}} From 7d6e10143860daf0c0be6d10cb398959791b66e1 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 13 May 2025 17:50:56 +0200 Subject: [PATCH 09/14] more refacto --- .../cts/tests/ParametersWithDataType.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java index b8d2cb2c03f..aac2ea5bcc0 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java @@ -95,17 +95,7 @@ public void enhanceParameters(Map parameters, Map bodyParams = (Map) param.getValue(); - for (String nestedParam : bodyParams.keySet()) { - for (CodegenProperty prop : operation.bodyParam.getVars()) { - if (prop.baseName.equals(nestedParam)) { - Map paramWithType = traverseParams(prop.baseName, bodyParams.get(nestedParam), prop, "", 0, false); - parametersWithDataType.add(paramWithType); - parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); - break; - } - } - } + flattenBodyParams((Map) param.getValue(), operation, parametersWithDataType); } else { Map paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false); parametersWithDataType.add(paramWithType); @@ -115,16 +105,7 @@ public void enhanceParameters(Map parameters, Map 0) { // also flatten when the body is the only parameter - for (String nestedParam : parameters.keySet()) { - for (CodegenProperty prop : operation.bodyParam.getVars()) { - if (prop.baseName.equals(nestedParam)) { - Map paramWithType = traverseParams(prop.baseName, parameters.get(nestedParam), prop, "", 0, false); - parametersWithDataType.add(paramWithType); - parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType); - break; - } - } - } + flattenBodyParams(parameters, operation, parametersWithDataType); } else { Map paramWithType = traverseParams(paramName, parameters, spec, "", 0, false); parametersWithDataType.add(paramWithType); @@ -142,6 +123,22 @@ private String toJSONWithVar(Map parameters) throws JsonProcessi return Json.mapper().writeValueAsString(parameters).replaceAll("\"\\$var: (.*?)\"", "$1"); } + private void flattenBodyParams( + Map parameters, + CodegenOperation operation, + List> parametersWithDataType + ) throws CTSException { + for (String nestedParam : parameters.keySet()) { + for (CodegenProperty prop : operation.bodyParam.getVars()) { + if (prop.baseName.equals(nestedParam)) { + Map paramWithType = traverseParams(prop.baseName, parameters.get(nestedParam), prop, "", 0, false); + parametersWithDataType.add(paramWithType); + break; + } + } + } + } + private Map traverseParams( String paramName, Object param, From 0c68622027c5d385c4bcf76f80bc0520d324724c Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Fri, 30 May 2025 09:56:46 +0200 Subject: [PATCH 10/14] generate in the next package --- clients/algoliasearch-client-go/README.md | 2 +- config/clients.config.json | 4 ++-- .../src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java | 4 ++-- playground/go/analytics.go | 2 +- playground/go/ingestion.go | 2 +- playground/go/insights.go | 2 +- playground/go/personalization.go | 2 +- playground/go/query-suggestions.go | 2 +- playground/go/recommend.go | 2 +- playground/go/search.go | 3 +-- templates/go/snippets/import.mustache | 4 ++-- templates/go/tests/client/benchmark.mustache | 2 +- templates/go/tests/client/client.mustache | 2 +- templates/go/tests/e2e/e2e.mustache | 2 +- templates/go/tests/requests/requests.mustache | 2 +- 15 files changed, 18 insertions(+), 19 deletions(-) diff --git a/clients/algoliasearch-client-go/README.md b/clients/algoliasearch-client-go/README.md index 7d301253739..c6a4f90b8c5 100644 --- a/clients/algoliasearch-client-go/README.md +++ b/clients/algoliasearch-client-go/README.md @@ -41,7 +41,7 @@ You can now import the Algolia API client in your project and play with it. ```go -import "github.com/algolia/algoliasearch-client-go/v4/algolia/search" +import "github.com/algolia/algoliasearch-client-go/v4/algolia/next/search" client, err := search.NewClient("YOUR_APP_ID", "YOUR_API_KEY") diff --git a/config/clients.config.json b/config/clients.config.json index 4b3c8ab2cff..9ed566099ac 100644 --- a/config/clients.config.json +++ b/config/clients.config.json @@ -88,8 +88,8 @@ "folder": "clients/algoliasearch-client-go", "gitRepoId": "algoliasearch-client-go", "packageVersion": "4.28.1", - "modelFolder": "algolia", - "apiFolder": "algolia", + "modelFolder": "algolia/next", + "apiFolder": "algolia/next", "dockerImage": "apic_base", "tests": { "extension": "_test.go", diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index 5adb90234f3..08d5bdba428 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -37,7 +37,7 @@ public void processOpts() { additionalProperties.put("packageName", client.equals("query-suggestions") ? "suggestions" : Helpers.camelize(client)); additionalProperties.put("is" + Helpers.capitalize(Helpers.camelize(client)) + "Client", true); - String outputFolder = "algolia" + File.separator + client; + String outputFolder = "algolia/next/" + client; setOutputDir(getOutputDir() + File.separator + outputFolder); super.processOpts(); @@ -59,7 +59,7 @@ public void processOpts() { supportingFiles.add(new SupportingFile("configuration.mustache", "", "configuration.go")); supportingFiles.add(new SupportingFile("client.mustache", "", "client.go")); - Helpers.addCommonSupportingFiles(supportingFiles, "../../"); + Helpers.addCommonSupportingFiles(supportingFiles, "../../../"); try { additionalProperties.put("packageVersion", Helpers.getClientConfigField("go", "packageVersion")); diff --git a/playground/go/analytics.go b/playground/go/analytics.go index d6c78605049..cb917aa6ace 100644 --- a/playground/go/analytics.go +++ b/playground/go/analytics.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/algolia/algoliasearch-client-go/v4/algolia/analytics" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/analytics" ) func testAnalytics(appID, apiKey string) int { diff --git a/playground/go/ingestion.go b/playground/go/ingestion.go index d858e804f7b..05a3b762592 100644 --- a/playground/go/ingestion.go +++ b/playground/go/ingestion.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/algolia/algoliasearch-client-go/v4/algolia/ingestion" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/ingestion" ) func testIngestion(appID, apiKey string) int { diff --git a/playground/go/insights.go b/playground/go/insights.go index 0dca26438a8..3f543cc4b11 100644 --- a/playground/go/insights.go +++ b/playground/go/insights.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/algolia/algoliasearch-client-go/v4/algolia/insights" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/insights" ) func testInsights(appID, apiKey string) int { diff --git a/playground/go/personalization.go b/playground/go/personalization.go index bdffed33577..6eff833cddf 100644 --- a/playground/go/personalization.go +++ b/playground/go/personalization.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/algolia/algoliasearch-client-go/v4/algolia/personalization" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/personalization" ) func testPersonalization(appID, apiKey string) int { diff --git a/playground/go/query-suggestions.go b/playground/go/query-suggestions.go index b529c4c1e71..85514140964 100644 --- a/playground/go/query-suggestions.go +++ b/playground/go/query-suggestions.go @@ -3,7 +3,7 @@ package main import ( "fmt" - suggestions "github.com/algolia/algoliasearch-client-go/v4/algolia/query-suggestions" + suggestions "github.com/algolia/algoliasearch-client-go/v4/algolia/next/query-suggestions" ) func testQuerySuggestions(appID, apiKey string) int { diff --git a/playground/go/recommend.go b/playground/go/recommend.go index 4085332ba13..ec13320e9be 100644 --- a/playground/go/recommend.go +++ b/playground/go/recommend.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/algolia/algoliasearch-client-go/v4/algolia/recommend" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/recommend" ) func testRecommend(appID, apiKey string) int { diff --git a/playground/go/search.go b/playground/go/search.go index 680b8c66b93..1389dc69b9d 100644 --- a/playground/go/search.go +++ b/playground/go/search.go @@ -3,8 +3,7 @@ package main import ( "fmt" - "github.com/algolia/algoliasearch-client-go/v4/algolia/search" - // "github.com/algolia/algoliasearch-client-go/v4/algolia/transport" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/search" ) func testSearch(appID, apiKey string) int { diff --git a/templates/go/snippets/import.mustache b/templates/go/snippets/import.mustache index 67d0b34473a..4fab4f3172d 100644 --- a/templates/go/snippets/import.mustache +++ b/templates/go/snippets/import.mustache @@ -1,4 +1,4 @@ import ( - "github.com/algolia/algoliasearch-client-go/v4/algolia/{{clientImport}}" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/{{clientImport}}" "github.com/algolia/algoliasearch-client-go/v4/algolia/utils" -) \ No newline at end of file +) diff --git a/templates/go/tests/client/benchmark.mustache b/templates/go/tests/client/benchmark.mustache index 9335affa7ce..95ea0bfc98c 100644 --- a/templates/go/tests/client/benchmark.mustache +++ b/templates/go/tests/client/benchmark.mustache @@ -10,7 +10,7 @@ import ( "gotests/tests" - "github.com/algolia/algoliasearch-client-go/v4/algolia/{{clientImport}}" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/{{clientImport}}" "github.com/algolia/algoliasearch-client-go/v4/algolia/transport" "github.com/algolia/algoliasearch-client-go/v4/algolia/call" "github.com/algolia/algoliasearch-client-go/v4/algolia/utils" diff --git a/templates/go/tests/client/client.mustache b/templates/go/tests/client/client.mustache index d35c38a6b72..b613bd7a6dc 100644 --- a/templates/go/tests/client/client.mustache +++ b/templates/go/tests/client/client.mustache @@ -13,7 +13,7 @@ import ( "gotests/tests" - "github.com/algolia/algoliasearch-client-go/v4/algolia/{{clientImport}}" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/{{clientImport}}" "github.com/algolia/algoliasearch-client-go/v4/algolia/transport" "github.com/algolia/algoliasearch-client-go/v4/algolia/compression" "github.com/algolia/algoliasearch-client-go/v4/algolia/utils" diff --git a/templates/go/tests/e2e/e2e.mustache b/templates/go/tests/e2e/e2e.mustache index 94d9f2757fd..1103d8f8eb1 100644 --- a/templates/go/tests/e2e/e2e.mustache +++ b/templates/go/tests/e2e/e2e.mustache @@ -13,7 +13,7 @@ import ( "gotests/tests" - "github.com/algolia/algoliasearch-client-go/v4/algolia/{{clientImport}}" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/{{clientImport}}" ) func createE2E{{#lambda.titlecase}}{{clientPrefix}}{{/lambda.titlecase}}Client(t *testing.T) *{{clientPrefix}}.APIClient { diff --git a/templates/go/tests/requests/requests.mustache b/templates/go/tests/requests/requests.mustache index d1bb0c837b3..475c38f0f90 100644 --- a/templates/go/tests/requests/requests.mustache +++ b/templates/go/tests/requests/requests.mustache @@ -11,7 +11,7 @@ import ( "gotests/tests" - "github.com/algolia/algoliasearch-client-go/v4/algolia/{{clientImport}}" + "github.com/algolia/algoliasearch-client-go/v4/algolia/next/{{clientImport}}" "github.com/algolia/algoliasearch-client-go/v4/algolia/transport" "github.com/algolia/algoliasearch-client-go/v4/algolia/utils" ) From f76f2f9272b5841c8655ac4ed9fa8d1de35b961f Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Fri, 30 May 2025 15:54:31 +0200 Subject: [PATCH 11/14] put the context as first param --- templates/go/api.mustache | 20 ++--- templates/go/search_helpers.mustache | 76 ++++++++++--------- templates/go/tests/client/benchmark.mustache | 3 +- templates/go/tests/client/client.mustache | 3 +- templates/go/tests/e2e/e2e.mustache | 1 + templates/go/tests/method.mustache | 2 +- templates/go/tests/requests/requests.mustache | 1 + 7 files changed, 54 insertions(+), 52 deletions(-) diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 9c71e9e0325..f768c21449a 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -28,7 +28,6 @@ import ( type config struct { // -- Request options for API calls - context context.Context queryParams url.Values headerParams map[string]string timeouts transport.RequestConfiguration @@ -75,12 +74,6 @@ func (r requestOption) apply(c *config) { r(c) } -func WithContext(ctx context.Context) requestOption { - return requestOption(func(c *config) { - c.context = ctx - }) -} - func WithHeaderParam(key string, value any) requestOption { return requestOption(func(c *config) { c.headerParams[key] = utils.ParameterToString(value) @@ -355,17 +348,17 @@ func (o *{{operationId}}Options) With{{#lambda.titlecase}}{{baseName}}{{/lambda. {{#optionalParams}} // - {{paramName}} {{#description}}- {{{.}}}{{/description}} (in optionalParams) {{/optionalParams}} -// - opts - Optional parameters for the API call (e.g. WithContext, WithHeaderParam...) +// - opts - Optional parameters for the API call (e.g. WithHeaderParam, WithReadTimeout...) {{#isDeprecated}} // // Deprecated: {{operationId}} is deprecated {{/isDeprecated}} -func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}{{^isMap}}*{{/isMap}}{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) ({{#returnType}}{{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}}, {{/returnType}}error) { +func (c *APIClient) {{nickname}}(ctx context.Context, {{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}{{^isMap}}*{{/isMap}}{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) ({{#returnType}}{{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}}, {{/returnType}}error) { {{#returnType}} var returnValue {{^isArray}}{{^returnTypeIsPrimitive}}*{{/returnTypeIsPrimitive}}{{/isArray}}{{{.}}} {{/returnType}} - res, resBody, err := c.{{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams, {{/hasOptionalParams}}opts...) + res, resBody, err := c.{{nickname}}WithHTTPInfo(ctx, {{#requiredParams}}{{paramName}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams, {{/hasOptionalParams}}opts...) if err != nil { return {{#returnType}}returnValue, {{/returnType}}err } @@ -406,12 +399,12 @@ func (c *APIClient) {{nickname}}({{#requiredParams}}{{paramName}} {{#required}}{ {{#optionalParams}} // - {{paramName}} {{#description}}- {{{.}}}{{/description}} (in optionalParams) {{/optionalParams}} -// - opts - Optional parameters for the API call (e.g. WithContext, WithHeaderParam...) +// - opts - Optional parameters for the API call (e.g. WithHeaderParam, WithReadTimeout...) {{#isDeprecated}} // // Deprecated: {{operationId}} is deprecated {{/isDeprecated}} -func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}{{^isMap}}*{{/isMap}}{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) (*http.Response, []byte, error) { +func (c *APIClient) {{nickname}}WithHTTPInfo(ctx context.Context, {{#requiredParams}}{{paramName}} {{#required}}{{#isModel}}*{{/isModel}}{{/required}}{{^required}}{{^isMap}}*{{/isMap}}{{/required}}{{{dataType}}}, {{/requiredParams}}{{#hasOptionalParams}}optionalParams *{{operationId}}Options, {{/hasOptionalParams}}opts ...RequestOption) (*http.Response, []byte, error) { {{#vendorExtensions}} requestPath := "{{{path}}}"{{#pathParams}} requestPath = strings.ReplaceAll(requestPath, {{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, {{#x-is-custom-request}}utils.ParameterToString({{paramName}}){{/x-is-custom-request}}{{^x-is-custom-request}}url.PathEscape(utils.ParameterToString({{paramName}})){{/x-is-custom-request}}){{/pathParams}} @@ -436,7 +429,6 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{/allParams}} conf := config{ - context: context.Background(), queryParams: url.Values{}, headerParams: map[string]string{}, {{#vendorExtensions.x-timeouts}} @@ -533,7 +525,7 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#requiredParams}}{{paramName}} {{ {{/required}} {{/vendorExtensions.x-flat-body}} {{/bodyParams}} - req, err := c.prepareRequest(conf.context, requestPath, http.Method{{httpMethod}}, {{^bodyParams}}nil{{/bodyParams}}{{#bodyParams}}postBody{{/bodyParams}}, conf.headerParams, conf.queryParams) + req, err := c.prepareRequest(ctx, requestPath, http.Method{{httpMethod}}, {{^bodyParams}}nil{{/bodyParams}}{{#bodyParams}}postBody{{/bodyParams}}, conf.headerParams, conf.queryParams) if err != nil { return nil, nil, err } diff --git a/templates/go/search_helpers.mustache b/templates/go/search_helpers.mustache index b69c595a95c..e3d7948bee1 100644 --- a/templates/go/search_helpers.mustache +++ b/templates/go/search_helpers.mustache @@ -43,8 +43,8 @@ func CreateIterable[T any](execute func(*T, error) (*T, error), validate func(*T // SearchForHits calls the `search` method but with certainty that we will only request Algolia records (hits) and not facets. // Disclaimer: We don't assert that the parameters you pass to this method only contains `hits` requests to prevent impacting search performances, this helper is purely for typing purposes. -func (c *APIClient) SearchForHits(requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchResponse, error) { - res, err := c.Search(requests, strategy, opts...) +func (c *APIClient) SearchForHits(ctx context.Context, requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchResponse, error) { + res, err := c.Search(ctx, requests, strategy, opts...) if err != nil { return nil, err } @@ -62,8 +62,8 @@ func (c *APIClient) SearchForHits(requests []SearchQuery, strategy *SearchStrate // SearchForFacets calls the `search` method but with certainty that we will only request Algolia facets and not records (hits). // Disclaimer: We don't assert that the parameters you pass to this method only contains `facets` requests to prevent impacting search performances, this helper is purely for typing purposes. -func (c *APIClient) SearchForFacets(requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchForFacetValuesResponse, error) { - res, err := c.Search(requests, strategy, opts...) +func (c *APIClient) SearchForFacets(ctx context.Context,requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchForFacetValuesResponse, error) { + res, err := c.Search(ctx, requests, strategy, opts...) if err != nil { return nil, err } @@ -83,6 +83,7 @@ func (c *APIClient) SearchForFacets(requests []SearchQuery, strategy *SearchStra // It returns the task response if the operation was successful. // It returns an error if the operation failed. func (c *APIClient) WaitForTask( + ctx context.Context, indexName string, taskID int64, opts ...IterableOption, @@ -94,7 +95,7 @@ func (c *APIClient) WaitForTask( return CreateIterable( //nolint:wrapcheck func(*GetTaskResponse, error) (*GetTaskResponse, error) { - return c.GetTask(indexName, taskID, toRequestOptions(opts)...) + return c.GetTask(ctx, indexName, taskID, toRequestOptions(opts)...) }, func(response *GetTaskResponse, err error) (bool, error) { if err != nil || response == nil { @@ -111,6 +112,7 @@ func (c *APIClient) WaitForTask( // It returns the task response if the operation was successful. // It returns an error if the operation failed. func (c *APIClient) WaitForAppTask( + ctx context.Context, taskID int64, opts ...IterableOption, ) (*GetTaskResponse, error) { @@ -121,7 +123,7 @@ func (c *APIClient) WaitForAppTask( return CreateIterable( //nolint:wrapcheck func(*GetTaskResponse, error) (*GetTaskResponse, error) { - return c.GetAppTask(taskID, toRequestOptions(opts)...) + return c.GetAppTask(ctx, taskID, toRequestOptions(opts)...) }, func(response *GetTaskResponse, err error) (bool, error) { if err != nil || response == nil { @@ -164,6 +166,7 @@ func slicesEqualUnordered[T cmp.Ordered](a []T, b []T) bool { // If the operation is "update", the apiKey parameter must be set. // If the operation is "delete" or "add", the apiKey parameter is not used. func (c *APIClient) WaitForApiKey( + ctx context.Context, key string, operation ApiKeyOperation, opts ...WaitForApiKeyOption, @@ -247,7 +250,7 @@ func (c *APIClient) WaitForApiKey( return CreateIterable( //nolint:wrapcheck func(*GetApiKeyResponse, error) (*GetApiKeyResponse, error) { - return c.GetApiKey(key, toRequestOptions(opts)...) + return c.GetApiKey(ctx, key, toRequestOptions(opts)...) }, validateFunc, waitForApiKeyToIterableOptions(opts)..., @@ -257,6 +260,7 @@ func (c *APIClient) WaitForApiKey( // BrowseObjects allows to aggregate all the hits returned by the API calls. // Use the `WithAggregator` option to collect all the responses. func (c *APIClient) BrowseObjects( + ctx context.Context, indexName string, browseParams BrowseParamsObject, opts ...IterableOption, @@ -272,7 +276,7 @@ func (c *APIClient) BrowseObjects( } return c.Browse( - indexName, BrowseParamsObjectAsBrowseParams(&browseParams), + ctx, indexName, BrowseParamsObjectAsBrowseParams(&browseParams), toRequestOptions(opts)..., ) }, @@ -288,6 +292,7 @@ func (c *APIClient) BrowseObjects( // BrowseRules allows to aggregate all the rules returned by the API calls. // Use the `WithAggregator` option to collect all the responses. func (c *APIClient) BrowseRules( + ctx context.Context, indexName string, optionalParams *SearchRulesOptions, opts ...IterableOption, @@ -312,7 +317,7 @@ func (c *APIClient) BrowseRules( optionalParams.Page = utils.ToPtr(int32(0)) } - return c.SearchRules(indexName, optionalParams, toRequestOptions(opts)...) + return c.SearchRules(ctx, indexName, optionalParams, toRequestOptions(opts)...) }, func(response *SearchRulesResponse, err error) (bool, error) { return err != nil || (response != nil && len(response.Hits) < int(hitsPerPage)), err @@ -326,6 +331,7 @@ func (c *APIClient) BrowseRules( // BrowseSynonyms allows to aggregate all the synonyms returned by the API calls. // Use the `WithAggregator` option to collect all the responses. func (c *APIClient) BrowseSynonyms( + ctx context.Context, indexName string, optionalParams *SearchSynonymsOptions, opts ...IterableOption, @@ -350,7 +356,7 @@ func (c *APIClient) BrowseSynonyms( optionalParams.Page = utils.ToPtr(*optionalParams.Page + 1) }() - return c.SearchSynonyms(indexName, optionalParams, toRequestOptions(opts)...) + return c.SearchSynonyms(ctx, indexName, optionalParams, toRequestOptions(opts)...) }, func(response *SearchSynonymsResponse, err error) (bool, error) { return err != nil || (response != nil && len(response.Hits) < int(hitsPerPage)), err @@ -454,23 +460,23 @@ func (c *APIClient) GetSecuredApiKeyRemainingValidity(securedApiKey string) (tim } // 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, opts ...ChunkedBatchOption) ([]BatchResponse, error) { - return c.ChunkedBatch(indexName, objects, ACTION_ADD_OBJECT, opts...) +func (c *APIClient) SaveObjects(ctx context.Context, indexName string, objects []map[string]any, opts ...ChunkedBatchOption) ([]BatchResponse, error) { + return c.ChunkedBatch(ctx, 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, opts ...ChunkedBatchOption) ([]BatchResponse, error) { +func (c *APIClient) DeleteObjects(ctx context.Context, 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, ACTION_DELETE_OBJECT, opts...) + return c.ChunkedBatch(ctx, 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, opts ...PartialUpdateObjectsOption) ([]BatchResponse, error) { +func (c *APIClient) PartialUpdateObjects(ctx context.Context, indexName string, objects []map[string]any, opts ...PartialUpdateObjectsOption) ([]BatchResponse, error) { conf := config{ headerParams: map[string]string{}, createIfNotExists: true, @@ -488,11 +494,11 @@ func (c *APIClient) PartialUpdateObjects(indexName string, objects []map[string] action = ACTION_PARTIAL_UPDATE_OBJECT_NO_CREATE } - return c.ChunkedBatch(indexName, objects, action, partialUpdateObjectsToChunkedBatchOptions(opts)...) + return c.ChunkedBatch(ctx, indexName, objects, action, partialUpdateObjectsToChunkedBatchOptions(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, opts ...ChunkedBatchOption) ([]BatchResponse, error) { +func (c *APIClient) ChunkedBatch(ctx context.Context, indexName string, objects []map[string]any, action Action, opts ...ChunkedBatchOption) ([]BatchResponse, error) { conf := config{ headerParams: map[string]string{}, waitForTasks: false, @@ -510,7 +516,7 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act requests = append(requests, *NewBatchRequest(action, obj)) if len(requests) == conf.batchSize || i == len(objects)-1 { - resp, err := c.Batch(indexName, requests, toRequestOptions(opts)...) + resp, err := c.Batch(ctx, indexName, requests, toRequestOptions(opts)...) if err != nil { return nil, err } @@ -522,7 +528,7 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act if conf.waitForTasks { for _, resp := range responses { - _, err := c.WaitForTask(indexName, resp.TaskID, toIterableOptions(opts)...) + _, err := c.WaitForTask(ctx, indexName, resp.TaskID, toIterableOptions(opts)...) if err != nil { return nil, err } @@ -620,7 +626,7 @@ func (c *APIClient) ReplaceAllObjectsWithTransformation(indexName string, object // 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/add-new-api-client#5-helpers for implementation details. -func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any, opts ...ReplaceAllObjectsOption) (*ReplaceAllObjectsResponse, error) { +func (c *APIClient) ReplaceAllObjects(ctx context.Context, indexName string, objects []map[string]any, opts ...ReplaceAllObjectsOption) (*ReplaceAllObjectsResponse, error) { tmpIndexName := fmt.Sprintf("%s_tmp_%d", indexName, time.Now().UnixNano()) conf := config{ @@ -634,49 +640,49 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any opts = append(opts, WithWaitForTasks(true)) - copyResp, err := c.OperationIndex(indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) + copyResp, err := c.OperationIndex(ctx, indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) if err != nil { return nil, err } - batchResp, err := c.ChunkedBatch(tmpIndexName, objects, ACTION_ADD_OBJECT, replaceAllObjectsToChunkBatchOptions(opts)...) + batchResp, err := c.ChunkedBatch(ctx, tmpIndexName, objects, ACTION_ADD_OBJECT, replaceAllObjectsToChunkBactchOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(tmpIndexName) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) + _, err = c.WaitForTask(ctx, tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(tmpIndexName) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - copyResp, err = c.OperationIndex(indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) + copyResp, err = c.OperationIndex(ctx, indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(tmpIndexName) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) + _, err = c.WaitForTask(ctx, tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(tmpIndexName) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - moveResp, err := c.OperationIndex(tmpIndexName, OPERATION_TYPE_MOVE, indexName, nil, toRequestOptions(opts)...) + moveResp, err := c.OperationIndex(ctx, tmpIndexName, OPERATION_TYPE_MOVE, indexName, nil, toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(tmpIndexName) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - _, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) + _, err = c.WaitForTask(ctx, tmpIndexName, moveResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(tmpIndexName) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } @@ -691,8 +697,8 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any // Exists returns whether an initialized index exists or not, along with a nil // error. When encountering a network error, a non-nil error is returned along // with false. -func (c *APIClient) IndexExists(indexName string) (bool, error) { - _, err := c.GetSettings(indexName) +func (c *APIClient) IndexExists(ctx context.Context, indexName string) (bool, error) { + _, err := c.GetSettings(ctx, indexName) if err == nil { return true, nil } diff --git a/templates/go/tests/client/benchmark.mustache b/templates/go/tests/client/benchmark.mustache index 95ea0bfc98c..97026140371 100644 --- a/templates/go/tests/client/benchmark.mustache +++ b/templates/go/tests/client/benchmark.mustache @@ -4,7 +4,8 @@ package benchmark import ( "testing" - + "regexp" + "context" "github.com/stretchr/testify/require" diff --git a/templates/go/tests/client/client.mustache b/templates/go/tests/client/client.mustache index b613bd7a6dc..86905b4f7c3 100644 --- a/templates/go/tests/client/client.mustache +++ b/templates/go/tests/client/client.mustache @@ -6,7 +6,8 @@ import ( "encoding/json" "net/url" "testing" - "time" + "regexp" + "context" "github.com/kinbiko/jsonassert" "github.com/stretchr/testify/require" diff --git a/templates/go/tests/e2e/e2e.mustache b/templates/go/tests/e2e/e2e.mustache index 1103d8f8eb1..7183e8e0c83 100644 --- a/templates/go/tests/e2e/e2e.mustache +++ b/templates/go/tests/e2e/e2e.mustache @@ -6,6 +6,7 @@ import ( "encoding/json" "os" "testing" + "context" "github.com/stretchr/testify/require" "github.com/kinbiko/jsonassert" diff --git a/templates/go/tests/method.mustache b/templates/go/tests/method.mustache index 8a4e9bda924..e089af1e0ca 100644 --- a/templates/go/tests/method.mustache +++ b/templates/go/tests/method.mustache @@ -1 +1 @@ -client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{> tests/requiredParams}}{{> tests/optionalWrapper}}{{> tests/nilWrapper}}{{> tests/inlineOptional}}{{> tests/requestOptions}}) \ No newline at end of file +client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#isAsyncMethod}}context.Background(), {{/isAsyncMethod}}{{> tests/requiredParams}}{{> tests/optionalWrapper}}{{> tests/nilWrapper}}{{> tests/inlineOptional}}{{> tests/requestOptions}}) \ No newline at end of file diff --git a/templates/go/tests/requests/requests.mustache b/templates/go/tests/requests/requests.mustache index 475c38f0f90..6bb006cbfb6 100644 --- a/templates/go/tests/requests/requests.mustache +++ b/templates/go/tests/requests/requests.mustache @@ -5,6 +5,7 @@ import ( "encoding/json" "testing" "time" + "context" "github.com/kinbiko/jsonassert" "github.com/stretchr/testify/require" From e65cb2032167291e642e3695597d77ac22054323 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Mon, 2 Jun 2025 17:26:33 +0200 Subject: [PATCH 12/14] fix the playground --- playground/go/analytics.go | 5 +++-- playground/go/ingestion.go | 5 +++-- playground/go/insights.go | 5 +++-- playground/go/main.go | 23 +++++++++++++++-------- playground/go/personalization.go | 6 +++--- playground/go/query-suggestions.go | 5 +++-- playground/go/recommend.go | 5 +++-- playground/go/search.go | 13 +++++++------ 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/playground/go/analytics.go b/playground/go/analytics.go index cb917aa6ace..9dd4461cda6 100644 --- a/playground/go/analytics.go +++ b/playground/go/analytics.go @@ -1,19 +1,20 @@ package main import ( + "context" "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/next/analytics" ) -func testAnalytics(appID, apiKey string) int { +func testAnalytics(ctx context.Context, appID, apiKey string) int { indexName := getEnvWithDefault("ANALYTICS_INDEX", "test_index") analyticsClient, err := analytics.NewClient(appID, apiKey, analytics.US) if err != nil { panic(err) } - getTopFilterForAttributeResponse, err := analyticsClient.GetTopFilterForAttribute("myAttribute1,myAttribute2", indexName, nil) + getTopFilterForAttributeResponse, err := analyticsClient.GetTopFilterForAttribute(ctx, "myAttribute1,myAttribute2", indexName, nil) if err != nil { fmt.Printf("request error with GetTopFilterForAttribute: %v\n", err) return 1 diff --git a/playground/go/ingestion.go b/playground/go/ingestion.go index 05a3b762592..7cb17e73a18 100644 --- a/playground/go/ingestion.go +++ b/playground/go/ingestion.go @@ -1,19 +1,20 @@ package main import ( + "context" "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/next/ingestion" ) -func testIngestion(appID, apiKey string) int { +func testIngestion(ctx context.Context, appID, apiKey string) int { ingestionClient, err := ingestion.NewClient(appID, apiKey, ingestion.US) if err != nil { panic(err) } // another example to generate payload for a request. - res, err := ingestionClient.ListTasks(nil) + res, err := ingestionClient.ListTasks(ctx, nil) if err != nil { fmt.Printf("request error: %v\n", err) diff --git a/playground/go/insights.go b/playground/go/insights.go index 3f543cc4b11..75eafbdb43e 100644 --- a/playground/go/insights.go +++ b/playground/go/insights.go @@ -1,12 +1,13 @@ package main import ( + "context" "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/next/insights" ) -func testInsights(appID, apiKey string) int { +func testInsights(ctx context.Context, appID, apiKey string) int { insightsClient, err := insights.NewClient(appID, apiKey, insights.US) if err != nil { panic(err) @@ -20,7 +21,7 @@ func testInsights(appID, apiKey string) int { "myToken", insights.WithClickedObjectIDsTimestamp(1234567890))), } - eventsResponse, err := insightsClient.PushEvents(events) + eventsResponse, err := insightsClient.PushEvents(ctx, events) if err != nil { fmt.Printf("request error with PushEvents: %v\n", err) return 1 diff --git a/playground/go/main.go b/playground/go/main.go index b4d8e4ec83b..2a6b6698c61 100644 --- a/playground/go/main.go +++ b/playground/go/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "os" @@ -10,7 +11,11 @@ import ( func main() { fmt.Println("Go playground") - godotenv.Load("../.env") + err := godotenv.Load("../.env") + if err != nil { + panic(fmt.Errorf("error loading .env file: %w", err)) + } + appID := os.Getenv("ALGOLIA_APPLICATION_ID") apiKey := os.Getenv("ALGOLIA_ADMIN_KEY") @@ -27,21 +32,23 @@ func main() { // debug.Enable() + ctx := context.Background() + switch client { case "ingestion": - returnCode = testIngestion(appID, apiKey) + returnCode = testIngestion(ctx, appID, apiKey) case "search": - returnCode = testSearch(appID, apiKey) + returnCode = testSearch(ctx, appID, apiKey) case "analytics": - returnCode = testAnalytics(appID, apiKey) + returnCode = testAnalytics(ctx, appID, apiKey) case "insights": - returnCode = testInsights(appID, apiKey) + returnCode = testInsights(ctx, appID, apiKey) case "personalization": - returnCode = testPersonalization(appID, apiKey) + returnCode = testPersonalization(ctx, appID, apiKey) case "query-suggestions": - returnCode = testQuerySuggestions(appID, apiKey) + returnCode = testQuerySuggestions(ctx, appID, apiKey) case "recommend": - returnCode = testRecommend(appID, apiKey) + returnCode = testRecommend(ctx, appID, apiKey) default: fmt.Println("Please specify a valid client name") os.Exit(1) diff --git a/playground/go/personalization.go b/playground/go/personalization.go index 6eff833cddf..68b51351b64 100644 --- a/playground/go/personalization.go +++ b/playground/go/personalization.go @@ -8,16 +8,16 @@ import ( "github.com/algolia/algoliasearch-client-go/v4/algolia/next/personalization" ) -func testPersonalization(appID, apiKey string) int { +func testPersonalization(ctx context.Context, appID, apiKey string) int { personalizationClient, err := personalization.NewClient(appID, apiKey, personalization.US) if err != nil { panic(err) } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond) defer cancel() // it will fail expectedly because of the very short timeout to showcase the context usage. - deleteUserProfileResponse, err := personalizationClient.DeleteUserProfile("userToken", personalization.WithContext(ctx)) + deleteUserProfileResponse, err := personalizationClient.DeleteUserProfile(ctx, "userToken") if err != nil { fmt.Printf("request error with DeleteUserProfile: %v\n", err) return 1 diff --git a/playground/go/query-suggestions.go b/playground/go/query-suggestions.go index 85514140964..9756bf6873c 100644 --- a/playground/go/query-suggestions.go +++ b/playground/go/query-suggestions.go @@ -1,18 +1,19 @@ package main import ( + "context" "fmt" suggestions "github.com/algolia/algoliasearch-client-go/v4/algolia/next/query-suggestions" ) -func testQuerySuggestions(appID, apiKey string) int { +func testQuerySuggestions(ctx context.Context, appID, apiKey string) int { suggestionsClient, err := suggestions.NewClient(appID, apiKey, suggestions.US) if err != nil { panic(err) } - querySuggestionsIndex, err := suggestionsClient.GetAllConfigs() + querySuggestionsIndex, err := suggestionsClient.GetAllConfigs(ctx) if err != nil { fmt.Printf("request error with GetAllConfigs: %v\n", err) return 1 diff --git a/playground/go/recommend.go b/playground/go/recommend.go index ec13320e9be..daaacccab50 100644 --- a/playground/go/recommend.go +++ b/playground/go/recommend.go @@ -1,12 +1,13 @@ package main import ( + "context" "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/next/recommend" ) -func testRecommend(appID, apiKey string) int { +func testRecommend(ctx context.Context, appID, apiKey string) int { recommendClient, err := recommend.NewClient(appID, apiKey) if err != nil { panic(err) @@ -27,7 +28,7 @@ func testRecommend(appID, apiKey string) int { }, }} - searchResponse, err := recommendClient.GetRecommendations(params) + searchResponse, err := recommendClient.GetRecommendations(ctx, params) if err != nil { fmt.Printf("request error with SearchSingleIndex: %v\n", err) return 1 diff --git a/playground/go/search.go b/playground/go/search.go index 1389dc69b9d..8988a2011d7 100644 --- a/playground/go/search.go +++ b/playground/go/search.go @@ -1,19 +1,20 @@ package main import ( + "context" "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/next/search" ) -func testSearch(appID, apiKey string) int { +func testSearch(ctx context.Context, appID, apiKey string) int { // indexName := getEnvWithDefault("SEARCH_INDEX", "test_index") searchClient, err := search.NewClient(appID, apiKey) if err != nil { panic(err) } - err = searchClient.BrowseObjects("test-flag", *search.NewEmptyBrowseParamsObject(), search.WithAggregator(func(res any, err error) { + err = searchClient.BrowseObjects(ctx, "test-flag", *search.NewEmptyBrowseParamsObject(), search.WithAggregator(func(res any, err error) { if err != nil { panic(err) } @@ -29,12 +30,12 @@ func testSearch(appID, apiKey string) int { //}))) // new way - //searchClient.Search([]search.SearchQuery{ + //searchClient.Search(ctx, []search.SearchQuery{ // search.NewSearchForHits("indexName").WithQuery("foo"), //}, nil) /* - response, err := searchClient.AddOrUpdateObject( + response, err := searchClient.AddOrUpdateObject(ctx, searchClient.NewApiAddOrUpdateObjectRequest( indexName, "1", @@ -49,12 +50,12 @@ func testSearch(appID, apiKey string) int { panic(err) } - _, err = searchClient.WaitForTask(indexName, *response.TaskID) + _, err = searchClient.WaitForTask(ctx, indexName, *response.TaskID) if err != nil { panic(err) } - searchResponse, err := searchClient.Search( + searchResponse, err := searchClient.Search(ctx, searchClient.NewApiSearchRequest( search.NewSearchMethodParams( []search.SearchQuery{ From cde5d91d22eba5bfd7d5a039d7ca98816c235e01 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Mon, 2 Jun 2025 23:12:21 +0200 Subject: [PATCH 13/14] fix the guides --- templates/go/guides/search/saveImageClassifications.mustache | 2 +- .../guides/search/saveImageClassificationsAndSettings.mustache | 2 +- templates/go/guides/search/savePopularRecords.mustache | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/go/guides/search/saveImageClassifications.mustache b/templates/go/guides/search/saveImageClassifications.mustache index 0c2aed4908b..d0eb83ce959 100644 --- a/templates/go/guides/search/saveImageClassifications.mustache +++ b/templates/go/guides/search/saveImageClassifications.mustache @@ -27,7 +27,7 @@ func saveImageClassifications() { images := []Image{} - err = client.BrowseObjects("", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) { + err = client.BrowseObjects(context.Background(), "", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) { if err != nil { panic(err) } diff --git a/templates/go/guides/search/saveImageClassificationsAndSettings.mustache b/templates/go/guides/search/saveImageClassificationsAndSettings.mustache index e49ec51dde5..9d52139e08d 100644 --- a/templates/go/guides/search/saveImageClassificationsAndSettings.mustache +++ b/templates/go/guides/search/saveImageClassificationsAndSettings.mustache @@ -27,7 +27,7 @@ func saveImageClassificationsAndSettings() { images := []Image{} - err = client.BrowseObjects("", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) { + err = client.BrowseObjects(context.Background(), "", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) { if err != nil { panic(err) } diff --git a/templates/go/guides/search/savePopularRecords.mustache b/templates/go/guides/search/savePopularRecords.mustache index 2c37c1a7167..22aa81de8ab 100644 --- a/templates/go/guides/search/savePopularRecords.mustache +++ b/templates/go/guides/search/savePopularRecords.mustache @@ -13,7 +13,7 @@ func savePopularRecords() { records := []map[string]any{ { /* Your records */ } } - err = client.BrowseObjects("", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) { + err = client.BrowseObjects(context.Background(), "", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) { if err != nil { panic(err) } From 3aad06f5d08ee67846426644f01bf6ee07b99ca8 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 14 Oct 2025 18:53:14 +0200 Subject: [PATCH 14/14] rebase with main --- clients/algoliasearch-client-go/.golangci.yml | 4 + .../algolia/codegen/AlgoliaGoGenerator.java | 22 +-- .../cts/tests/ParametersWithDataType.java | 8 +- .../codegen/cts/tests/TestsGenerator.java | 5 +- templates/go/api.mustache | 1 + templates/go/client.mustache | 18 --- templates/go/ingestion_helpers.mustache | 43 +++--- templates/go/search_helpers.mustache | 128 ++++++------------ .../CTS/requests/abtesting-v3/addABTests.json | 20 ++- .../requests/abtesting-v3/scheduleABTest.json | 24 ++-- tests/output/go/.golangci.yml | 1 + 11 files changed, 117 insertions(+), 157 deletions(-) diff --git a/clients/algoliasearch-client-go/.golangci.yml b/clients/algoliasearch-client-go/.golangci.yml index ef10120bd06..2dd039ed968 100644 --- a/clients/algoliasearch-client-go/.golangci.yml +++ b/clients/algoliasearch-client-go/.golangci.yml @@ -50,6 +50,10 @@ linters: presets: - comments - std-error-handling + rules: + - path: api_search.go + linters: + - wrapcheck formatters: enable: - gofmt diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index 08d5bdba428..0722b61a494 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -49,6 +49,7 @@ public void processOpts() { typeMapping.put("AnyType", "any"); modelNameMapping.put("range", "modelRange"); + typeMapping.put("integer", "int"); apiTestTemplateFiles.clear(); modelTestTemplateFiles.clear(); @@ -85,7 +86,10 @@ public void processOpenAPI(OpenAPI openAPI) { Timeouts.enrichBundle(openAPI, additionalProperties); additionalProperties.put( "appDescription", - Arrays.stream(openAPI.getInfo().getDescription().split("\n")).map(line -> "// " + line).collect(Collectors.joining("\n")).trim() + Arrays.stream(openAPI.getInfo().getDescription().split("\n")) + .map(line -> "// " + line) + .collect(Collectors.joining("\n")) + .trim() ); } @@ -127,7 +131,8 @@ public Map postProcessAllModels(Map objs) String modelName = entry.getKey(); CodegenModel model = entry.getValue().getModels().get(0).getModel(); - // for some reason the property additionalPropertiesIsAnyType is not propagated to the + // for some reason the property additionalPropertiesIsAnyType is not propagated + // to the // property for (CodegenProperty prop : model.getVars()) { ModelsMap propertyModel = models.get(prop.datatypeWithEnum); @@ -154,7 +159,12 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List "// " + line).collect(Collectors.joining("\n"))).trim(); + ope.notes = (lines[0] + + "\n" + + Arrays.stream(lines) + .skip(1) + .map(line -> "// " + line) + .collect(Collectors.joining("\n"))).trim(); // enrich the params for (CodegenParameter param : ope.optionalParams) { @@ -171,15 +181,13 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List> parameters) { // Store ordered params from the spec - var orderedParams = spec.getVars().stream().map(v -> v.baseName).toList(); + var orderedParams = spec + .getVars() + .stream() + .map(v -> v.baseName) + .toList(); // Create a map to store the indices of each string in orderedParams Map indexMap = IntStream.range(0, orderedParams.size()).boxed().collect(Collectors.toMap(orderedParams::get, i -> i)); diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index da6d9059651..73150fc7929 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -151,7 +151,10 @@ public static void setOptionalParameters( if (AlgoliaGoGenerator.canFlattenBody(ope) && ope.bodyParam != null) { List vars = ope.bodyParam.getVars(); - bodyPropsOptional = (int) vars.stream().filter(p -> !p.required).count(); + bodyPropsOptional = (int) vars + .stream() + .filter(p -> !p.required) + .count(); isBodyTooBig = vars.isEmpty(); Map paramBody = parameters; diff --git a/templates/go/api.mustache b/templates/go/api.mustache index f768c21449a..53a21259b53 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -365,6 +365,7 @@ func (c *APIClient) {{nickname}}(ctx context.Context, {{#requiredParams}}{{param if res == nil { return {{#returnType}}returnValue, {{/returnType}}reportError("res is nil") } + defer res.Body.Close() if res.StatusCode >= 300 { return {{#returnType}}returnValue, {{/returnType}}c.decodeError(res, resBody) diff --git a/templates/go/client.mustache b/templates/go/client.mustache index 04d91241630..40dca4bd8cd 100644 --- a/templates/go/client.mustache +++ b/templates/go/client.mustache @@ -312,24 +312,6 @@ func reportError(format string, a ...any) error { return fmt.Errorf(format, a...) } -// A wrapper for strict JSON decoding -func newStrictDecoder(data []byte) *json.Decoder { - dec := json.NewDecoder(bytes.NewBuffer(data)) - dec.DisallowUnknownFields() - return dec -} - -// A wrapper for validating a struct, returns nil if value is not a struct -func validateStruct(v any) error { - err := validator.New().Struct(v) - validationErrors, ok := err.(validator.ValidationErrors) - if ok && len(validationErrors) > 0 { - return validationErrors - } - - return nil -} - // Set request body from an any func setBody(body any, c compression.Compression) (*bytes.Buffer, error) { if body == nil { diff --git a/templates/go/ingestion_helpers.mustache b/templates/go/ingestion_helpers.mustache index bfa11410336..410145a2c8e 100644 --- a/templates/go/ingestion_helpers.mustache +++ b/templates/go/ingestion_helpers.mustache @@ -1,15 +1,13 @@ -/* -ChunkedPush Chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `push` requests by leveraging the Transformation pipeline setup in the Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/). - - @param indexName string - the index name to save objects into. - @param objects []map[string]any - List of objects to save. - @param action Action - The action to perform on the objects. - @param referenceIndexName *string - This is required when targeting an index that does not have a push connector setup (e.g. a tmp index), but you wish to attach another index's transformation to it (e.g. the source index name). - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []WatchResponse - List of push responses. - @return error - Error if any. -*/ -func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, action Action, referenceIndexName *string, opts ...ChunkedBatchOption) ([]WatchResponse, error) { +// ChunkedPush Chunks the given `objects` list in subset of 1000 elements max in order to make it fit in `push` requests by leveraging the Transformation pipeline setup in the Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/). +// +// Parameters: +// - transformationID - Unique identifier of a transformation. +// - indexName string - the index name to save objects into. +// - objects []map[string]any - List of objects to save. +// - action Action - The action to perform on the objects. +// - referenceIndexName *string - This is required when targeting an index that does not have a push connector setup (e.g. a tmp index), but you wish to attach another index's transformation to it (e.g. the source index name). +// - opts - Optional parameters for the API call (e.g. WithHeaderParam, WithReadTimeout...) +func (c *APIClient) ChunkedPush(ctx context.Context, indexName string, objects []map[string]any, action Action, referenceIndexName *string, opts ...ChunkedBatchOption) ([]WatchResponse, error) { conf := config{ headerParams: map[string]string{}, waitForTasks: false, @@ -45,18 +43,12 @@ func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, acti return nil, reportError("unable to unmarshal the given `objects` to an `[]PushTaskRecords` payload: %w", err) } - request := c.NewApiPushRequest( - indexName, - NewEmptyPushTaskPayload(). - SetAction(action). - SetRecords(pushRecords), - ) - - if referenceIndexName != nil { - request = request.WithReferenceIndexName(*referenceIndexName) - } + opts := NewPushOptions() + if referenceIndexName != nil { + opts = opts.WithReferenceIndexName(*referenceIndexName) + } - resp, err := c.Push(request) + resp, err := c.Push(ctx, indexName, action, pushRecords, opts) if err != nil { return nil, err //nolint: wrapcheck } @@ -65,7 +57,6 @@ func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, acti records = make([]map[string]any, 0, len(objects)%conf.batchSize) } - if conf.waitForTasks && len(responses) > 0 && (len(responses)%waitBatchSize == 0 || i == len(objects)-1) { var waitableResponses []WatchResponse @@ -82,7 +73,7 @@ func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, acti return nil, reportError("received unexpected response from the push endpoint, eventID must not be undefined") } - return c.GetEvent(c.NewApiGetEventRequest(resp.RunID, *resp.EventID)) + return c.GetEvent(ctx, resp.RunID, *resp.EventID) }, func(response *Event, err error) (bool, error) { var apiErr *APIError @@ -104,4 +95,4 @@ func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, acti } return responses, nil -} \ No newline at end of file +} diff --git a/templates/go/search_helpers.mustache b/templates/go/search_helpers.mustache index e3d7948bee1..6d8db96caec 100644 --- a/templates/go/search_helpers.mustache +++ b/templates/go/search_helpers.mustache @@ -1,46 +1,3 @@ -func CreateIterable[T any](execute func(*T, error) (*T, error), validate func(*T, error) (bool, error), opts ...IterableOption) (*T, error) { - conf := config{ - headerParams: map[string]string{}, - maxRetries: -1, - timeout: func(count int) time.Duration { - return 0 * time.Millisecond - }, - } - - for _, opt := range opts { - opt.apply(&conf) - } - - var executor func(*T, error) (*T, error) - - retryCount := 0 - - executor = func(previousResponse *T, previousError error) (*T, error) { - response, responseErr := execute(previousResponse, previousError) - - retryCount++ - - if conf.aggregator != nil { - conf.aggregator(response, responseErr) - } - - canStop, err := validate(response, responseErr) - if canStop || err != nil { - return response, err - } - - if conf.maxRetries >= 0 && retryCount >= conf.maxRetries { - return nil, errs.NewWaitError(fmt.Sprintf("The maximum number of retries exceeded. (%d/%d)", retryCount, conf.maxRetries)) - } - - time.Sleep(conf.timeout(retryCount)) - - return executor(response, responseErr) - } - - return executor(nil, nil) -} - // SearchForHits calls the `search` method but with certainty that we will only request Algolia records (hits) and not facets. // Disclaimer: We don't assert that the parameters you pass to this method only contains `hits` requests to prevent impacting search performances, this helper is purely for typing purposes. func (c *APIClient) SearchForHits(ctx context.Context, requests []SearchQuery, strategy *SearchStrategy, opts ...RequestOption) ([]SearchResponse, error) { @@ -266,7 +223,7 @@ func (c *APIClient) BrowseObjects( opts ...IterableOption, ) error { if browseParams.HitsPerPage == nil { - browseParams.HitsPerPage = utils.ToPtr(int32(1000)) + browseParams.HitsPerPage = utils.ToPtr(1000) } _, err := CreateIterable( //nolint:wrapcheck @@ -297,7 +254,7 @@ func (c *APIClient) BrowseRules( optionalParams *SearchRulesOptions, opts ...IterableOption, ) error { - hitsPerPage := int32(1000) + hitsPerPage := 1000 if optionalParams == nil { optionalParams = NewSearchRulesOptions() } @@ -314,13 +271,13 @@ func (c *APIClient) BrowseRules( } if optionalParams.Page == nil { - optionalParams.Page = utils.ToPtr(int32(0)) + optionalParams.Page = utils.ToPtr(0) } return c.SearchRules(ctx, indexName, optionalParams, toRequestOptions(opts)...) }, func(response *SearchRulesResponse, err error) (bool, error) { - return err != nil || (response != nil && len(response.Hits) < int(hitsPerPage)), err + return err != nil || (response != nil && len(response.Hits) < hitsPerPage), err }, opts..., ) @@ -336,7 +293,7 @@ func (c *APIClient) BrowseSynonyms( optionalParams *SearchSynonymsOptions, opts ...IterableOption, ) error { - hitsPerPage := int32(1000) + hitsPerPage := 1000 if optionalParams == nil { optionalParams = NewSearchSynonymsOptions() } @@ -345,7 +302,7 @@ func (c *APIClient) BrowseSynonyms( } if optionalParams.Page == nil { - optionalParams.Page = utils.ToPtr(int32(0)) + optionalParams.Page = utils.ToPtr(0) } _, err := CreateIterable( //nolint:wrapcheck @@ -359,7 +316,7 @@ func (c *APIClient) BrowseSynonyms( return c.SearchSynonyms(ctx, indexName, optionalParams, toRequestOptions(opts)...) }, func(response *SearchSynonymsResponse, err error) (bool, error) { - return err != nil || (response != nil && len(response.Hits) < int(hitsPerPage)), err + return err != nil || (response != nil && len(response.Hits) < hitsPerPage), err }, opts..., ) @@ -540,7 +497,7 @@ func (c *APIClient) ChunkedBatch(ctx context.Context, indexName string, objects // 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. // See https://api-clients-automation.netlify.app/docs/custom-helpers/#replaceallobjects for implementation details. -func (c *APIClient) ReplaceAllObjectsWithTransformation(indexName string, objects []map[string]any, opts ...ReplaceAllObjectsOption) (*ReplaceAllObjectsWithTransformationResponse, error) { +func (c *APIClient) ReplaceAllObjectsWithTransformation(ctx context.Context, 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.") } @@ -558,49 +515,49 @@ func (c *APIClient) ReplaceAllObjectsWithTransformation(indexName string, object opts = append(opts, WithWaitForTasks(true)) - copyResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope(conf.scopes))), toRequestOptions(opts)...) + copyResp, err := c.OperationIndex(ctx, indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) if err != nil { return nil, err } - watchResp, err := c.ingestionTransporter.ChunkedPush(tmpIndexName, objects, ingestion.Action(ACTION_ADD_OBJECT), &indexName, toIngestionChunkedBatchOptions(replaceAllObjectsToChunkBatchOptions(opts))...) + watchResp, err := c.ingestionTransporter.ChunkedPush(ctx, tmpIndexName, objects, ingestion.Action(ACTION_ADD_OBJECT), &indexName, toIngestionChunkedBatchOptions(replaceAllObjectsToChunkBatchOptions(opts))...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err //nolint:wrapcheck } - _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) + _, err = c.WaitForTask(ctx, tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - copyResp, err = c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope(conf.scopes))), toRequestOptions(opts)...) + copyResp, err = c.OperationIndex(ctx, indexName, OPERATION_TYPE_COPY, tmpIndexName, &conf.scopes, toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) + _, err = c.WaitForTask(ctx, tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - moveResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(tmpIndexName, NewOperationIndexParams(OPERATION_TYPE_MOVE, indexName)), toRequestOptions(opts)...) + moveResp, err := c.OperationIndex(ctx, tmpIndexName, OPERATION_TYPE_MOVE, indexName, nil, toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } - _, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) + _, err = c.WaitForTask(ctx, tmpIndexName, moveResp.TaskID, replaceAllObjectsToIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + _, _ = c.DeleteIndex(ctx, tmpIndexName) return nil, err } @@ -645,7 +602,7 @@ func (c *APIClient) ReplaceAllObjects(ctx context.Context, indexName string, obj return nil, err } - batchResp, err := c.ChunkedBatch(ctx, tmpIndexName, objects, ACTION_ADD_OBJECT, replaceAllObjectsToChunkBactchOptions(opts)...) + batchResp, err := c.ChunkedBatch(ctx, tmpIndexName, objects, ACTION_ADD_OBJECT, replaceAllObjectsToChunkBatchOptions(opts)...) if err != nil { _, _ = c.DeleteIndex(ctx, tmpIndexName) @@ -698,7 +655,7 @@ func (c *APIClient) ReplaceAllObjects(ctx context.Context, indexName string, obj // error. When encountering a network error, a non-nil error is returned along // with false. func (c *APIClient) IndexExists(ctx context.Context, indexName string) (bool, error) { - _, err := c.GetSettings(ctx, indexName) + _, err := c.GetSettings(ctx, indexName, nil) if err == nil { return true, nil } @@ -711,33 +668,28 @@ func (c *APIClient) IndexExists(ctx context.Context, indexName string) (bool, er return false, err } -/* -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. - - @param indexName string - the index name to save objects into. - @param objects []map[string]any - List of objects to save. - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []BatchResponse - List of batch responses. - @return error - Error if any. -*/ -func (c *APIClient) SaveObjectsWithTransformation(indexName string, objects []map[string]any, opts ...ChunkedBatchOption) ([]ingestion.WatchResponse, 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. +// +// Parameters: +// - indexName string - the index name to save objects into. +// - objects []map[string]any - List of objects to save. +// - opts - Optional parameters for the API call (e.g. WithHeaderParam, WithReadTimeout...) +func (c *APIClient) SaveObjectsWithTransformation(ctx context.Context, 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 c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(ACTION_ADD_OBJECT), nil, toIngestionChunkedBatchOptions(opts)...) //nolint:wrapcheck + return c.ingestionTransporter.ChunkedPush(ctx, 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. - - @param indexName string - the index name to save objects into. - @param objects []map[string]any - List of objects to save. - @param opts ...ChunkedBatchOption - Optional parameters for the request. - @return []BatchResponse - List of batch responses. - @return error - Error if any. -*/ -func (c *APIClient) PartialUpdateObjectsWithTransformation(indexName string, objects []map[string]any, opts ...PartialUpdateObjectsOption) ([]ingestion.WatchResponse, error) { +// 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. +// +// Parameters: +// - ctx context.Context - The context to use for the request. +// - indexName string - the index name to save objects into. +// - objects []map[string]any - List of objects to save. +// - opts - Optional parameters for the API call (e.g. WithHeaderParam, WithReadTimeout...) +func (c *APIClient) PartialUpdateObjectsWithTransformation(ctx context.Context, 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.") } @@ -759,5 +711,5 @@ func (c *APIClient) PartialUpdateObjectsWithTransformation(indexName string, obj action = ACTION_PARTIAL_UPDATE_OBJECT_NO_CREATE } - return c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(action), nil, toIngestionChunkedBatchOptions(partialUpdateObjectsToChunkedBatchOptions(opts))...) //nolint:wrapcheck + return c.ingestionTransporter.ChunkedPush(ctx, indexName, objects, ingestion.Action(action), nil, toIngestionChunkedBatchOptions(partialUpdateObjectsToChunkedBatchOptions(opts))...) //nolint:wrapcheck } \ No newline at end of file diff --git a/tests/CTS/requests/abtesting-v3/addABTests.json b/tests/CTS/requests/abtesting-v3/addABTests.json index 388d8341f84..2a649004b8a 100644 --- a/tests/CTS/requests/abtesting-v3/addABTests.json +++ b/tests/CTS/requests/abtesting-v3/addABTests.json @@ -2,9 +2,7 @@ { "testName": "addABTests with minimal parameters", "parameters": { - "endAt": "2022-12-31T00:00:00.000Z", "name": "myABTest", - "metrics": [{"name": "myMetric"}], "variants": [ { "index": "AB_TEST_1", @@ -14,15 +12,19 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "metrics": [ + { + "name": "myMetric" + } + ], + "endAt": "2022-12-31T00:00:00.000Z" }, "request": { "path": "/3/abtests", "method": "POST", "body": { - "endAt": "2022-12-31T00:00:00.000Z", "name": "myABTest", - "metrics": [{"name": "myMetric"}], "variants": [ { "index": "AB_TEST_1", @@ -32,7 +34,13 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "metrics": [ + { + "name": "myMetric" + } + ], + "endAt": "2022-12-31T00:00:00.000Z" } } } diff --git a/tests/CTS/requests/abtesting-v3/scheduleABTest.json b/tests/CTS/requests/abtesting-v3/scheduleABTest.json index 2a6df45bcb5..ef01f58b070 100644 --- a/tests/CTS/requests/abtesting-v3/scheduleABTest.json +++ b/tests/CTS/requests/abtesting-v3/scheduleABTest.json @@ -2,10 +2,7 @@ { "testName": "scheduleABTest with minimal parameters", "parameters": { - "endAt": "2022-12-31T00:00:00.000Z", - "scheduledAt": "2022-11-31T00:00:00.000Z", "name": "myABTest", - "metrics": [{"name": "myMetric"}], "variants": [ { "index": "AB_TEST_1", @@ -15,16 +12,20 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "metrics": [ + { + "name": "myMetric" + } + ], + "scheduledAt": "2022-11-31T00:00:00.000Z", + "endAt": "2022-12-31T00:00:00.000Z" }, "request": { "path": "/3/abtests/schedule", "method": "POST", "body": { - "endAt": "2022-12-31T00:00:00.000Z", - "scheduledAt": "2022-11-31T00:00:00.000Z", "name": "myABTest", - "metrics": [{"name": "myMetric"}], "variants": [ { "index": "AB_TEST_1", @@ -34,7 +35,14 @@ "index": "AB_TEST_2", "trafficPercentage": 50 } - ] + ], + "metrics": [ + { + "name": "myMetric" + } + ], + "scheduledAt": "2022-11-31T00:00:00.000Z", + "endAt": "2022-12-31T00:00:00.000Z" } } } diff --git a/tests/output/go/.golangci.yml b/tests/output/go/.golangci.yml index a81e164d960..e12675c4f2f 100644 --- a/tests/output/go/.golangci.yml +++ b/tests/output/go/.golangci.yml @@ -13,6 +13,7 @@ linters: - goconst - godoclint - gomoddirectives + - ineffassign - ireturn - lll - maintidx