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/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..0e00cca6 100644 --- a/oas_apivideo.yaml +++ b/oas_apivideo.yaml @@ -11899,9 +11899,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 @@ -12300,6 +12300,40 @@ paths: x-client-paginated: true x-doctave: code-samples: + - language: node + code: | + const metrics = await client.analytics.getMetricsOverTime({ + metric: 'play', + filterBy: { + browser: ['chrome', 'firefox'], + continent: ['EU', 'AF'], + }, + }); + - 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{ + DeviceType: &[]string{"computer", "phone"}, + Tag: PtrString("tag"), + }, + }) + - language: python + code: | + video_with_tags = self.api.get_metrics_breakdown(metric='play', breakdown='continent', filter_by=FilterBy2( + device_type=["computer", "phone"], + tag="test", + ), + ) /webhooks: get: tags: @@ -14629,7 +14663,6 @@ components: required: - context - data - - pagination analytics-metrics-breakdown-response: title: Analytics response for metrics breakdown by dimension type: object @@ -14733,7 +14766,7 @@ components: data: description: Returns an array of metrics and the timestamps . type: array - items: + items: type: object properties: emittedAt: @@ -15053,7 +15086,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 +15094,60 @@ 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: 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: