Skip to content

Commit ee6d198

Browse files
committed
feat(clients): flatten body parameters
1 parent e904fe5 commit ee6d198

29 files changed

+502
-431
lines changed

clients/algoliasearch-client-go/README.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,37 +47,34 @@ import "github.com/algolia/algoliasearch-client-go/v4/algolia/search"
4747
client, err := search.NewClient("YOUR_APP_ID", "YOUR_API_KEY")
4848

4949
// Add a new record to your Algolia index
50-
response, err := client.SaveObject(client.NewApiSaveObjectRequest(
51-
"<YOUR_INDEX_NAME>", map[string]any{"objectID": "id", "test": "val"},
52-
))
50+
saveResponse, err := client.SaveObject("<YOUR_INDEX_NAME>", map[string]any{"objectID": "id", "test": "val"})
5351
if err != nil {
5452
// handle the eventual error
5553
panic(err)
5654
}
5755

5856
// use the model directly
59-
print(response)
57+
print(saveResponse)
6058

6159
// Poll the task status to know when it has been indexed
62-
taskResponse, err := searchClient.WaitForTask("<YOUR_INDEX_NAME>", response.TaskID, nil, nil, nil)
60+
_, err = client.WaitForTask("<YOUR_INDEX_NAME>", saveResponse.TaskID)
6361
if err != nil {
6462
panic(err)
6563
}
6664

6765
// Fetch search results, with typo tolerance
68-
response, err := client.Search(client.NewApiSearchRequest(
69-
70-
search.NewEmptySearchMethodParams().SetRequests(
71-
[]search.SearchQuery{*search.SearchForHitsAsSearchQuery(
72-
search.NewEmptySearchForHits().SetIndexName("<YOUR_INDEX_NAME>").SetQuery("<YOUR_QUERY>").SetHitsPerPage(50))}),
73-
))
66+
response, err := client.Search([]search.SearchQuery{
67+
*search.SearchForHitsAsSearchQuery(search.NewSearchForHits("<YOUR_INDEX_NAME>").SetQuery("<YOUR_QUERY>").SetHitsPerPage(50)),
68+
}, nil)
7469
if err != nil {
7570
// handle the eventual error
7671
panic(err)
7772
}
7873

