From e7498754efa30619a10e784228939e4fc65ec07f Mon Sep 17 00:00:00 2001 From: Olivier Lando Date: Fri, 26 Jul 2024 14:49:31 +0200 Subject: [PATCH] Properly encode filterBy parameter --- .../video/api/client/generator/Csharp.java | 12 +- .../java/video/api/client/generator/Go.java | 2 +- .../java/video/api/client/generator/Java.java | 8 + .../video/api/client/generator/Swift5.java | 19 +- .../api/client/generator/TypeScript.java | 15 +- oas_apivideo.yaml | 253 ++++++++++++++++-- templates/csharp/ApiClient.mustache | 20 ++ templates/csharp/model.mustache | 2 +- templates/csharp/post-generate.sh | 1 + .../csharp/statics/src/Model/DeepObject.cs | 4 + templates/go/api.mustache | 6 +- templates/go/client.mustache | 41 +++ .../libraries/okhttp-gson/ApiClient.mustache | 26 +- .../api/client/api/models/DeepObject.java | 4 + .../api/client/api/models/DeepObject.java | 4 + .../api/client/api/models/DeepObject.java | 4 + templates/nodejs/src/api/api.ts.mustache | 13 +- templates/python/api_client.mustache | 6 +- templates/swift5/APIHelper.mustache | 4 - templates/swift5/api.mustache | 23 +- templates/swift5/modelObject.mustache | 22 ++ 21 files changed, 434 insertions(+), 55 deletions(-) create mode 100644 templates/csharp/statics/src/Model/DeepObject.cs create mode 100644 templates/java/statics/android-uploader/src/main/java/video/api/client/api/models/DeepObject.java create mode 100644 templates/java/statics/android/src/main/java/video/api/client/api/models/DeepObject.java create mode 100644 templates/java/statics/java/src/main/java/video/api/client/api/models/DeepObject.java diff --git a/apivideo-generator/src/main/java/video/api/client/generator/Csharp.java b/apivideo-generator/src/main/java/video/api/client/generator/Csharp.java index 38470689..ae445bc8 100644 --- a/apivideo-generator/src/main/java/video/api/client/generator/Csharp.java +++ b/apivideo-generator/src/main/java/video/api/client/generator/Csharp.java @@ -173,13 +173,17 @@ public Map postProcessOperationsWithModels(Map o @Override public Map postProcessModels(Map objs) { Map res = super.postProcessModels(objs); - List models = (List) res.get("models"); + ArrayList> models = (ArrayList>) res.get("models"); - models.forEach(model -> { - ((CodegenModel)model.get("model")).vars.forEach(var -> { + models.forEach(map -> { + CodegenModel model = map.get("model"); + if(model.isMap) { + model.vendorExtensions.put("x-implements", Collections.singletonList("DeepObject")); + } + model.vars.forEach(var -> { if(var.name.equals("_AccessToken")) var.name = "AccessToken"; if (var.defaultValue != null) { - ((CodegenModel)model.get("model")).vendorExtensions.put("x-has-defaults", true); + model.vendorExtensions.put("x-has-defaults", true); } }); }); diff --git a/apivideo-generator/src/main/java/video/api/client/generator/Go.java b/apivideo-generator/src/main/java/video/api/client/generator/Go.java index 304fae04..75c7a7e7 100644 --- a/apivideo-generator/src/main/java/video/api/client/generator/Go.java +++ b/apivideo-generator/src/main/java/video/api/client/generator/Go.java @@ -94,7 +94,7 @@ public Map postProcessOperationsWithModels(Map o operation.allParams.stream().filter(p -> p.baseName.equals(queryParam.baseName)).forEach(p -> p.dataType = "time.Time"); queryParam.dataType = "time.Time"; } - if(queryParam.vendorExtensions.containsKey("x-is-deep-object")) additionalImports.add("fmt"); + //if(queryParam.vendorExtensions.containsKey("x-is-deep-object")) additionalImports.add("fmt"); }); // overwrite operationId & nickname values of the operation with the x-client-action diff --git a/apivideo-generator/src/main/java/video/api/client/generator/Java.java b/apivideo-generator/src/main/java/video/api/client/generator/Java.java index 6444d637..e68412f6 100644 --- a/apivideo-generator/src/main/java/video/api/client/generator/Java.java +++ b/apivideo-generator/src/main/java/video/api/client/generator/Java.java @@ -168,6 +168,14 @@ private void handlePagination(List allModels, CodegenOperation operation @Override public Map postProcessModels(Map objs) { Map stringObjectMap = super.postProcessModels(objs); + ArrayList> models = (ArrayList>) stringObjectMap.get("models"); + models.stream().forEach((map) -> { + CodegenModel model = map.get("model"); + if(model.isMap) { + ((List)model.vendorExtensions.get("x-implements") ).add("DeepObject"); + } + }); + ((ArrayList) stringObjectMap.get("imports")).removeIf((v) -> ((Map) v).values().contains("org.openapitools.jackson.nullable.JsonNullable")); return stringObjectMap; } diff --git a/apivideo-generator/src/main/java/video/api/client/generator/Swift5.java b/apivideo-generator/src/main/java/video/api/client/generator/Swift5.java index 9e365a21..e4a79e14 100644 --- a/apivideo-generator/src/main/java/video/api/client/generator/Swift5.java +++ b/apivideo-generator/src/main/java/video/api/client/generator/Swift5.java @@ -59,7 +59,16 @@ public Map postProcessOperationsWithModels(Map o } applyToAllParams(operation, (params) -> params.forEach(pp -> { - if("deepObject".equals(pp.style)) pp.collectionFormat = "deepObject"; + if(pp.vendorExtensions != null && pp.vendorExtensions.containsKey("x-is-deep-object")) { + if(!pp.getHasVars()) { + pp.vendorExtensions.remove("x-is-deep-object"); + } else { + if("deepObject".equals(pp.style)) { + pp.collectionFormat = "deepObject"; + } + } + } + })); applyToAllParams(operation, (params) -> params.removeIf(pp -> getVendorExtensionBooleanValue(pp, VENDOR_X_CLIENT_IGNORE)) ); @@ -80,6 +89,7 @@ private void applyToAllParams(CodegenOperation operation, Consumer postProcessModels(Map objs) { Map stringObjectMap = super.postProcessModels(objs); @@ -108,6 +117,12 @@ public Map postProcessModels(Map objs) { List> models = (List)objs.get("models"); models.forEach(map -> { CodegenModel model = ((CodegenModel)map.get("model")); + if(model.vendorExtensions != null && model.vendorExtensions.containsKey("x-is-deep-object")) { + model.allVars.forEach(var -> { + String nameWithoutEndDigits = model.name.replaceAll("_\\d+$", ""); + var.vendorExtensions.put("x-model-name", nameWithoutEndDigits); + }); + } model.vars.forEach(var -> { if(var.vendorExtensions.containsKey("x-optional-nullable") && var.dataType.equals("String")) { var.dataType = "NullableString"; diff --git a/apivideo-generator/src/main/java/video/api/client/generator/TypeScript.java b/apivideo-generator/src/main/java/video/api/client/generator/TypeScript.java index 475be613..449c4d2d 100644 --- a/apivideo-generator/src/main/java/video/api/client/generator/TypeScript.java +++ b/apivideo-generator/src/main/java/video/api/client/generator/TypeScript.java @@ -637,7 +637,20 @@ protected String getParameterDataType(Parameter parameter, Schema p) { } else if (ModelUtils.isMapSchema(p)) { inner = (Schema) p.getAdditionalProperties(); return "{ [key: string]: " + this.getParameterDataType(parameter, inner) + "; }"; - } else if (ModelUtils.isStringSchema(p)) { + } else if (p != null && + parameter.getName() != null && + parameter.getIn().equalsIgnoreCase("query") && + parameter.getExplode() && + parameter.getExtensions() != null && + parameter.get$ref() != null && + parameter.getExtensions().containsKey("x-is-deep-object")) { + String refName = parameter.get$ref().substring(parameter.get$ref().lastIndexOf("/") + 1); + refName = refName.replace("_", ""); + refName = refName.substring(0, 1).toUpperCase() + refName.substring(1); + + return refName; + } + else if (ModelUtils.isStringSchema(p)) { // Handle string enums if (p.getEnum() != null) { return enumValuesToEnumTypeUnion(p.getEnum(), "string"); diff --git a/oas_apivideo.yaml b/oas_apivideo.yaml index 3113e520..5ce48786 100644 --- a/oas_apivideo.yaml +++ b/oas_apivideo.yaml @@ -11857,6 +11857,68 @@ paths: x-group-parameters: true x-doctave: code-samples: + - language: java + code: | + FilterBy2 filterBy = new FilterBy2(); + + filterBy.setBrowser(Collections.singletonList("Chrome")); + filterBy.setContinent(Arrays.asList(FilterBy2.ContinentEnum.NA, FilterBy2.ContinentEnum.EU)); + filterBy.setMediaType(FilterBy2.MediaTypeEnum.VIDEO); + filterBy.setTag("test"); + + + AnalyticsAggregatedMetricsResponse res = apiClient.analytics().getAggregatedMetrics("play", "total").filterBy(filterBy).execute(); + System.out.println(res.getData()); + - language: node + code: | + const res = await client.analytics.getAggregatedMetrics({ + aggregation: 'total', + metric: 'play', + filterBy: { + browser: ['chrome', 'firefox'], + continent: ['EU', 'AF'], + tag: 'test', + }, + }); + console.log(res.data); + - language: csharp + code: | + FilterBy2 filterBy = new FilterBy2 + { + continent = new List { "EU", "US" }, + devicetype = new List { "phone" }, + tag = "test" + }; + AnalyticsAggregatedMetricsResponse res = apiClient.Analytics() + .getAggregatedMetrics("play", "total").From(new DateTime(2024, 7, 1)).FilterBy(filterBy).execute(); + - language: go + code: | + res, err := cl.Analytics.GetAggregatedMetrics("play", "day", AnalyticsApiGetAggregatedMetricsRequest{ + filterBy: &FilterBy2{ + Continent: &[]string{"EU", "AA"}, + DeviceType: &[]string{"computer", "phone"}, + Tag: PtrString("tag"), + }, + }) + - language: python + code: | + res = self.api.get_aggregated_metrics(metric='play', aggregation='count', filter_by=FilterBy2( + device_type=["computer", "phone"], + tag="test", + ), + ) + - language: swift + code: | + AnalyticsAPI.getAggregatedMetrics( + metric: .play, + aggregation: .total, + filterBy: FilterBy2( + continent: [.an, .eu], + tag: "test" + ) + ) { analytics, error in + // ... + } '/data/buckets/{metric}/{breakdown}': get: tags: @@ -11899,9 +11961,9 @@ paths: - `media-type`: Returns analytics based on the type of content. Possible values: `video` and `live-stream`. - `continent`: Returns analytics based on the viewers' continent. The list of supported continents names are based on the [GeoNames public database](https://www.geonames.org/countries/). Possible values are: `AS`, `AF`, `NA`, `SA`, `AN`, `EU`, `AZ`. - `country`: Returns analytics based on the viewers' country. The list of supported country names are based on the [GeoNames public database](https://www.geonames.org/countries/). - - `device-type`: Returns analytics based on the type of device used by the viewers. Possible response values are: `computer`, `phone`, `tablet`, `tv`, `console`, `wearable`, `unknown`. - - `operating-system`: Returns analytics based on the operating system used by the viewers. Response values include `windows`, `mac osx`, `android`, `ios`, `linux`. - - `browser`: Returns analytics based on the browser used by the viewers. Response values include `chrome`, `firefox`, `edge`, `opera`. + - `device-type`: Returns analytics based on the type of device used by the viewers. Response values can include: `computer`, `phone`, `tablet`, `tv`, `console`, `wearable`, `unknown`. + - `operating-system`: Returns analytics based on the operating system used by the viewers. Response values can include `windows`, `mac osx`, `android`, `ios`, `linux`. + - `browser`: Returns analytics based on the browser used by the viewers. Response values can include `chrome`, `firefox`, `edge`, `opera`. style: simple explode: false required: true @@ -12084,6 +12146,69 @@ paths: x-client-paginated: true x-doctave: code-samples: + - language: java + code: | + FilterBy2 filterBy = new FilterBy2(); + + filterBy.setBrowser(Collections.singletonList("Chrome")); + filterBy.setContinent(Arrays.asList(FilterBy2.ContinentEnum.NA, FilterBy2.ContinentEnum.EU)); + filterBy.setMediaType(FilterBy2.MediaTypeEnum.VIDEO); + filterBy.setTag("test"); + + Page res = apiClient.analytics().getMetricsBreakdown("play", "media-id").filterBy(filterBy).pageSize(30).execute(); + for (AnalyticsMetricsBreakdownResponseData item : res.getItems()) { + System.out.println(item.getDimensionValue() + ": " + item.getMetricValue()); + } + - language: node + code: | + const res = await client.analytics.getMetricsBreakdown({ + breakdown: 'continent', + metric: 'play', + filterBy: { + browser: ['chrome', 'firefox'], + continent: ['EU', 'AF'], + tag: 'test', + }, + }); + console.log(res.data); + - language: csharp + code: | + FilterBy2 filterBy = new FilterBy2 + { + continent = new List { "EU", "US" }, + devicetype = new List { "phone" }, + tag = "test" + }; + Page page = apiClient.Analytics() + .getMetricsBreakdown("play", "continent").From(new DateTime(2024, 7, 1)).FilterBy(filterBy).execute(); + - language: go + code: | + res, err := cl.Analytics.GetMetricsBreakdown("play", "continent", AnalyticsApiGetMetricsBreakdownRequest{ + filterBy: &FilterBy2{ + Continent: &[]string{"EU", "AA"}, + DeviceType: &[]string{"computer", "phone"}, + Tag: PtrString("tag"), + }, + }) + - language: python + code: | + res = self.api.get_metrics_breakdown(metric='play', breakdown='continent', filter_by=FilterBy2( + device_type=["computer", "phone"], + tag="test", + ), + ) + - language: swift + code: | + AnalyticsAPI.getMetricsBreakdown( + metric: .play, + breakdown: .browser, + filterBy: FilterBy2( + continent: [.an, .eu], + tag: "test" + ) + ) { analytics, error in + // ... + } '/data/timeseries/{metric}': get: tags: @@ -12300,6 +12425,69 @@ paths: x-client-paginated: true x-doctave: code-samples: + - language: java + code: | + FilterBy2 filterBy = new FilterBy2(); + + filterBy.setBrowser(Collections.singletonList("Chrome")); + filterBy.setContinent(Arrays.asList(FilterBy2.ContinentEnum.NA, FilterBy2.ContinentEnum.EU)); + filterBy.setMediaType(FilterBy2.MediaTypeEnum.VIDEO); + filterBy.setTag("test"); + + Page res = apiClient.analytics().getMetricsOverTime("play").filterBy(filterBy).pageSize(30).execute(); + for (AnalyticsMetricsOverTimeResponseData item : res.getItems()) { + System.out.println(item.getEmittedAt() + ": " + item.getMetricValue()); + } + - language: node + code: | + const res = await client.analytics.getMetricsOverTime({ + metric: 'play', + filterBy: { + mediaType: 'video', + browser: ['chrome', 'firefox'], + continent: ['EU', 'AF'], + tag: "test", + }, + }); + + console.log(res); + - language: csharp + code: | + FilterBy2 filterBy = new FilterBy2 + { + continent = new List { "EU", "US" }, + devicetype = new List { "phone" }, + tag = "test" + }; + Page res = apiClient.Analytics() + .getMetricsOverTime("play").From(new DateTime(2024, 7, 1)).FilterBy(filterBy).execute(); + - language: go + code: | + res, err := cl.Analytics.GetMetricsOverTime("play", AnalyticsApiGetMetricsOverTimeRequest{ + filterBy: &FilterBy2{ + Continent: &[]string{"EU", "AA"}, + DeviceType: &[]string{"computer", "phone"}, + Tag: PtrString("tag"), + }, + }) + - language: python + code: | + res = self.api.get_metrics_over_time(metric='play', filter_by=FilterBy2( + device_type=["computer", "phone"], + tag="test", + ), + ) + - language: swift + code: | + AnalyticsAPI.getMetricsOverTime( + metric: .play, + filterBy: FilterBy2( + continent: [.an, .eu], + tag: "test" + ) + ) { analytics, error in + // ... + } /webhooks: get: tags: @@ -14629,7 +14817,6 @@ components: required: - context - data - - pagination analytics-metrics-breakdown-response: title: Analytics response for metrics breakdown by dimension type: object @@ -14733,7 +14920,7 @@ components: data: description: Returns an array of metrics and the timestamps . type: array - items: + items: type: object properties: emittedAt: @@ -15053,7 +15240,7 @@ components: Use this parameter to filter the API's response based on different data dimensions. You can serialize filters in your query to receive more detailed breakdowns of your analytics. - If you do not set a value for `filterBy`, the API returns the full dataset for your project. - - The API only accepts the `mediaId` and `mediaType` filters when you call `/data/metrics/play/total`. + - The API only accepts the `mediaId` and `mediaType` filters when you call `/data/metrics/play/total` or `/data/buckets/play-total/media-id`. These are the available breakdown dimensions: @@ -15061,47 +15248,61 @@ components: - `mediaType`: Returns analytics based on the type of content. Possible values: `video` and `live-stream`. - `continent`: Returns analytics based on the viewers' continent. The list of supported continents names are based on the [GeoNames public database](https://www.geonames.org/countries/). You must use the ISO-3166 alpha2 format, for example `EU`. Possible values are: `AS`, `AF`, `NA`, `SA`, `AN`, `EU`, `AZ`. - `country`: Returns analytics based on the viewers' country. The list of supported country names are based on the [GeoNames public database](https://www.geonames.org/countries/). You must use the ISO-3166 alpha2 format, for example `FR`. - - `deviceType`: Returns analytics based on the type of device used by the viewers. Possible response values are: `computer`, `phone`, `tablet`, `tv`, `console`, `wearable`, `unknown`. - - `operatingSystem`: Returns analytics based on the operating system used by the viewers. Response values include `windows`, `mac osx`, `android`, `ios`, `linux`. - - `browser`: Returns analytics based on the browser used by the viewers. Response values include `chrome`, `firefox`, `edge`, `opera`. + - `deviceType`: Returns analytics based on the type of device used by the viewers. Response values can include: `computer`, `phone`, `tablet`, `tv`, `console`, `wearable`, `unknown`. + - `operatingSystem`: Returns analytics based on the operating system used by the viewers. Response values can include `windows`, `mac osx`, `android`, `ios`, `linux`. + - `browser`: Returns analytics based on the browser used by the viewers. Response values can include `chrome`, `firefox`, `edge`, `opera`. - `tag`: Returns analytics for videos using this tag. This filter only accepts a single value and is case sensitive. Read more about tagging your videos [here](https://docs.api.video/vod/tags-metadata). in: query required: false - example: {"mediaType":"video","continent":"EU","country":"FR"} + example: filterBy[country]=FR&filterBy[operatingSystem]=windows&filterBy[browser][]=firefox&filterBy[browser][]=chrome&filterBy[tag]=Cool videos style: deepObject + x-is-deep-object: true explode: true schema: + x-is-deep-object: true type: object properties: mediaId: - type: string + type: array + items: + type: string description: Returns analytics based on the unique identifiers of a video or a live stream. - example: vi4blUQJFrYWbaG44NChkH27 + example: ['vi4blUQJFrYWbaG44NChkH27'] mediaType: type: string enum: [video, live-stream] example: video continent: - type: string + type: array + items: + type: string + enum: [AS, AF, NA, SA, AN, EU, AZ] description: Returns analytics based on the viewers' continent. The list of supported continents names are based on the [GeoNames public database](https://www.geonames.org/countries/). You must use the ISO-3166 alpha2 format, for example `EU`. - enum: [AS, AF, NA, SA, AN, EU, AZ] - example: EU + example: ['EU'] country: description: Returns analytics based on the viewers' country. The list of supported country names are based on the [GeoNames public database](https://www.geonames.org/countries/). You must use the ISO-3166 alpha2 format, for example `FR`. - type: string - example: FR + type: array + items: + type: string + example: ['FR'] deviceType: - type: string - description: 'Returns analytics based on the type of device used by the viewers. Possible response values are: `computer`, `phone`, `tablet`, `tv`, `console`, `wearable`, `unknown`.' - example: computer + type: array + items: + type: string + description: 'Returns analytics based on the type of device used by the viewers. Response values can include: `computer`, `phone`, `tablet`, `tv`, `console`, `wearable`, `unknown`.' + example: ['computer'] operatingSystem: - type: string - description: Returns analytics based on the operating system used by the viewers. Response values include `windows`, `mac osx`, `android`, `ios`, `linux`. - example: windows + type: array + items: + type: string + description: Returns analytics based on the operating system used by the viewers. Response values can include `windows`, `mac osx`, `android`, `ios`, `linux`. + example: ['windows'] browser: - description: Returns analytics based on the browser used by the viewers. Response values include `chrome`, `firefox`, `edge`, `opera`. - type: string - example: firefox + description: Returns analytics based on the browser used by the viewers. Response values can include `chrome`, `firefox`, `edge`, `opera`. + type: array + items: + type: string + example: ['firefox'] tag: type: string description: Returns analytics for videos using this tag. This filter only accepts a single value and is case sensitive. Read more about tagging your videos [here](https://docs.api.video/vod/tags-metadata). diff --git a/templates/csharp/ApiClient.mustache b/templates/csharp/ApiClient.mustache index 03987425..2915ac3d 100644 --- a/templates/csharp/ApiClient.mustache +++ b/templates/csharp/ApiClient.mustache @@ -19,6 +19,7 @@ using RestSharp.Portable.HttpClient; {{^netStandard}} using RestSharp; {{/netStandard}} +using ApiVideo.Model; namespace {{packageName}}.Client { @@ -680,6 +681,25 @@ namespace {{packageName}}.Client parameters.Add(new KeyValuePair(name + "[" + entry.Key + "]", ParameterToString(entry.Value))); } } + else if (value is DeepObject) { + var dict2 = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(value, Newtonsoft.Json.Formatting.Indented)); + foreach (var entry in dict2) + { + if (entry.Value != null) + { + if (entry.Value is IList) + { + var valueCollection = entry.Value as IEnumerable; + foreach (object item in valueCollection) + { + parameters.Add(new KeyValuePair(name + "[" + entry.Key + "][]", ParameterToString(item))); + } + } else { + parameters.Add(new KeyValuePair(name + "[" + entry.Key + "]", ParameterToString(entry.Value))); + } + } + } + } else { parameters.Add(new KeyValuePair(name, ParameterToString(value))); diff --git a/templates/csharp/model.mustache b/templates/csharp/model.mustache index 9c8b5b19..40cc2096 100644 --- a/templates/csharp/model.mustache +++ b/templates/csharp/model.mustache @@ -13,7 +13,7 @@ namespace {{packageName}}.Model { /// {{description}} /// [DataContract] - public class {{classname}}{{#parent}} : {{{parent}}}{{/parent}} { + public class {{classname}}{{#parent}} : {{{parent}}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}: {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}} { {{#vars}} {{^vendorExtensions.x-optional-nullable}} /// /// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{{description}}}{{/description}} diff --git a/templates/csharp/post-generate.sh b/templates/csharp/post-generate.sh index afff2132..0ff3beed 100755 --- a/templates/csharp/post-generate.sh +++ b/templates/csharp/post-generate.sh @@ -6,6 +6,7 @@ cp ../../templates/common-resources/LICENSE ./ mv src/ApiVideo ApiVideo rm -Rf src mv ApiVideo src +cp -R ../../templates/csharp/statics/src/Model/DeepObject.cs ./src/Model/ rm .travis.yml rm build.bat rm build.sh diff --git a/templates/csharp/statics/src/Model/DeepObject.cs b/templates/csharp/statics/src/Model/DeepObject.cs new file mode 100644 index 00000000..651fc627 --- /dev/null +++ b/templates/csharp/statics/src/Model/DeepObject.cs @@ -0,0 +1,4 @@ +namespace ApiVideo.Model { + public interface DeepObject { + } +} \ No newline at end of file diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 3af307d0..3d638606 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -477,11 +477,7 @@ func (s *{{{classname}}}Service) {{nickname}}WithContext(ctx context.Context{{#r {{/isCollectionFormatMulti}} {{^isCollectionFormatMulti}} {{#vendorExtensions.x-is-deep-object}} - if r.{{paramName}} != nil && len(*r.{{paramName}}) > 0 { - for k, v := range *r.{{paramName}} { - localVarQueryParams.Add(fmt.Sprintf("{{baseName}}[%s]", k), v) - } - } + addDeepQueryParams(r.{{paramName}}, "{{paramName}}", localVarQueryParams) {{/vendorExtensions.x-is-deep-object}} {{^vendorExtensions.x-is-deep-object}} localVarQueryParams.Add("{{baseName}}", parameterToString(*r.{{paramName}}, "{{#collectionFormat}}{{collectionFormat}}{{/collectionFormat}}")) diff --git a/templates/go/client.mustache b/templates/go/client.mustache index 8443cf54..6e5bfc85 100644 --- a/templates/go/client.mustache +++ b/templates/go/client.mustache @@ -584,3 +584,44 @@ func parameterToString(obj interface{}, collectionFormat string) string { return fmt.Sprintf("%v", obj) } + + +func addDeepQueryParams(filter interface{}, prefix string, queryParams url.Values) { + v := reflect.ValueOf(filter) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + switch v.Kind() { + case reflect.Struct: + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldType := t.Field(i) + + if field.Kind() == reflect.Ptr && !field.IsNil() { + field = field.Elem() + } + + lowercaseFirstChar := strings.ToLower(string(fieldType.Name[0])) + restOfString := fieldType.Name[1:] + lowercaseName := lowercaseFirstChar + restOfString + + if field.Kind() == reflect.Slice { + for j := 0; j < field.Len(); j++ { + item := field.Index(j) + queryParams.Add(fmt.Sprintf("%s[%s][%d]", prefix, lowercaseName, j), item.String()) + } + } else if field.Kind() == reflect.String { + queryParams.Add(fmt.Sprintf("%s[%s]", prefix, lowercaseName), field.String()) + } + } + case reflect.Map: + for _, key := range v.MapKeys() { + val := v.MapIndex(key) + queryParams.Add(fmt.Sprintf("%s[%s]", prefix, key.String()), fmt.Sprintf("%v", val)) + } + default: + fmt.Println("Unsupported type") + } +} \ No newline at end of file diff --git a/templates/java/libraries/okhttp-gson/ApiClient.mustache b/templates/java/libraries/okhttp-gson/ApiClient.mustache index 58fddec9..51381e26 100644 --- a/templates/java/libraries/okhttp-gson/ApiClient.mustache +++ b/templates/java/libraries/okhttp-gson/ApiClient.mustache @@ -3,6 +3,7 @@ package {{invokerPackage}}; import {{invokerPackage}}.auth.ApiVideoAuthInterceptor; +import {{invokerPackage}}.models.DeepObject; {{#dynamicOperations}} import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; @@ -28,6 +29,7 @@ import org.joda.time.format.DateTimeFormatter; import org.threeten.bp.LocalDate; import org.threeten.bp.OffsetDateTime; import org.threeten.bp.format.DateTimeFormatter; +import org.threeten.bp.ZoneId; {{/threetenbp}} {{#hasOAuthMethods}} import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; @@ -54,7 +56,6 @@ import java.text.DateFormat; import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -import java.time.ZoneId; {{/java8}} {{/threetenbp}} import java.util.*; @@ -465,10 +466,10 @@ public class ApiClient { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); String formattedDate; - if (param instanceof Date) { + {{^threetenbp}}{{#java8}}if (param instanceof Date) { OffsetDateTime odt = ((Date) param).toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime(); formattedDate = odt.format(formatter); - } else if (param instanceof OffsetDateTime) { + } else {{/java8}}{{/threetenbp}}if (param instanceof OffsetDateTime) { formattedDate = ((OffsetDateTime) param).format(formatter); } else { OffsetDateTime odt = ((LocalDate) param).atStartOfDay(ZoneId.systemDefault()).toOffsetDateTime(); @@ -515,6 +516,25 @@ public class ApiClient { return params; } + + if (value instanceof DeepObject) { + Map map = json.deserialize(json.serialize(value), Map.class); + map.forEach((k, v) -> { + if (k != null) { + if(v instanceof List) { + List list = (List) v; + for (int i = 0; i < list.size(); i++) { + Object item = list.get(i); + params.add(new Pair(name + "[" + k + "][" + i + "]", item == null ? null : item.toString())); + } + } else { + params.add(new Pair(name + "[" + k + "]", v == null ? null : v.toString())); + } + } + }); + return params; + } + params.add(new Pair(name, parameterToString(value))); return params; } diff --git a/templates/java/statics/android-uploader/src/main/java/video/api/client/api/models/DeepObject.java b/templates/java/statics/android-uploader/src/main/java/video/api/client/api/models/DeepObject.java new file mode 100644 index 00000000..eed8c2ce --- /dev/null +++ b/templates/java/statics/android-uploader/src/main/java/video/api/client/api/models/DeepObject.java @@ -0,0 +1,4 @@ +package video.api.client.api.models; + +public interface DeepObject { +} diff --git a/templates/java/statics/android/src/main/java/video/api/client/api/models/DeepObject.java b/templates/java/statics/android/src/main/java/video/api/client/api/models/DeepObject.java new file mode 100644 index 00000000..eed8c2ce --- /dev/null +++ b/templates/java/statics/android/src/main/java/video/api/client/api/models/DeepObject.java @@ -0,0 +1,4 @@ +package video.api.client.api.models; + +public interface DeepObject { +} diff --git a/templates/java/statics/java/src/main/java/video/api/client/api/models/DeepObject.java b/templates/java/statics/java/src/main/java/video/api/client/api/models/DeepObject.java new file mode 100644 index 00000000..eed8c2ce --- /dev/null +++ b/templates/java/statics/java/src/main/java/video/api/client/api/models/DeepObject.java @@ -0,0 +1,4 @@ +package video.api.client.api.models; + +public interface DeepObject { +} diff --git a/templates/nodejs/src/api/api.ts.mustache b/templates/nodejs/src/api/api.ts.mustache index fe32802d..9d85a329 100644 --- a/templates/nodejs/src/api/api.ts.mustache +++ b/templates/nodejs/src/api/api.ts.mustache @@ -314,7 +314,18 @@ export default class {{classname}} { if (typeof {{paramName}} !== 'object') { throw new Error(`${ {{paramName}} } is not an object`); } - Object.keys({{paramName}}).forEach((k) => urlSearchParams.append("{{baseName}}["+k+"]", ObjectSerializer.serialize({{paramName}}[k], "string", ""))); + Object.keys({{paramName}}).forEach((k) => { + if(({{paramName}} as any)[k] instanceof Object) { + Object.keys(({{paramName}} as any)[k]).forEach((key) => { + urlSearchParams.append( + `{{baseName}}[${k}][${key}]`, + ObjectSerializer.serialize(({{baseName}} as any)[k][key], 'string', '') + ); + }); + } else { + urlSearchParams.append("{{baseName}}["+k+"]", ObjectSerializer.serialize(({{paramName}} as any)[k], "string", "")) + } + }); {{/ vendorExtensions.x-is-deep-object }} {{^ vendorExtensions.x-is-deep-object }} {{#isArray}} diff --git a/templates/python/api_client.mustache b/templates/python/api_client.mustache index 6a1348e4..2060a11d 100644 --- a/templates/python/api_client.mustache +++ b/templates/python/api_client.mustache @@ -527,7 +527,11 @@ class ApiClient(object): if k in collection_formats: collection_format = collection_formats[k] if collection_format == 'deepObject': - new_params.extend((k+'['+value+']', v[value]) for value in v) + for value in v: + if isinstance(v[value], list): + new_params.extend((k + '[' + value + '][]', item) for item in v[value]) + else: + new_params.append((k+'['+value+']', v[value])) elif collection_format == 'multi': new_params.extend((k, value) for value in v) else: diff --git a/templates/swift5/APIHelper.mustache b/templates/swift5/APIHelper.mustache index 78c98979..ca647781 100644 --- a/templates/swift5/APIHelper.mustache +++ b/templates/swift5/APIHelper.mustache @@ -78,10 +78,6 @@ import Vapor{{/useVapor}} result.append(URLQueryItem(name: item.key, value: "\(value)")) } } - - if destination.isEmpty { - return nil - } return destination } } diff --git a/templates/swift5/api.mustache b/templates/swift5/api.mustache index 1871ab7a..e4327d6b 100644 --- a/templates/swift5/api.mustache +++ b/templates/swift5/api.mustache @@ -555,11 +555,15 @@ extension {{projectName}}API { {{/hasFormParams}} {{/bodyParam}}{{#hasQueryParams}} var localVariableUrlComponents = URLComponents(string: localVariableURLString) - localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}} - {{#queryParams}} - {{> _param}}, - {{/queryParams}} - ]){{/hasQueryParams}}{{^hasQueryParams}} + localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}}{{#queryParams}}{{^vendorExtensions.x-is-deep-object}} + {{> _param}},{{/vendorExtensions.x-is-deep-object}}{{/queryParams}} + ]) + {{#queryParams}} +{{#vendorExtensions.x-is-deep-object}} localVariableUrlComponents?.queryItems?.append(contentsOf: {{paramName}}?.encodeToQueryParams() ?? []) + + {{/vendorExtensions.x-is-deep-object}} + {{/queryParams}} + {{/hasQueryParams}}{{^hasQueryParams}} let localVariableUrlComponents = URLComponents(string: localVariableURLString){{/hasQueryParams}} {{#vendorExtensions.x-client-chunk-upload}}var{{/vendorExtensions.x-client-chunk-upload}}{{^vendorExtensions.x-client-chunk-upload}}let{{/vendorExtensions.x-client-chunk-upload}} localVariableNillableHeaders: [String: Any?] = [{{^headerParams}}{{^hasFormParams}} @@ -637,7 +641,14 @@ extension {{projectName}}API { {{#queryParams}} {{> _param}}, {{/queryParams}} - ]){{/hasQueryParams}}{{^hasQueryParams}} + ]) + {{#queryParams}} + + {{#vendorExtensions.x-is-deep-object}} + localVariableUrlComponents?.queryItems?.append(contentsOf: filterBy?.encodeToQueryParams() ?? []) + {{/vendorExtensions.x-is-deep-object}} + {{/queryParams}} + {{/hasQueryParams}}{{^hasQueryParams}} let localVariableUrlComponents = URLComponents(string: localVariableURLString){{/hasQueryParams}} var localVariableNillableHeaders: [String: Any?] = [{{^headerParams}}{{^hasFormParams}} diff --git a/templates/swift5/modelObject.mustache b/templates/swift5/modelObject.mustache index d3add802..2d1bd1e7 100644 --- a/templates/swift5/modelObject.mustache +++ b/templates/swift5/modelObject.mustache @@ -58,6 +58,28 @@ } }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} +{{#vendorExtensions.x-is-deep-object}} + public func encodeToQueryParams() -> [URLQueryItem] { + var queryItems = [URLQueryItem]() + + {{#allVars}} + {{^isArray}} + if let {{{name}}} = {{{name}}} { + queryItems.append(URLQueryItem(name: "{{{ vendorExtensions.x-model-name }}}[{{{name}}}]", value: {{{name}}}{{#isEnum}}.rawValue{{/isEnum}})) + } + {{/isArray}} + {{#isArray}} + if let {{{name}}} = {{{name}}}, !{{{name}}}.isEmpty { + for (index, val) in {{{name}}}.enumerated() { + queryItems.append(URLQueryItem(name: "{{{ vendorExtensions.x-model-name }}}[{{{name}}}][\(index)]", value: val{{#isEnum}}.rawValue{{/isEnum}})) + } + } + {{/isArray}} + {{/allVars}} + + return queryItems + } +{{/vendorExtensions.x-is-deep-object}} // Encodable protocol methods {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws {