diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a237a2e1a..5353e2ded5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ruby JSON serialization #429 +### Changed + +- Fixed a bug where raw collections requests would not be supported #467 + ## [0.0.7] - 2021-08-04 ### Added diff --git a/abstractions/dotnet/src/IHttpCore.cs b/abstractions/dotnet/src/IHttpCore.cs index 04f816cc0c..1f6ab4b198 100644 --- a/abstractions/dotnet/src/IHttpCore.cs +++ b/abstractions/dotnet/src/IHttpCore.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Kiota.Abstractions.Serialization; @@ -22,6 +23,13 @@ public interface IHttpCore { /// The deserialized response model. Task SendAsync(RequestInfo requestInfo, IResponseHandler responseHandler = default) where ModelType : IParsable; /// + /// Excutes the HTTP request specified by the given RequestInfo and returns the deserialized response model collection. + /// + /// The RequestInfo object to use for the HTTP request. + /// The response handler to use for the HTTP request instead of the default handler. + /// The deserialized response model collection. + Task> SendCollectionAsync(RequestInfo requestInfo, IResponseHandler responseHandler = default) where ModelType : IParsable; + /// /// Excutes the HTTP request specified by the given RequestInfo and returns the deserialized primitive response model. /// /// The RequestInfo object to use for the HTTP request. diff --git a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj index c8689b793a..19efcc8c81 100644 --- a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj +++ b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj @@ -4,7 +4,7 @@ net5.0 true https://github.com/microsoft/kiota - 1.0.14 + 1.0.15 diff --git a/abstractions/dotnet/src/RequestInfo.cs b/abstractions/dotnet/src/RequestInfo.cs index e26c814b51..0b588e1e37 100644 --- a/abstractions/dotnet/src/RequestInfo.cs +++ b/abstractions/dotnet/src/RequestInfo.cs @@ -69,15 +69,19 @@ public void SetStreamContent(Stream content) { /// Sets the request body from a model with the specified content type. /// /// The core service to get the serialization writer from. - /// The model to serialize. + /// The models to serialize. /// The content type to set. /// The model type to serialize. - public void SetContentFromParsable(T item, IHttpCore coreService, string contentType) where T : IParsable { + public void SetContentFromParsable(IHttpCore coreService, string contentType, params T[] items) where T : IParsable { if(string.IsNullOrEmpty(contentType)) throw new ArgumentNullException(nameof(contentType)); if(coreService == null) throw new ArgumentNullException(nameof(coreService)); + if(items == null || !items.Any()) throw new InvalidOperationException($"{nameof(items)} cannot be null or empty"); using var writer = coreService.SerializationWriterFactory.GetSerializationWriter(contentType); - writer.WriteObjectValue(null, item); + if(items.Count() == 1) + writer.WriteObjectValue(null, items[0]); + else + writer.WriteCollectionOfObjectValues(null, items); Headers.Add(contentTypeHeader, contentType); Content = writer.GetSerializedContent(); } diff --git a/abstractions/java/lib/build.gradle b/abstractions/java/lib/build.gradle index efff7f9aa9..35a741340a 100644 --- a/abstractions/java/lib/build.gradle +++ b/abstractions/java/lib/build.gradle @@ -46,7 +46,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-abstractions' - version '1.0.14' + version '1.0.15' from(components.java) } } diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java index 9702f3b352..0401f52db6 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java @@ -1,6 +1,7 @@ package com.microsoft.kiota; import java.util.concurrent.CompletableFuture; +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -27,6 +28,15 @@ public interface HttpCore { * @return a {@link CompletableFuture} with the deserialized response model. */ CompletableFuture sendAsync(@Nonnull final RequestInfo requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler); + /** + * Excutes the HTTP request specified by the given RequestInfo and returns the deserialized response model collection. + * @param requestInfo the request info to execute. + * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param targetClass the class of the response model to deserialize the response into. + * @param the type of the response model to deserialize the response into. + * @return a {@link CompletableFuture} with the deserialized response model collection. + */ + CompletableFuture> sendCollectionAsync(@Nonnull final RequestInfo requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler); /** * Excutes the HTTP request specified by the given RequestInfo and returns the deserialized primitive response model. * @param requestInfo the request info to execute. diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestInfo.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestInfo.java index 5498b5f12a..836637672c 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestInfo.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestInfo.java @@ -3,6 +3,7 @@ import java.net.URI; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Objects; @@ -71,18 +72,23 @@ public void setStreamContent(@Nonnull final InputStream value) { } /** * Sets the request body from a model with the specified content type. - * @param value the model. + * @param values the models. * @param contentType the content type. * @param httpCore The core service to get the serialization writer from. * @param the model type. */ - public void setContentFromParsable(@Nonnull final T value, @Nonnull final HttpCore httpCore, @Nonnull final String contentType) { + public void setContentFromParsable(@Nonnull final HttpCore httpCore, @Nonnull final String contentType, @Nonnull final T... values) { Objects.requireNonNull(httpCore); - Objects.requireNonNull(value); + Objects.requireNonNull(values); Objects.requireNonNull(contentType); + if(values.length == 0) throw new RuntimeException("values cannot be empty"); + try(final SerializationWriter writer = httpCore.getSerializationWriterFactory().getSerializationWriter(contentType)) { headers.put(contentTypeHeader, contentType); - writer.writeObjectValue(null, value); + if(values.length == 1) + writer.writeObjectValue(null, values[0]); + else + writer.writeCollectionOfObjectValues(null, Arrays.asList(values)); this.content = writer.getSerializedContent(); } catch (IOException ex) { throw new RuntimeException("could not serialize payload", ex); diff --git a/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/request_info.rb b/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/request_info.rb index ff46ffcb03..35c6828207 100644 --- a/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/request_info.rb +++ b/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/request_info.rb @@ -28,11 +28,15 @@ def set_stream_content(value = $stdin) @headers[@@content_type_header] = @@binary_content_type end - def set_content_from_parsable(value, serializer_factory, content_type) + def set_content_from_parsable(serializer_factory, content_type, values) begin writer = serializer_factory.get_serialization_writer(content_type) headers[@@content_type_header] = content_type - writer.write_object_value(nil, value); + if values != nil && values.kind_of?(Array) + writer.write_collection_of_object_values(nil, values) + else + writer.write_object_value(nil, values); + end this.content = writer.get_serialized_content(); rescue => exception raise Exception.new "could not serialize payload" diff --git a/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/version.rb b/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/version.rb index c1fcf77781..c0d254ba88 100644 --- a/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/version.rb +++ b/abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module MicrosoftKiotaAbstractions - VERSION = "0.1.1" + VERSION = "0.1.2" end diff --git a/abstractions/typescript/package-lock.json b/abstractions/typescript/package-lock.json index 3937378476..a4425989b7 100644 --- a/abstractions/typescript/package-lock.json +++ b/abstractions/typescript/package-lock.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.14", + "version": "1.0.15", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/abstractions/typescript/package.json b/abstractions/typescript/package.json index ce5df1df10..4bac866b94 100644 --- a/abstractions/typescript/package.json +++ b/abstractions/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.14", + "version": "1.0.15", "description": "Core abstractions for kiota generated libraries in TypeScript and JavaScript", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/abstractions/typescript/src/httpCore.ts b/abstractions/typescript/src/httpCore.ts index cad8b8132f..80094ed1f8 100644 --- a/abstractions/typescript/src/httpCore.ts +++ b/abstractions/typescript/src/httpCore.ts @@ -18,6 +18,15 @@ export interface HttpCore { * @return a {@link Promise} with the deserialized response model. */ sendAsync(requestInfo: RequestInfo, type: new() => ModelType, responseHandler: ResponseHandler | undefined): Promise; + /** + * Excutes the HTTP request specified by the given RequestInfo and returns the deserialized response model collection. + * @param requestInfo the request info to execute. + * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param type the class of the response model to deserialize the response into. + * @typeParam ModelType the type of the response model to deserialize the response into. + * @return a {@link Promise} with the deserialized response model collection. + */ + sendCollectionAsync(requestInfo: RequestInfo, type: new() => ModelType, responseHandler: ResponseHandler | undefined): Promise; /** * Excutes the HTTP request specified by the given RequestInfo and returns the deserialized primitive response model. * @param requestInfo the request info to execute. diff --git a/abstractions/typescript/src/requestInfo.ts b/abstractions/typescript/src/requestInfo.ts index 5973f39961..96d46f8789 100644 --- a/abstractions/typescript/src/requestInfo.ts +++ b/abstractions/typescript/src/requestInfo.ts @@ -36,18 +36,22 @@ export class RequestInfo { private static contentTypeHeader = "Content-Type"; /** * Sets the request body from a model with the specified content type. - * @param value the model. + * @param values the models. * @param contentType the content type. * @param httpCore The core service to get the serialization writer from. * @typeParam T the model type. */ - public setContentFromParsable = (value?: T | undefined, httpCore?: HttpCore | undefined, contentType?: string | undefined): void => { + public setContentFromParsable = (httpCore?: HttpCore | undefined, contentType?: string | undefined, ...values: T[]): void => { if(!httpCore) throw new Error("httpCore cannot be undefined"); if(!contentType) throw new Error("contentType cannot be undefined"); + if(!values || values.length === 0) throw new Error("values cannot be undefined or empty"); const writer = httpCore.getSerializationWriterFactory().getSerializationWriter(contentType); this.headers.set(RequestInfo.contentTypeHeader, contentType); - writer.writeObjectValue(undefined, value); + if(values.length === 1) + writer.writeObjectValue(undefined, values[0]); + else + writer.writeCollectionOfObjectValues(undefined, values); this.content = writer.getSerializedContent(); } /** diff --git a/http/dotnet/httpclient/src/HttpCore.cs b/http/dotnet/httpclient/src/HttpCore.cs index 8bfe6ce6d3..d8b2300579 100644 --- a/http/dotnet/httpclient/src/HttpCore.cs +++ b/http/dotnet/httpclient/src/HttpCore.cs @@ -35,6 +35,17 @@ public HttpCore(IAuthenticationProvider authenticationProvider, IParseNodeFactor } /// Factory to use to get a serializer for payload serialization public ISerializationWriterFactory SerializationWriterFactory { get { return sWriterFactory; } } + public async Task> SendCollectionAsync(RequestInfo requestInfo, IResponseHandler responseHandler = default) where ModelType : IParsable { + var response = await GetHttpResponseMessage(requestInfo); + requestInfo.Content?.Dispose(); + if(responseHandler == null) { + var rootNode = await GetRootParseNode(response); + var result = rootNode.GetCollectionOfObjectValues(); + return result; + } + else + return await responseHandler.HandleResponseAsync>(response); + } public async Task SendAsync(RequestInfo requestInfo, IResponseHandler responseHandler = null) where ModelType : IParsable { var response = await GetHttpResponseMessage(requestInfo); diff --git a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj index f2b8fbbeac..24e945e100 100644 --- a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj +++ b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj @@ -4,11 +4,11 @@ net5.0 true https://github.com/microsoft/kiota - 1.0.4 + 1.0.5 - + diff --git a/http/java/okhttp/lib/build.gradle b/http/java/okhttp/lib/build.gradle index ee79fd1316..5456401adc 100644 --- a/http/java/okhttp/lib/build.gradle +++ b/http/java/okhttp/lib/build.gradle @@ -36,7 +36,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:30.1.1-jre' api 'com.squareup.okhttp3:okhttp:4.9.1' - api 'com.microsoft.kiota:kiota-abstractions:1.0.14' + api 'com.microsoft.kiota:kiota-abstractions:1.0.15' } publishing { @@ -53,7 +53,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-http-okhttp' - version '1.0.4' + version '1.0.5' from(components.java) } } diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java index 5289201a13..820c280820 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java @@ -77,6 +77,33 @@ public void enableBackingStore() { this.sWriterFactory = Objects.requireNonNull(ApiClientBuilder.enableBackingStoreForSerializationWriterFactory(sWriterFactory)); } @Nonnull + public CompletableFuture> sendCollectionAsync(@Nonnull final RequestInfo requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { + Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); + + return addBearerIfNotPresent(requestInfo).thenCompose(x -> { + final HttpCoreCallbackFutureWrapper wrapper = new HttpCoreCallbackFutureWrapper(); + this.client.newCall(getRequestFromRequestInfo(requestInfo)).enqueue(wrapper); + return wrapper.future; + }).thenCompose(response -> { + if(responseHandler == null) { + final ResponseBody body = response.body(); + try { + try (final InputStream rawInputStream = body.byteStream()) { + final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); + final Iterable result = rootNode.getCollectionOfObjectValues(targetClass); + return CompletableFuture.completedStage(result); + } + } catch(IOException ex) { + return CompletableFuture.failedFuture(new RuntimeException("failed to read the response body", ex)); + } finally { + response.close(); + } + } else { + return responseHandler.handleResponseAsync(response); + } + }); + } + @Nonnull public CompletableFuture sendAsync(@Nonnull final RequestInfo requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); diff --git a/http/typescript/fetch/package-lock.json b/http/typescript/fetch/package-lock.json index 1f17ff9ef4..09988994a4 100644 --- a/http/typescript/fetch/package-lock.json +++ b/http/typescript/fetch/package-lock.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-http-fetch", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/http/typescript/fetch/package.json b/http/typescript/fetch/package.json index 5645cf3ab8..6b8fc1724c 100644 --- a/http/typescript/fetch/package.json +++ b/http/typescript/fetch/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-http-fetch", - "version": "1.0.4", + "version": "1.0.5", "description": "Kiota HttpCore implementation with fetch", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -29,7 +29,7 @@ "registry": "https://npm.pkg.github.com" }, "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.14", + "@microsoft/kiota-abstractions": "^1.0.15", "cross-fetch": "^3.1.4", "web-streams-polyfill": "^3.1.0" }, diff --git a/http/typescript/fetch/src/httpCore.ts b/http/typescript/fetch/src/httpCore.ts index f51091ee75..44db699ec4 100644 --- a/http/typescript/fetch/src/httpCore.ts +++ b/http/typescript/fetch/src/httpCore.ts @@ -36,6 +36,27 @@ export class HttpCore implements IHttpCore { if(segments.length === 0) return undefined; else return segments[0]; } + public sendCollectionAsync = async (requestInfo: RequestInfo, type: new() => ModelType, responseHandler: ResponseHandler | undefined): Promise => { + if(!requestInfo) { + throw new Error('requestInfo cannot be null'); + } + await this.addBearerIfNotPresent(requestInfo); + + const request = this.getRequestFromRequestInfo(requestInfo); + const response = await this.httpClient.fetch(this.getRequestUrl(requestInfo), request); + if(responseHandler) { + return await responseHandler.handleResponseAsync(response); + } else { + const payload = await response.arrayBuffer(); + const responseContentType = this.getResponseContentType(response); + if(!responseContentType) + throw new Error("no response content type found for deserialization"); + + const rootNode = this.parseNodeFactory.getRootParseNode(responseContentType, payload); + const result = rootNode.getCollectionOfObjectValues(type); + return result as unknown as ModelType[]; + } + } public sendAsync = async (requestInfo: RequestInfo, type: new() => ModelType, responseHandler: ResponseHandler | undefined): Promise => { if(!requestInfo) { throw new Error('requestInfo cannot be null'); diff --git a/samples b/samples index ec4096414a..9a92a3518e 160000 --- a/samples +++ b/samples @@ -1 +1 @@ -Subproject commit ec4096414a1062064a614372b3df4b7c06ae72e1 +Subproject commit 9a92a3518e450240d647de9527a717f71dd5fe74 diff --git a/src/Kiota.Builder/CodeDOM/CodeTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeTypeBase.cs index 31807d071d..ab101d7277 100644 --- a/src/Kiota.Builder/CodeDOM/CodeTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeTypeBase.cs @@ -15,7 +15,8 @@ protected CodeTypeBase(CodeElement parent) : base(parent) { public bool ActionOf {get;set;} = false; public bool IsNullable {get;set;} = true; public CodeTypeCollectionKind CollectionKind {get;set;} = CodeTypeCollectionKind.None; - + public bool IsCollection { get { return CollectionKind != CodeTypeCollectionKind.None; } } + public bool IsArray { get { return CollectionKind == CodeTypeCollectionKind.Array; } } public ChildType BaseClone(CodeTypeBase source) where ChildType : CodeTypeBase { ActionOf = source.ActionOf; diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 2f69c2e9bc..77652803ef 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -366,7 +366,7 @@ protected static void AddPropertiesAndMethodTypesImports(CodeElement current, bo .Distinct(); var methodsParametersTypes = methods .SelectMany(x => x.Parameters) - .Where(x => x.IsOfKind(CodeParameterKind.Custom)) + .Where(x => x.IsOfKind(CodeParameterKind.Custom, CodeParameterKind.RequestBody)) .Select(x => x.Type) .Distinct(); var indexerTypes = currentClass diff --git a/src/Kiota.Builder/Writers/CSharp/CSharpConventionService.cs b/src/Kiota.Builder/Writers/CSharp/CSharpConventionService.cs index 8bcbc1ca3b..36921b1d75 100644 --- a/src/Kiota.Builder/Writers/CSharp/CSharpConventionService.cs +++ b/src/Kiota.Builder/Writers/CSharp/CSharpConventionService.cs @@ -66,6 +66,11 @@ public string TranslateType(string typeName) default: return typeName?.ToFirstCharacterUpperCase() ?? "object"; } } + public bool IsPrimitiveType(string typeName) { + return !string.IsNullOrEmpty(typeName) && + (NullableTypes.Contains(typeName) || + "string".Equals(typeName, StringComparison.OrdinalIgnoreCase)); + } public string GetParameterSignature(CodeParameter parameter) { var parameterType = GetTypeString(parameter.Type); diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 01e8166a03..e201e7b767 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -151,7 +151,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, IEnumerable x?.Name).Where(x => x != null).Aggregate((x,y) => $"{x}, {y}"); writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); - writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, returnType)}(requestInfo, responseHandler);"); + writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler);"); } private const string _requestInfoVarName = "requestInfo"; private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, LanguageWriter writer) { @@ -168,7 +168,7 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter req if(requestBodyParam.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) writer.WriteLine($"{_requestInfoVarName}.SetStreamContent({requestBodyParam.Name});"); else - writer.WriteLine($"{_requestInfoVarName}.SetContentFromParsable({requestBodyParam.Name}, {conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); + writer.WriteLine($"{_requestInfoVarName}.SetContentFromParsable({conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\", {requestBodyParam.Name});"); } if(queryStringParam != null) { writer.WriteLine($"if ({queryStringParam.Name} != null) {{"); @@ -199,9 +199,10 @@ private void WriteSerializerBody(bool shouldHide, CodeClass parentClass, Languag if(additionalDataProperty != null) writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});"); } - private static string GetSendRequestMethodName(bool isVoid, bool isStream, string returnType) { + private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { if(isVoid) return "SendNoContentAsync"; - else if(isStream) return $"SendPrimitiveAsync<{returnType}>"; + else if(isStream || conventions.IsPrimitiveType(returnType)) return $"SendPrimitiveAsync<{returnType}>"; + else if(isCollection) return $"SendCollectionAsync<{returnType.StripArraySuffix()}>"; else return $"SendAsync<{returnType}>"; } private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer) { @@ -220,14 +221,16 @@ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string var staticModifier = code.IsStatic ? "static " : string.Empty; var hideModifier = inherits && code.IsSerializationMethod ? "new " : string.Empty; var genericTypePrefix = isVoid ? string.Empty : "<"; - var genricTypeSuffix = code.IsAsync && !isVoid ? ">": string.Empty; + var genericTypeSuffix = code.IsAsync && !isVoid ? ">": string.Empty; var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor); var asyncPrefix = code.IsAsync ? "async Task" + genericTypePrefix : string.Empty; var voidCorrectedTaskReturnType = code.IsAsync && isVoid ? string.Empty : returnType; + if(code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor)) + voidCorrectedTaskReturnType = $"IEnumerable<{voidCorrectedTaskReturnType.StripArraySuffix()}>"; // TODO: Task type should be moved into the refiner var completeReturnType = isConstructor ? string.Empty : - $"{asyncPrefix}{voidCorrectedTaskReturnType}{genricTypeSuffix} "; + $"{asyncPrefix}{voidCorrectedTaskReturnType}{genericTypeSuffix} "; var baseSuffix = string.Empty; if(isConstructor && inherits) baseSuffix = " : base()"; diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index ab607349e7..8e7e32127e 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -170,7 +170,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ writer.WriteLine("try {"); writer.IncreaseIndent(); WriteGeneratorMethodCall(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer, $"final RequestInfo {requestInfoVarName} = "); - var sendMethodName = conventions.PrimitiveTypes.Contains(returnType) ? "sendPrimitiveAsync" : "sendAsync"; + var sendMethodName = GetSendRequestMethodName(codeElement.ReturnType.IsCollection, returnType); if(codeElement.Parameters.Any(x => x.IsOfKind(CodeParameterKind.ResponseHandler))) writer.WriteLine($"return this.httpCore.{sendMethodName}(requestInfo, {returnType}.class, responseHandler);"); else @@ -182,6 +182,11 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ writer.DecreaseIndent(); writer.WriteLine("}"); } + private string GetSendRequestMethodName(bool isCollection, string returnType) { + if(conventions.PrimitiveTypes.Contains(returnType)) return $"sendPrimitiveAsync"; + else if(isCollection) return $"sendCollectionAsync"; + else return $"sendAsync"; + } private const string requestInfoVarName = "requestInfo"; private static void WriteGeneratorMethodCall(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, LanguageWriter writer, string prefix) { var generatorMethodName = (codeElement.Parent as CodeClass) @@ -216,7 +221,7 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter req if(requestBodyParam.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) writer.WriteLine($"{requestInfoVarName}.setStreamContent({requestBodyParam.Name});"); else - writer.WriteLine($"{requestInfoVarName}.setContentFromParsable({requestBodyParam.Name}, {conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); + writer.WriteLine($"{requestInfoVarName}.setContentFromParsable({conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\", {requestBodyParam.Name});"); if(queryStringParam != null) { var httpMethodPrefix = codeElement.HttpMethod.ToString().ToFirstCharacterUpperCase(); writer.WriteLine($"if ({queryStringParam.Name} != null) {{"); @@ -267,7 +272,10 @@ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string }); var parameters = string.Join(", ", code.Parameters.Select(p=> conventions.GetParameterSignature(p)).ToList()); var throwableDeclarations = code.IsOfKind(CodeMethodKind.RequestGenerator) ? "throws URISyntaxException ": string.Empty; - var finalReturnType = isConstructor ? string.Empty : $" {returnTypeAsyncPrefix}{returnType}{returnTypeAsyncSuffix}"; + var collectionCorrectedReturnType = code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor) ? + $"Iterable<{returnType.StripArraySuffix()}>" : + returnType; + var finalReturnType = isConstructor ? string.Empty : $" {returnTypeAsyncPrefix}{collectionCorrectedReturnType}{returnTypeAsyncSuffix}"; writer.WriteLine($"{accessModifier}{genericTypeParameterDeclaration}{finalReturnType} {methodName}({parameters}) {throwableDeclarations}{{"); } private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer) { diff --git a/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs index eb36854362..1cc80ec919 100644 --- a/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs @@ -149,7 +149,7 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter req if(requestBodyParam.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) writer.WriteLine($"request_info.set_stream_content({requestBodyParam.Name})"); else - writer.WriteLine($"request_info.set_content_from_parsable({requestBodyParam.Name}, self.{RubyConventionService.SerializerFactoryPropertyName}, \"{codeElement.ContentType}\")"); + writer.WriteLine($"request_info.set_content_from_parsable(self.{RubyConventionService.SerializerFactoryPropertyName}, \"{codeElement.ContentType}\", {requestBodyParam.Name})"); } writer.WriteLine("return request_info;"); } diff --git a/src/Kiota.Builder/Writers/StringExtensions.cs b/src/Kiota.Builder/Writers/StringExtensions.cs new file mode 100644 index 0000000000..e9e1cc7d2f --- /dev/null +++ b/src/Kiota.Builder/Writers/StringExtensions.cs @@ -0,0 +1,5 @@ +namespace Kiota.Builder.Writers { + public static class StringExtensions { + public static string StripArraySuffix(this string original) => string.IsNullOrEmpty(original) ? original : original.TrimEnd(']').TrimEnd('['); + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 775e8886be..8b7d427be7 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -172,7 +172,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, IEnumerable"; } } - private static string GetTypeFactory(bool isVoid, bool isStream, string returnType) { + private string GetTypeFactory(bool isVoid, bool isStream, string returnType) { if(isVoid) return string.Empty; - else if(isStream) return $" \"{returnType}\","; - else return $" {returnType},"; + else if(isStream || conventions.IsPrimitiveType(returnType)) return $" \"{returnType}\","; + else return $" {returnType.StripArraySuffix()},"; } - private static string GetSendRequestMethodName(bool isVoid, bool isStream, string returnType) { + private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { if(isVoid) return "sendNoResponseContentAsync"; - else if(isStream) return $"sendPrimitiveAsync<{returnType}>"; + else if(isStream || conventions.IsPrimitiveType(returnType)) return $"sendPrimitiveAsync<{returnType}>"; + else if(isCollection) return $"sendCollectionAsync<{returnType.StripArraySuffix()}>"; else return $"sendAsync<{returnType}>"; } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 94b8a76ad2..aedb88a30e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -90,7 +90,12 @@ public string TranslateType(string typeName) _ => typeName.ToFirstCharacterUpperCase() ?? "object", }; } - + public bool IsPrimitiveType(string typeName) { + return typeName switch { + ("number" or "string" or "byte[]" or "boolean" or "void") => true, + _ => false, + }; + } internal static string RemoveInvalidDescriptionCharacters(string originalDescription) => originalDescription?.Replace("\\", "/"); public void WriteShortDescription(string description, LanguageWriter writer) { diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs index c93aea9ea9..a7a2b1d79d 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs @@ -137,6 +137,17 @@ public void WritesRequestExecutorBody() { AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestExecutorBodyForCollections() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("SendCollectionAsync", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesRequestGeneratorBody() { method.MethodKind = CodeMethodKind.RequestGenerator; method.HttpMethod = HttpMethod.Get; diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index 8e2ebf6d15..cf2e59cfc7 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -148,6 +148,17 @@ public void WritesRequestExecutorBody() { AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestExecutorBodyForCollections() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("sendCollectionAsync", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesRequestGeneratorBody() { method.MethodKind = CodeMethodKind.RequestGenerator; method.HttpMethod = HttpMethod.Get; diff --git a/tests/Kiota.Builder.Tests/Writers/StringExtensionsTests.cs b/tests/Kiota.Builder.Tests/Writers/StringExtensionsTests.cs new file mode 100644 index 0000000000..e0c8861a17 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/StringExtensionsTests.cs @@ -0,0 +1,17 @@ +using Kiota.Builder.Writers; +using Xunit; + +namespace Kiota.Builder.Tests.Writers { + public class StringExtensionsTests { + [Fact] + public void Defensive() { + Assert.Null(StringExtensions.StripArraySuffix(null)); + Assert.Empty(StringExtensions.StripArraySuffix(string.Empty)); + } + [Fact] + public void StripsSuffix() { + Assert.Equal("foo", StringExtensions.StripArraySuffix("foo[]")); + Assert.Equal("[]foo", StringExtensions.StripArraySuffix("[]foo")); + } + } +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index f127cf3be8..9329190d7c 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -136,6 +136,17 @@ public void WritesRequestExecutorBody() { AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestExecutorBodyForCollections() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("sendCollectionAsync", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesRequestGeneratorBody() { method.MethodKind = CodeMethodKind.RequestGenerator; method.HttpMethod = HttpMethod.Get;