7974
// use the model directly
8075
print(response)
76+
77+
return 0
8178
```
8279

8380
For full documentation, visit the **[Algolia Go API Client](https://www.algolia.com/doc/libraries/go/)**.

generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.swagger.v3.oas.models.servers.Server;
1212
import java.io.File;
1313
import java.util.*;
14+
import java.util.stream.Collectors;
1415
import org.openapitools.codegen.*;
1516
import org.openapitools.codegen.languages.GoClientCodegen;
1617
import org.openapitools.codegen.model.ModelMap;
@@ -78,6 +79,10 @@ public void processOpenAPI(OpenAPI openAPI) {
7879
super.processOpenAPI(openAPI);
7980
Helpers.generateServers(super.fromServers(openAPI.getServers()), additionalProperties);
8081
Timeouts.enrichBundle(openAPI, additionalProperties);
82+
additionalProperties.put(
83+
"appDescription",
84+
Arrays.stream(openAPI.getInfo().getDescription().split("\n")).map(line -> "// " + line).collect(Collectors.joining("\n")).trim()
85+
);
8186
}
8287

8388
@Override
@@ -140,6 +145,126 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
140145
@Override
141146
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> models) {
142147
OperationsMap operations = super.postProcessOperationsWithModels(objs, models);
148+
149+
// Flatten body params to remove the wrapping object
150+
for (CodegenOperation ope : operations.getOperations().getOperation()) {
151+
// clean up the description
152+
String[] lines = ope.unescapedNotes.split("\n");
153+
ope.notes = (lines[0] + "\n" + Arrays.stream(lines).skip(1).map(line -> "// " + line).collect(Collectors.joining("\n"))).trim();
154+
155+
for (CodegenParameter param : ope.optionalParams) {
156+
param.nameInPascalCase = Helpers.capitalize(param.baseName);
157+
}
158+
159+
CodegenParameter bodyParam = ope.bodyParam;
160+
if (bodyParam == null) {
161+
continue;
162+
}
163+
bodyParam.nameInPascalCase = Helpers.capitalize(bodyParam.baseName);
164+
if (!bodyParam.isModel) {
165+
continue;
166+
}
167+
168+
// check for colision with other params
169+
boolean hasCollision = false;
170+
for (CodegenProperty prop : bodyParam.getVars()) {
171+
for (CodegenParameter param : ope.allParams) {
172+
if (param.paramName.equals(prop.baseName)) {
173+
hasCollision = true;
174+
break;
175+
}
176+
}
177+
}
178+
if (hasCollision) {
179+
System.out.println("Operation " + ope.operationId + " has a body param with the same name as another param, skipping flattening");
180+
continue;
181+
}
182+
183+
if (ope.operationId.equals("Browse")) {
184+
System.out.println(
185+
ope.allParams.size() +
186+
" params " +
187+
ope.requiredParams.size() +
188+
" required params " +
189+
ope.optionalParams.size() +
190+
" optional params"
191+
);
192+
}
193+
194+
bodyParam.vendorExtensions.put("x-flat-body", bodyParam.getVars().size() > 0);
195+
196+
if (bodyParam.getVars().size() > 0) {
197+
ope.allParams.removeIf(param -> param.isBodyParam);
198+
ope.requiredParams.removeIf(param -> param.isBodyParam);
199+
ope.optionalParams.removeIf(param -> param.isBodyParam);
200+
}
201+
202+
for (CodegenProperty prop : bodyParam.getVars()) {
203+
// there is no easy way to convert a prop to a param, we need to copy all the fields
204+
CodegenParameter param = new CodegenParameter();
205+
206+
prop.nameInLowerCase = toParamName(prop.baseName);
207+
param.nameInPascalCase = Helpers.capitalize(prop.baseName);
208+
param.paramName = toParamName(prop.baseName);
209+
param.baseName = prop.baseName;
210+
param.baseType = prop.baseType;
211+
param.dataType = prop.dataType;
212+
param.datatypeWithEnum = prop.datatypeWithEnum;
213+
param.description = prop.description;
214+
param.example = prop.example;
215+
param.isModel = prop.isModel;
216+
param.isArray = prop.isArray;
217+
param.isContainer = prop.isContainer;
218+
param.isMap = prop.isMap;
219+
param.isEnum = prop.isEnum;
220+
param.isEnumRef = prop.isEnumRef;
221+
param.isPrimitiveType = prop.isPrimitiveType;
222+
param.isString = prop.isString;
223+
param.isNumeric = prop.isNumeric;
224+
param.isBoolean = prop.isBoolean;
225+
param.isDate = prop.isDate;
226+
param.isDateTime = prop.isDateTime;
227+
param.isFreeFormObject = prop.isFreeFormObject;
228+
param.isNullable = prop.isNullable;
229+
param.jsonSchema = prop.jsonSchema;
230+
param.required = prop.required;
231+
param.vendorExtensions = prop.vendorExtensions;
232+
param.allowableValues = prop.allowableValues;
233+
234+
if (prop.required) {
235+
ope.requiredParams.add(param);
236+
ope.hasRequiredParams = true;
237+
} else {
238+
ope.optionalParams.add(param);
239+
ope.hasOptionalParams = true;
240+
}
241+
ope.allParams.add(param);
242+
}
243+
244+
System.out.println(
245+
ope.operationId +
246+
" has " +
247+
ope.requiredParams.size() +
248+
" required params and " +
249+
ope.optionalParams.size() +
250+
" optional params " +
251+
bodyParam.getVars().size() +
252+
" body params "
253+
);
254+
255+
// If the optional param struct only has 1 param, we can remove the wrapper
256+
if (ope.optionalParams.size() == 1) {
257+
CodegenParameter param = ope.optionalParams.get(0);
258+
259+
// move it to required, it's easier to handle im mustache
260+
ope.hasOptionalParams = false;
261+
ope.optionalParams.clear();
262+
263+
ope.hasRequiredParams = true;
264+
ope.requiredParams.add(param);
265+
}
266+
}
267+
143268
ModelPruner.removeOrphans(this, operations, models);
144269
Helpers.removeHelpers(operations);
145270
GenericPropagator.propagateGenericsToOperations(operations, models);

generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,20 +237,13 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
237237
continue;
238238
}
239239

240-
boolean hasBodyParams = !ope.bodyParams.isEmpty();
240+
boolean hasBodyParams = ope.bodyParam != null;
241241
boolean hasHeaderParams = !ope.headerParams.isEmpty();
242242
boolean hasQueryParams = !ope.queryParams.isEmpty();
243243
boolean hasPathParams = !ope.pathParams.isEmpty();
244244

245245
// If there is nothing but body params, we just check if it's a single param
246-
if (
247-
hasBodyParams &&
248-
!hasHeaderParams &&
249-
!hasQueryParams &&
250-
!hasPathParams &&
251-
ope.bodyParams.size() == 1 &&
252-
!ope.bodyParams.get(0).isArray
253-
) {
246+
if (hasBodyParams && !hasHeaderParams && !hasQueryParams && !hasPathParams && !ope.bodyParam.isArray) {
254247
// At this point the single parameter is already an object, to avoid double wrapping we skip
255248
// it
256249
ope.vendorExtensions.put("x-is-single-body-param", true);

generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void enhanceParameters(Map<String, Object> parameters, Map<String, Object
6060
IJsonSchemaValidationProperties spec = null;
6161
String paramName = null;
6262
// special case if there is only bodyParam which is not an array
63-
if (operation != null && operation.allParams.size() == 1 && operation.bodyParams.size() == 1 && !operation.bodyParam.isArray) {
63+
if (operation != null && operation.allParams.size() == 1 && operation.bodyParam != null && !operation.bodyParam.isArray) {
6464
spec = operation.bodyParam;
6565
paramName = operation.bodyParam.paramName;
6666
}
@@ -71,7 +71,7 @@ public void enhanceParameters(Map<String, Object> parameters, Map<String, Object
7171
if (paramName == null) {
7272
if (parameters != null) {
7373
for (Entry<String, Object> param : parameters.entrySet()) {
74-
CodegenParameter specParam = null;
74+
IJsonSchemaValidationProperties specParam = null;
7575
if (operation != null) {
7676
for (CodegenParameter sp : operation.allParams) {
7777
if (sp.paramName.equals(param.getKey())) {
@@ -83,9 +83,42 @@ public void enhanceParameters(Map<String, Object> parameters, Map<String, Object
8383
throw new CTSException("Parameter " + param.getKey() + " not found in the root parameter");
8484
}
8585
}
86-
Map<String, Object> paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false);
87-
parametersWithDataType.add(paramWithType);
88-
parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType);
86+
// for go, we flatten the body params
87+
if (
88+
language.equals("go") &&
89+
specParam != null &&
90+
((CodegenParameter) specParam).isBodyParam &&
91+
operation != null &&
92+
operation.bodyParam != null &&
93+
operation.bodyParam.isModel &&
94+
operation.bodyParam.required
95+
) {
96+
// check for colision with other params
97+
boolean hasCollision = false;
98+
for (CodegenProperty prop : operation.bodyParam.getVars()) {
99+
for (CodegenParameter otherParam : operation.allParams) {
100+
if (otherParam.paramName.equals(prop.baseName)) {
101+
hasCollision = true;
102+
break;
103+
}
104+
}
105+
}
106+
if (!hasCollision) {
107+
// flatten the body params by skipping one level
108+
Map<String, Object> bodyParams = (Map<String, Object>) param.getValue();
109+
for (CodegenProperty prop : operation.bodyParam.getVars()) {
110+
Object nestedParam = bodyParams.get(prop.baseName);
111+
112+
Map<String, Object> paramWithType = traverseParams(prop.baseName, nestedParam, prop, "", 0, false);
113+
parametersWithDataType.add(paramWithType);
114+
parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType);
115+
}
116+
}
117+
} else {
118+
Map<String, Object> paramWithType = traverseParams(param.getKey(), param.getValue(), specParam, "", 0, false);
119+
parametersWithDataType.add(paramWithType);
120+
parametersWithDataTypeMap.put((String) paramWithType.get("key"), paramWithType);
121+
}
89122
}
90123
}
91124
} else {

generators/src/main/java/com/algolia/codegen/cts/tests/Snippet.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public void addMethodCall(Map<String, Object> context, ParametersWithDataType pa
8585
}
8686
}
8787

88+
TestsGenerator.setOptionalParameters(ope, context);
89+
8890
paramsType.enhanceParameters(parameters, context, ope);
8991
} catch (CTSException e) {
9092
e.setTestName((String) context.get("testName"));

generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
156156
// default to true because most api calls are asynchronous
157157
testOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true));
158158

159+
setOptionalParameters(ope, stepOut);
159160
addRequestOptions(paramsType, step.requestOptions, stepOut);
160161

161162
methodCount++;

generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
import java.nio.file.Paths;
1212
import java.util.*;
1313
import org.apache.commons.lang3.ArrayUtils;
14-
import org.openapitools.codegen.CodegenModel;
15-
import org.openapitools.codegen.CodegenOperation;
16-
import org.openapitools.codegen.SupportingFile;
14+
import org.openapitools.codegen.*;
1715

1816
public abstract class TestsGenerator {
1917

@@ -134,4 +132,30 @@ protected void addRequestOptions(ParametersWithDataType paramsType, RequestOptio
134132
output.put("requestOptions", requestOptions);
135133
}
136134
}
135+
136+
public static void setOptionalParameters(CodegenOperation ope, Map<String, Object> test) {
137+
long bodyPropsOptional = 0;
138+
if (ope.bodyParam != null) {
139+
if (ope.bodyParam.isModel && ope.bodyParam.required) {
140+
// check for colision with other params
141+
boolean hasCollision = false;
142+
for (CodegenProperty prop : ope.bodyParam.getVars()) {
143+
for (CodegenParameter param : ope.allParams) {
144+
if (param.paramName.equals(prop.baseName)) {
145+
hasCollision = true;
146+
break;
147+
}
148+
}
149+
}
150+
151+
if (!hasCollision) {
152+
bodyPropsOptional = ope.bodyParam.getVars().stream().filter(prop -> !prop.required).count();
153+
}
154+
}
155+
}
156+
157+
// hasOptionalWrapper if there is more that one optional param, after the body has been
158+
// flattened, only relevant for go
159+
test.put("hasOptionalWrapper", ope.optionalParams.size() + bodyPropsOptional > 1);
160+
}
137161
}

generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
161161
test.put("hasParams", ope.hasParams);
162162
test.put("isHelper", isHelper);
163163

164+
setOptionalParameters(ope, test);
164165
addRequestOptions(paramsType, req.requestOptions, test);
165166

166167
// Determines whether the endpoint is expected to return a response payload deserialized

playground/go/analytics.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ func testAnalytics(appID, apiKey string) int {
1313
panic(err)
1414
}
1515

16-
getTopFilterForAttributeResponse, err := analyticsClient.GetTopFilterForAttribute(
17-
analyticsClient.NewApiGetTopFilterForAttributeRequest("myAttribute1,myAttribute2", indexName),
18-
)
16+
getTopFilterForAttributeResponse, err := analyticsClient.GetTopFilterForAttribute("myAttribute1,myAttribute2", indexName, nil)
1917
if err != nil {
2018
fmt.Printf("request error with GetTopFilterForAttribute: %v\n", err)
2119
return 1

playground/go/ingestion.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ func testIngestion(appID, apiKey string) int {
1313
}
1414

1515
// another example to generate payload for a request.
16-
_, payload, err := ingestionClient.ListTasksWithHTTPInfo(ingestionClient.NewApiListTasksRequest())
16+
res, err := ingestionClient.ListTasks(nil)
1717
if err != nil {
1818
fmt.Printf("request error: %v\n", err)
1919

2020
return 1
2121
}
2222

23-
fmt.Println(string(payload))
23+
fmt.Println(res)
2424

2525
return 0
2626
}

0 commit comments

Comments
 (0)