From eb9b64fda98f65d29c52909a68853ec2197f0f3b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 14 Jul 2021 13:50:04 -0400 Subject: [PATCH 01/19] - adds http client builder to allow for default middlewares and customization --- .../httpclient/src/HttpClientBuilder.cs | 48 +++++++++++++++++++ http/dotnet/httpclient/src/HttpCore.cs | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 http/dotnet/httpclient/src/HttpClientBuilder.cs diff --git a/http/dotnet/httpclient/src/HttpClientBuilder.cs b/http/dotnet/httpclient/src/HttpClientBuilder.cs new file mode 100644 index 0000000000..4645395a94 --- /dev/null +++ b/http/dotnet/httpclient/src/HttpClientBuilder.cs @@ -0,0 +1,48 @@ +using System.Linq; +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.Kiota.Abstractions; + +namespace Microsoft.Kiota.Http.HttpClient { + /// + /// This class is used to build the HttpClient instance used by the core service. + /// + public static class HttpClientBuilder { + /// + /// Initializes the with the default configuration and middlewares including a authentention middleware using the if provided. + /// + /// The to use for authentention. + /// The with the default middlewares. + public static System.Net.Http.HttpClient Create(IAuthenticationProvider authenticationProvider = default) { + var defaultHandlers = CreateDefaultHandlers(authenticationProvider); + var handler = CreateMainHandlerFromHandlersCollection(defaultHandlers.ToArray()); + return handler != null ? new System.Net.Http.HttpClient(handler) : new System.Net.Http.HttpClient(); //TODO configure the default client options + } + /// + /// Creates a default set of middleware to be used by the . + /// + /// The to authenticate requests. + /// A list of the default handlers used by the client. + public static IList CreateDefaultHandlers(IAuthenticationProvider authenticationProvider = default) + { + return new List(); //TODO add the default middlewares when they are ready + } + /// + /// Creates a to use for the from the provided instances. Order matters. + /// + /// The instances to create the from. + /// The created . + public static DelegatingHandler CreateMainHandlerFromHandlersCollection(params DelegatingHandler[] handlers) { + if(handlers == null || !handlers.Any()) return default; + var handlersAsList = handlers.ToList(); + handlersAsList.ForEach(h => { + var previousItemIndex = handlersAsList.IndexOf(h) - 1; + if(previousItemIndex >= 0) { + var previousItem = handlersAsList.ElementAt(previousItemIndex); + previousItem.InnerHandler = h; + } + }); + return handlers.First(); + } + } +} diff --git a/http/dotnet/httpclient/src/HttpCore.cs b/http/dotnet/httpclient/src/HttpCore.cs index db276f4b48..5c03c57238 100644 --- a/http/dotnet/httpclient/src/HttpCore.cs +++ b/http/dotnet/httpclient/src/HttpCore.cs @@ -28,7 +28,7 @@ public HttpCore(IAuthenticationProvider authenticationProvider, IParseNodeFactor { authProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider)); createdClient = httpClient == null; - client = httpClient ?? new System.Net.Http.HttpClient(); + client = httpClient ?? HttpClientBuilder.Create(authProvider); pNodeFactory = parseNodeFactory ?? ParseNodeFactoryRegistry.DefaultInstance; sWriterFactory = serializationWriterFactory ?? SerializationWriterFactoryRegistry.DefaultInstance; } From 80d73c0b2cd719c5ee4f81fb2a0676b426d63ca3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 14 Jul 2021 13:55:41 -0400 Subject: [PATCH 02/19] - renames chaining method --- http/dotnet/httpclient/src/HttpClientBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/dotnet/httpclient/src/HttpClientBuilder.cs b/http/dotnet/httpclient/src/HttpClientBuilder.cs index 4645395a94..4a7ff51c4b 100644 --- a/http/dotnet/httpclient/src/HttpClientBuilder.cs +++ b/http/dotnet/httpclient/src/HttpClientBuilder.cs @@ -15,7 +15,7 @@ public static class HttpClientBuilder { /// The with the default middlewares. public static System.Net.Http.HttpClient Create(IAuthenticationProvider authenticationProvider = default) { var defaultHandlers = CreateDefaultHandlers(authenticationProvider); - var handler = CreateMainHandlerFromHandlersCollection(defaultHandlers.ToArray()); + var handler = ChainHandlersCollectionAndGetFirstLink(defaultHandlers.ToArray()); return handler != null ? new System.Net.Http.HttpClient(handler) : new System.Net.Http.HttpClient(); //TODO configure the default client options } /// @@ -32,7 +32,7 @@ public static IList CreateDefaultHandlers(IAuthenticationProv /// /// The instances to create the from. /// The created . - public static DelegatingHandler CreateMainHandlerFromHandlersCollection(params DelegatingHandler[] handlers) { + public static DelegatingHandler ChainHandlersCollectionAndGetFirstLink(params DelegatingHandler[] handlers) { if(handlers == null || !handlers.Any()) return default; var handlersAsList = handlers.ToList(); handlersAsList.ForEach(h => { From de3151a9c417625b92e7866188e1d92fddedb5cf Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 14 Jul 2021 16:01:11 -0400 Subject: [PATCH 03/19] - adds support for middleware options in dotnet abstractions and core --- abstractions/dotnet/src/IMiddlewareOption.cs | 7 ++++++ abstractions/dotnet/src/RequestInfo.cs | 25 ++++++++++++++++++++ http/dotnet/httpclient/src/HttpCore.cs | 3 +++ 3 files changed, 35 insertions(+) create mode 100644 abstractions/dotnet/src/IMiddlewareOption.cs diff --git a/abstractions/dotnet/src/IMiddlewareOption.cs b/abstractions/dotnet/src/IMiddlewareOption.cs new file mode 100644 index 0000000000..2d416c9f47 --- /dev/null +++ b/abstractions/dotnet/src/IMiddlewareOption.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Kiota.Abstractions { + /// + /// Represents a middleware option. + /// + public interface IMiddlewareOption { + } +} diff --git a/abstractions/dotnet/src/RequestInfo.cs b/abstractions/dotnet/src/RequestInfo.cs index 42c20d2665..0526212f26 100644 --- a/abstractions/dotnet/src/RequestInfo.cs +++ b/abstractions/dotnet/src/RequestInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using System.IO; using Microsoft.Kiota.Abstractions.Serialization; @@ -30,6 +31,30 @@ public class RequestInfo /// The Request Body. /// public Stream Content { get; set; } + private Dictionary _middlewareOptions = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// Gets the middleware options for this request. Options are unique by type. If an option of the same type is added twice, the last one wins. + /// + public IEnumerable MiddlewareOptions { get { return _middlewareOptions.Values; } } + /// + /// Adds a middleware option to the request. + /// + /// The middleware option to add. + public void AddMiddlewareOptions(params IMiddlewareOption[] options) { + if(!options?.Any() ?? false) throw new ArgumentNullException(nameof(options)); + foreach(var option in options.Where(x => x != null)) + if(!_middlewareOptions.TryAdd(option.GetType().FullName, option)) + _middlewareOptions[option.GetType().FullName] = option; + } + /// + /// Removes given middleware options from the current request. + /// + /// Middleware options to remove. + public void RemoveMiddlewareOptions(params IMiddlewareOption[] options) { + if(!options?.Any() ?? false) throw new ArgumentNullException(nameof(options)); + foreach(var optionName in options.Where(x => x != null).Select(x => x.GetType().FullName)) + _middlewareOptions.Remove(optionName); + } private const string binaryContentType = "application/octet-stream"; private const string contentTypeHeader = "Content-Type"; /// diff --git a/http/dotnet/httpclient/src/HttpCore.cs b/http/dotnet/httpclient/src/HttpCore.cs index 5c03c57238..8bfe6ce6d3 100644 --- a/http/dotnet/httpclient/src/HttpCore.cs +++ b/http/dotnet/httpclient/src/HttpCore.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -128,6 +129,8 @@ private HttpRequestMessage GetRequestMessageFromRequestInfo(RequestInfo requestI string.Empty)), }; + if(requestInfo.MiddlewareOptions.Any()) + requestInfo.MiddlewareOptions.ToList().ForEach(x => message.Options.Set(new HttpRequestOptionsKey(x.GetType().FullName), x)); if(requestInfo.Headers?.Any() ?? false) requestInfo.Headers.Where(x => !contentTypeHeaderName.Equals(x.Key, StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => message.Headers.Add(x.Key, x.Value)); if(requestInfo.Content != null) { From 821b2d7d23da910cff9375ae7f65cd0595691c9a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 08:43:21 -0400 Subject: [PATCH 04/19] - adds changelog entry for middlewares --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1392be5f2d..6d87a4f3dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Better client configuration #268 - Request builders constructors for data validation #322 +- Adds middleware support for http clients #330 ## [0.0.5] - 2021-06-10 From 15223c9aed815e7d2218d1a6788e1d56ca8b7441 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 09:35:27 -0400 Subject: [PATCH 05/19] - replaces defensive programming by noop on add middleware options to avoid generating checks --- abstractions/dotnet/src/RequestInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abstractions/dotnet/src/RequestInfo.cs b/abstractions/dotnet/src/RequestInfo.cs index 0526212f26..e26c814b51 100644 --- a/abstractions/dotnet/src/RequestInfo.cs +++ b/abstractions/dotnet/src/RequestInfo.cs @@ -41,7 +41,7 @@ public class RequestInfo /// /// The middleware option to add. public void AddMiddlewareOptions(params IMiddlewareOption[] options) { - if(!options?.Any() ?? false) throw new ArgumentNullException(nameof(options)); + if(!options?.Any() ?? false) return; // it's a no-op if there are no options and this avoid having to check in the code gen. foreach(var option in options.Where(x => x != null)) if(!_middlewareOptions.TryAdd(option.GetType().FullName, option)) _middlewareOptions[option.GetType().FullName] = option; From d396e42af98ecb58f2b0cfb95c07bebd14323b60 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 09:36:12 -0400 Subject: [PATCH 06/19] - adds generation for options parameter in dotnet --- src/Kiota.Builder/CodeDOM/CodeParameter.cs | 3 +- src/Kiota.Builder/KiotaBuilder.cs | 8 ++++ .../Writers/CSharp/CodeMethodWriter.cs | 37 +++++++++---------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeParameter.cs b/src/Kiota.Builder/CodeDOM/CodeParameter.cs index 703ee7a679..cd4d88ac23 100644 --- a/src/Kiota.Builder/CodeDOM/CodeParameter.cs +++ b/src/Kiota.Builder/CodeDOM/CodeParameter.cs @@ -12,7 +12,8 @@ public enum CodeParameterKind RequestBody, SetterValue, HttpCore, - CurrentPath + CurrentPath, + Options } public class CodeParameter : CodeTerminal, ICloneable, IDocumentedElement diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index b474ebd966..01f05c3759 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -575,6 +575,14 @@ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, O }; headersParam.Type = new CodeType(headersParam) { Name = "IDictionary", ActionOf = true, IsExternal = true }; method.AddParameter(headersParam); + var optionsParam = new CodeParameter(method) { + Name = "o", + Optional = true, + ParameterKind = CodeParameterKind.Options, + Description = "Request options for HTTP middlewares" + }; + optionsParam.Type = new CodeType(optionsParam) { Name = "IEnumerable", ActionOf = false, IsExternal = true }; + method.AddParameter(optionsParam); } private IEnumerable GetAllNamespaceNamesForModelByReferenceId(string referenceId) { if(string.IsNullOrEmpty(referenceId)) throw new ArgumentNullException(nameof(referenceId)); diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 35adae3f80..3ef607c6bf 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -27,6 +27,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); + var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) { if(nameof(String).Equals(parameter.Type.Name, StringComparison.OrdinalIgnoreCase)) writer.WriteLine($"if(string.IsNullOrEmpty({parameter.Name})) throw new ArgumentNullException(nameof({parameter.Name}));"); @@ -38,10 +39,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteSerializerBody(inherits, parentClass, writer); break; case CodeMethodKind.RequestGenerator: - WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, writer); + WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer); break; case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, isVoid, returnType, writer); + WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, isVoid, returnType, writer); break; case CodeMethodKind.Deserializer: WriteDeserializerBody(codeElement, parentClass, writer); @@ -139,7 +140,7 @@ private string GetDeserializationMethodName(CodeTypeBase propType) { return $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>"; } } - private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, bool isVoid, string returnType, LanguageWriter writer) { + private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, bool isVoid, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); @@ -148,19 +149,16 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ .OfType() .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) ?.Name; - writer.WriteLine($"var requestInfo = {generatorMethodName}("); - writer.IncreaseIndent(); - writer.WriteLine(new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name }.Where(x => x != null).Aggregate((x,y) => $"{x}, {y}")); - writer.DecreaseIndent(); - writer.WriteLines(");", - $"{(isVoid ? string.Empty : "return ")}await HttpCore.{GetSendRequestMethodName(isVoid, isStream, returnType)}(requestInfo, responseHandler);"); - + var parametersList = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name, optionsParam?.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);"); } - private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, LanguageWriter writer) { + private const string _requestInfoVarName = "requestInfo"; + private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var operationName = codeElement.HttpMethod.ToString(); - writer.WriteLine("var requestInfo = new RequestInfo {"); + writer.WriteLine($"var {_requestInfoVarName} = new RequestInfo {{"); writer.IncreaseIndent(); writer.WriteLines($"HttpMethod = HttpMethod.{operationName?.ToUpperInvariant()},", $"URI = new Uri({conventions.CurrentPathPropertyName} + {conventions.PathSegmentPropertyName}),"); @@ -168,23 +166,24 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter req writer.WriteLine("};"); if(requestBodyParam != null) { if(requestBodyParam.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"requestInfo.SetStreamContent({requestBodyParam.Name});"); + writer.WriteLine($"{_requestInfoVarName}.SetStreamContent({requestBodyParam.Name});"); else - writer.WriteLine($"requestInfo.SetContentFromParsable({requestBodyParam.Name}, {conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); + writer.WriteLine($"{_requestInfoVarName}.SetContentFromParsable({requestBodyParam.Name}, {conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); } if(queryStringParam != null) { writer.WriteLine($"if ({queryStringParam.Name} != null) {{"); writer.IncreaseIndent(); writer.WriteLines($"var qParams = new {operationName?.ToFirstCharacterUpperCase()}QueryParameters();", $"{queryStringParam.Name}.Invoke(qParams);", - "qParams.AddQueryParameters(requestInfo.QueryParameters);"); + $"qParams.AddQueryParameters({_requestInfoVarName}.QueryParameters);"); writer.DecreaseIndent(); writer.WriteLine("}"); } - if(headersParam != null) { - writer.WriteLines($"{headersParam.Name}?.Invoke(requestInfo.Headers);", - "return requestInfo;"); - } + if(headersParam != null) + writer.WriteLine($"{headersParam.Name}?.Invoke({_requestInfoVarName}.Headers);"); + if(optionsParam != null) + writer.WriteLine($"{_requestInfoVarName}.AddMiddlewareOptions({optionsParam.Name}?.ToArray());"); + writer.WriteLine($"return {_requestInfoVarName};"); } private void WriteSerializerBody(bool shouldHide, CodeClass parentClass, LanguageWriter writer) { var additionalDataProperty = parentClass.GetChildElements(true).OfType().FirstOrDefault(x => x.IsOfKind(CodePropertyKind.AdditionalData)); From 0edab1dc408c3cb927d2fe5467b2c74bce62fe11 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 10:13:06 -0400 Subject: [PATCH 07/19] - adds middleware option in java abstractions --- .../com/microsoft/kiota/MiddlewareOption.java | 6 +++++ .../java/com/microsoft/kiota/RequestInfo.java | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 abstractions/java/lib/src/main/java/com/microsoft/kiota/MiddlewareOption.java diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/MiddlewareOption.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/MiddlewareOption.java new file mode 100644 index 0000000000..bffcaaaf49 --- /dev/null +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/MiddlewareOption.java @@ -0,0 +1,6 @@ +package com.microsoft.kiota; + +/** Represents a middleware option. */ +public interface MiddlewareOption { + +} \ No newline at end of file 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 335ceefa38..69a038d442 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.Collection; import java.util.HashMap; import java.util.Objects; import java.util.function.Function; @@ -31,6 +32,32 @@ public class RequestInfo { /** The Request Body. */ @Nullable public InputStream content; + private HashMap _middlewareOptions = new HashMap<>(); + /** + * Gets the middleware options for this request. Options are unique by type. If an option of the same type is added twice, the last one wins. + * @return the middleware options for this request. + */ + public Collection getMiddlewareOptions() { return _middlewareOptions.values(); } + /** + * Adds a middleware option to this request. + * @param option the middleware option to add. + */ + public void AddMiddlewareOptions(@Nullable final MiddlewareOption... options) { + if(options == null || options.length == 0) return; + for(final MiddlewareOption option : options) { + _middlewareOptions.put(option.getClass().getCanonicalName(), option); + } + } + /** + * Removes a middleware option from this request. + * @param option the middleware option to remove. + */ + public void RemoveMiddlewareOptions(@Nullable final MiddlewareOption... options) { + if(options == null || options.length == 0) return; + for(final MiddlewareOption option : options) { + _middlewareOptions.remove(option.getClass().getCanonicalName()); + } + } private static String binaryContentType = "application/octet-stream"; private static String contentTypeHeader = "Content-Type"; /** From dcfdcac0886aa7a25ed7cc043270ce073c1dc27d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 10:44:51 -0400 Subject: [PATCH 08/19] - adds default client configuration for java http --- .../com/microsoft/kiota/http/HttpCore.java | 9 ++++---- .../kiota/http/OkHttpClientBuilder.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpClientBuilder.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 a8a250cfa7..5289201a13 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 @@ -1,6 +1,3 @@ -/* - * This Java source file was generated by the Gradle 'init' task. - */ package com.microsoft.kiota.http; import java.io.IOException; @@ -18,6 +15,7 @@ import com.microsoft.kiota.ApiClientBuilder; import com.microsoft.kiota.RequestInfo; +import com.microsoft.kiota.MiddlewareOption; import com.microsoft.kiota.ResponseHandler; import com.microsoft.kiota.AuthenticationProvider; import com.microsoft.kiota.serialization.ParseNodeFactoryRegistry; @@ -55,7 +53,7 @@ public HttpCore(@Nonnull final AuthenticationProvider authenticationProvider, @N public HttpCore(@Nonnull final AuthenticationProvider authenticationProvider, @Nullable final ParseNodeFactory parseNodeFactory, @Nullable final SerializationWriterFactory serializationWriterFactory, @Nullable final OkHttpClient client) { this.authProvider = Objects.requireNonNull(authenticationProvider, "parameter authenticationProvider cannot be null"); if(client == null) { - this.client = new OkHttpClient.Builder().build(); + this.client = OkHttpClientBuilder.Create(this.authProvider).build(); } else { this.client = client; } @@ -210,6 +208,9 @@ public void writeTo(BufferedSink sink) throws IOException { for (final Map.Entry header : requestInfo.headers.entrySet()) { requestBuilder.addHeader(header.getKey(), header.getValue()); } + for(final MiddlewareOption option : requestInfo.getMiddlewareOptions()) { + requestBuilder.tag(option); + } return requestBuilder.build(); } } diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpClientBuilder.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpClientBuilder.java new file mode 100644 index 0000000000..4eea3c6217 --- /dev/null +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpClientBuilder.java @@ -0,0 +1,21 @@ +package com.microsoft.kiota.http; + +import com.microsoft.kiota.AuthenticationProvider; + +import okhttp3.OkHttpClient; + +import javax.annotation.Nullable; + +/** This class is used to build the HttpClient instance used by the core service. */ +public class OkHttpClientBuilder { + private OkHttpClientBuilder() { } + /** + * Creates an OkHttpClient Builder with the default configuration and middlewares including a authentention middleware using the {@link AuthenticationProvider} if provided. + * @param authenticationProvider the authentication provider used to authenticate the requests. + * @return an OkHttpClient Builder instance. + */ + public static OkHttpClient.Builder Create(@Nullable final AuthenticationProvider authenticationProvider) { + return new OkHttpClient.Builder(); //TODO configure the default client options. + //TODO add the default middlewares when they are ready + } +} \ No newline at end of file From 406f6e80b84064f40f5694b7e80c7587709ff03d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 11:40:38 -0400 Subject: [PATCH 09/19] - adds options parameter for java generation --- .../java/com/microsoft/kiota/RequestInfo.java | 4 +-- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 5 ++++ src/Kiota.Builder/Refiners/JavaRefiner.cs | 26 +++++++++++++------ .../Writers/Java/CodeMethodWriter.cs | 18 +++++++++---- 4 files changed, 38 insertions(+), 15 deletions(-) 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 69a038d442..5498b5f12a 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 @@ -42,7 +42,7 @@ public class RequestInfo { * Adds a middleware option to this request. * @param option the middleware option to add. */ - public void AddMiddlewareOptions(@Nullable final MiddlewareOption... options) { + public void addMiddlewareOptions(@Nullable final MiddlewareOption... options) { if(options == null || options.length == 0) return; for(final MiddlewareOption option : options) { _middlewareOptions.put(option.getClass().getCanonicalName(), option); @@ -52,7 +52,7 @@ public void AddMiddlewareOptions(@Nullable final MiddlewareOption... options) { * Removes a middleware option from this request. * @param option the middleware option to remove. */ - public void RemoveMiddlewareOptions(@Nullable final MiddlewareOption... options) { + public void removeMiddlewareOptions(@Nullable final MiddlewareOption... options) { if(options == null || options.length == 0) return; for(final MiddlewareOption option : options) { _middlewareOptions.remove(option.getClass().getCanonicalName()); diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index fb2c4fb180..8df7eb760b 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -54,6 +54,10 @@ public bool IsSerializationMethod { } public List SerializerModules { get; set; } public List DeserializerModules { get; set; } + /// + /// Indicates whether this method is an overload for another method. + /// + public bool IsOverload { get; set; } public object Clone() { @@ -72,6 +76,7 @@ public object Clone() PathSegment = PathSegment?.Clone() as string, SerializerModules = SerializerModules == null ? null : new (SerializerModules), DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules), + IsOverload = IsOverload, }; } diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index 867ec220aa..74d8bcb9cd 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -78,11 +78,13 @@ private static void AddListImport(CodeElement currentElement) { new ("RequestInfo", "com.microsoft.kiota"), new ("ResponseHandler", "com.microsoft.kiota"), new ("QueryParametersBase", "com.microsoft.kiota"), + new ("MiddlewareOption", "com.microsoft.kiota"), new ("Map", "java.util"), new ("URI", "java.net"), new ("URISyntaxException", "java.net"), new ("InputStream", "java.io"), new ("Function", "java.util.function"), + new ("Collection", "java.util"), }; private static readonly Tuple[] defaultNamespaces = new Tuple[] { new ("SerializationWriter", "com.microsoft.kiota.serialization"), @@ -121,10 +123,13 @@ private static void CorrectCoreType(CodeElement currentElement) { } } if (currentElement is CodeMethod currentMethod) { - if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("IResponseHandler")).ToList().ForEach(x => x.Type.Name = "ResponseHandler"); + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.ResponseHandler) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "Collection"); + } else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("ISerializationWriter")).ToList().ForEach(x => x.Type.Name = "SerializationWriter"); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) { currentMethod.ReturnType.Name = $"Map>"; currentMethod.Name = "getFieldDeserializers"; @@ -157,17 +162,21 @@ private static void AndInsertOverrideMethodForRequestExecutorsAndBuilders(CodeEl if(codeMethods.Any()) { var originalExecutorMethods = codeMethods.Where(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); var executorMethodsToAdd = originalExecutorMethods - .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter)) + .Select(x => GetMethodClone(x, CodeParameterKind.ResponseHandler)) + .Union(originalExecutorMethods + .Select(x => GetMethodClone(x, CodeParameterKind.Options, CodeParameterKind.ResponseHandler))) .Union(originalExecutorMethods - .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter, CodeParameterKind.Headers))) + .Select(x => GetMethodClone(x, CodeParameterKind.Headers, CodeParameterKind.Options, CodeParameterKind.ResponseHandler))) .Union(originalExecutorMethods - .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter, CodeParameterKind.Headers, CodeParameterKind.ResponseHandler))) + .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter, CodeParameterKind.Headers, CodeParameterKind.Options, CodeParameterKind.ResponseHandler))) .Where(x => x != null); var originalGeneratorMethods = codeMethods.Where(x => x.IsOfKind(CodeMethodKind.RequestGenerator)); var generatorMethodsToAdd = originalGeneratorMethods - .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter)) + .Select(x => GetMethodClone(x, CodeParameterKind.Options)) + .Union(originalGeneratorMethods + .Select(x => GetMethodClone(x, CodeParameterKind.Headers, CodeParameterKind.Options))) .Union(originalGeneratorMethods - .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter, CodeParameterKind.Headers))) + .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter, CodeParameterKind.Headers, CodeParameterKind.Options))) .Where(x => x != null); if(executorMethodsToAdd.Any() || generatorMethodsToAdd.Any()) currentClass.AddMethod(executorMethodsToAdd.Union(generatorMethodsToAdd).ToArray()); @@ -187,6 +196,7 @@ private static CodeMethod GetMethodClone(CodeMethod currentMethod, params CodePa if(currentMethod.Parameters.Any(x => parameterTypesToExclude.Contains(x.ParameterKind))) { var cloneMethod = currentMethod.Clone() as CodeMethod; cloneMethod.Parameters.RemoveAll(x => parameterTypesToExclude.Contains(x.ParameterKind)); + cloneMethod.IsOverload = true; return cloneMethod; } else return null; diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index b70bb19c7c..89ae697868 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -33,6 +33,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); + var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) { writer.WriteLine($"Objects.requireNonNull({parameter.Name});"); } @@ -47,10 +48,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteIndexerBody(codeElement, writer, returnType); break; case CodeMethodKind.RequestGenerator: - WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, writer); + WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer); break; case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, returnType, writer); + WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, returnType, writer); break; case CodeMethodKind.Getter: WriteGetterBody(codeElement, writer, parentClass); @@ -161,7 +162,7 @@ private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass } writer.WriteLine("}};"); } - private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, string returnType, LanguageWriter writer) { + private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var generatorMethodName = (codeElement.Parent as CodeClass) @@ -173,7 +174,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ writer.WriteLine("try {"); writer.IncreaseIndent(); writer.WriteLine($"final RequestInfo requestInfo = {generatorMethodName}("); - var requestInfoParameters = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name }.Where(x => x != null); + var requestInfoParameters = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name, optionsParam?.Name }.Where(x => x != null); if(requestInfoParameters.Any()) { writer.IncreaseIndent(); writer.WriteLine(requestInfoParameters.Aggregate((x,y) => $"{x}, {y}")); @@ -192,7 +193,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ writer.DecreaseIndent(); writer.WriteLine("}"); } - private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, LanguageWriter writer) { + private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); writer.WriteLine("final RequestInfo requestInfo = new RequestInfo() {{"); @@ -223,6 +224,13 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter req writer.DecreaseIndent(); writer.WriteLine("}"); } + if(optionsParam != null) { + writer.WriteLine($"if ({optionsParam.Name} != null) {{"); + writer.IncreaseIndent(); + writer.WriteLine($"requestInfo.addMiddlewareOptions({optionsParam.Name}.toArray());"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + } writer.WriteLine("return requestInfo;"); } private void WriteSerializerBody(CodeClass parentClass, LanguageWriter writer) { From 5787885c5b6b0c43840418b9e3d16adab9f6f331 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 13:50:12 -0400 Subject: [PATCH 10/19] - fixes java overload generation with middleware options --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 8 ++- src/Kiota.Builder/CodeDOM/CodeParameter.cs | 3 +- src/Kiota.Builder/KiotaBuilder.cs | 3 +- src/Kiota.Builder/Refiners/JavaRefiner.cs | 6 +- .../Writers/Java/CodeMethodWriter.cs | 56 +++++++++++-------- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 8df7eb760b..5529fabce1 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -57,7 +57,11 @@ public bool IsSerializationMethod { /// /// Indicates whether this method is an overload for another method. /// - public bool IsOverload { get; set; } + public bool IsOverload { get { return OriginalMethod != null; } } + /// + /// Provides a reference to the original method that this method is an overload of. + /// + public CodeMethod OriginalMethod { get; set; } public object Clone() { @@ -76,7 +80,7 @@ public object Clone() PathSegment = PathSegment?.Clone() as string, SerializerModules = SerializerModules == null ? null : new (SerializerModules), DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules), - IsOverload = IsOverload, + OriginalMethod = OriginalMethod }; } diff --git a/src/Kiota.Builder/CodeDOM/CodeParameter.cs b/src/Kiota.Builder/CodeDOM/CodeParameter.cs index cd4d88ac23..be3f1480fa 100644 --- a/src/Kiota.Builder/CodeDOM/CodeParameter.cs +++ b/src/Kiota.Builder/CodeDOM/CodeParameter.cs @@ -13,7 +13,8 @@ public enum CodeParameterKind SetterValue, HttpCore, CurrentPath, - Options + Options, + Serializer } public class CodeParameter : CodeTerminal, ICloneable, IDocumentedElement diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 01f05c3759..19f1b1e642 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -780,7 +780,8 @@ private void AddSerializationMembers(CodeClass model, bool includeAdditionalProp serializeMethod.ReturnType = new CodeType(serializeMethod) { Name = voidType, IsNullable = false, IsExternal = true }; var parameter = new CodeParameter(serializeMethod) { Name = "writer", - Description = "Serialization writer to use to serialize this model" + Description = "Serialization writer to use to serialize this model", + ParameterKind = CodeParameterKind.Serializer, }; parameter.Type = new CodeType(parameter) { Name = "ISerializationWriter", IsExternal = true, IsNullable = false }; serializeMethod.AddParameter(parameter); diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index 74d8bcb9cd..804768d92a 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -129,14 +129,14 @@ private static void CorrectCoreType(CodeElement currentElement) { currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "Collection"); } else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) { currentMethod.ReturnType.Name = $"Map>"; currentMethod.Name = "getFieldDeserializers"; } else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) - .Where(x => x.Type.Name.StartsWith("I", StringComparison.InvariantCultureIgnoreCase)) + .Where(x => x.Type.Name.StartsWith("I", StringComparison.OrdinalIgnoreCase)) .ToList() .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" } @@ -196,7 +196,7 @@ private static CodeMethod GetMethodClone(CodeMethod currentMethod, params CodePa if(currentMethod.Parameters.Any(x => parameterTypesToExclude.Contains(x.ParameterKind))) { var cloneMethod = currentMethod.Clone() as CodeMethod; cloneMethod.Parameters.RemoveAll(x => parameterTypesToExclude.Contains(x.ParameterKind)); - cloneMethod.IsOverload = true; + cloneMethod.OriginalMethod = currentMethod; return cloneMethod; } else return null; diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index 89ae697868..ab607349e7 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -47,7 +47,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri case CodeMethodKind.IndexerBackwardCompatibility: WriteIndexerBody(codeElement, writer, returnType); break; - case CodeMethodKind.RequestGenerator: + case CodeMethodKind.RequestGenerator when codeElement.IsOverload: + WriteGeneratorMethodCall(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer, "return "); + break; + case CodeMethodKind.RequestGenerator when !codeElement.IsOverload: WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer); break; case CodeMethodKind.RequestExecutor: @@ -164,23 +167,9 @@ private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass } private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - - var generatorMethodName = (codeElement.Parent as CodeClass) - .GetChildElements(true) - .OfType() - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) - ?.Name - ?.ToFirstCharacterLowerCase(); writer.WriteLine("try {"); writer.IncreaseIndent(); - writer.WriteLine($"final RequestInfo requestInfo = {generatorMethodName}("); - var requestInfoParameters = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name, optionsParam?.Name }.Where(x => x != null); - if(requestInfoParameters.Any()) { - writer.IncreaseIndent(); - writer.WriteLine(requestInfoParameters.Aggregate((x,y) => $"{x}, {y}")); - writer.DecreaseIndent(); - } - writer.WriteLine(");"); + WriteGeneratorMethodCall(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer, $"final RequestInfo {requestInfoVarName} = "); var sendMethodName = conventions.PrimitiveTypes.Contains(returnType) ? "sendPrimitiveAsync" : "sendAsync"; if(codeElement.Parameters.Any(x => x.IsOfKind(CodeParameterKind.ResponseHandler))) writer.WriteLine($"return this.httpCore.{sendMethodName}(requestInfo, {returnType}.class, responseHandler);"); @@ -193,10 +182,31 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ writer.DecreaseIndent(); writer.WriteLine("}"); } + 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) + .GetChildElements(true) + .OfType() + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) + ?.Name + ?.ToFirstCharacterLowerCase(); + var paramsList = new List { requestBodyParam, queryStringParam, headersParam, optionsParam }; + var requestInfoParameters = paramsList.Where(x => x != null) + .Select(x => x.Name) + .ToList(); + var shouldSkipBodyParam = requestBodyParam == null && (codeElement.HttpMethod == HttpMethod.Get || codeElement.HttpMethod == HttpMethod.Delete); + var skipIndex = shouldSkipBodyParam ? 1 : 0; + if(codeElement.IsOverload && !codeElement.OriginalMethod.Parameters.Any(x => x.IsOfKind(CodeParameterKind.QueryParameter)) || // we're on an overload and the original method has no query parameters + !codeElement.IsOverload && queryStringParam == null) // we're on the original method and there is no query string parameter + skipIndex++;// we skip the query string parameter null value + requestInfoParameters.AddRange(paramsList.Where(x => x == null).Skip(skipIndex).Select(x => "null")); + var paramsCall = requestInfoParameters.Any() ? requestInfoParameters.Aggregate((x,y) => $"{x}, {y}") : string.Empty; + writer.WriteLine($"{prefix}{generatorMethodName}({paramsCall});"); + } private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - writer.WriteLine("final RequestInfo requestInfo = new RequestInfo() {{"); + writer.WriteLine($"final RequestInfo {requestInfoVarName} = new RequestInfo() {{{{"); writer.IncreaseIndent(); writer.WriteLines($"uri = new URI({conventions.CurrentPathPropertyName} + {conventions.PathSegmentPropertyName});", $"httpMethod = HttpMethod.{codeElement.HttpMethod?.ToString().ToUpperInvariant()};"); @@ -204,34 +214,34 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter req writer.WriteLine("}};"); if(requestBodyParam != null) if(requestBodyParam.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"requestInfo.setStreamContent({requestBodyParam.Name});"); + writer.WriteLine($"{requestInfoVarName}.setStreamContent({requestBodyParam.Name});"); else - writer.WriteLine($"requestInfo.setContentFromParsable({requestBodyParam.Name}, {conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); + writer.WriteLine($"{requestInfoVarName}.setContentFromParsable({requestBodyParam.Name}, {conventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); if(queryStringParam != null) { var httpMethodPrefix = codeElement.HttpMethod.ToString().ToFirstCharacterUpperCase(); writer.WriteLine($"if ({queryStringParam.Name} != null) {{"); writer.IncreaseIndent(); writer.WriteLines($"final {httpMethodPrefix}QueryParameters qParams = new {httpMethodPrefix}QueryParameters();", $"{queryStringParam.Name}.accept(qParams);", - "qParams.AddQueryParameters(requestInfo.queryParameters);"); + $"qParams.AddQueryParameters({requestInfoVarName}.queryParameters);"); writer.DecreaseIndent(); writer.WriteLine("}"); } if(headersParam != null) { writer.WriteLine($"if ({headersParam.Name} != null) {{"); writer.IncreaseIndent(); - writer.WriteLine($"{headersParam.Name}.accept(requestInfo.headers);"); + writer.WriteLine($"{headersParam.Name}.accept({requestInfoVarName}.headers);"); writer.DecreaseIndent(); writer.WriteLine("}"); } if(optionsParam != null) { writer.WriteLine($"if ({optionsParam.Name} != null) {{"); writer.IncreaseIndent(); - writer.WriteLine($"requestInfo.addMiddlewareOptions({optionsParam.Name}.toArray());"); + writer.WriteLine($"{requestInfoVarName}.addMiddlewareOptions({optionsParam.Name}.toArray(new MiddlewareOption[0]));"); writer.DecreaseIndent(); writer.WriteLine("}"); } - writer.WriteLine("return requestInfo;"); + writer.WriteLine($"return {requestInfoVarName};"); } private void WriteSerializerBody(CodeClass parentClass, LanguageWriter writer) { var additionalDataProperty = parentClass.GetPropertiesOfKind(CodePropertyKind.AdditionalData).FirstOrDefault(); From eb70a508f35a3fcb3dfafcc2860fb5f17ecb7cbf Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 15:00:47 -0400 Subject: [PATCH 11/19] - adds middleware options for typescript abstractions --- abstractions/typescript/src/index.ts | 3 ++- abstractions/typescript/src/middlewareOption.ts | 5 +++++ abstractions/typescript/src/requestInfo.ts | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 abstractions/typescript/src/middlewareOption.ts diff --git a/abstractions/typescript/src/index.ts b/abstractions/typescript/src/index.ts index cd6b5b6955..627037850c 100644 --- a/abstractions/typescript/src/index.ts +++ b/abstractions/typescript/src/index.ts @@ -8,4 +8,5 @@ export * from "./nativeResponseHandler"; export * from "./nativeResponseWrapper"; export * from './serialization'; export * from './utils'; -export * from './store'; \ No newline at end of file +export * from './store'; +export * from './middlewareOption'; \ No newline at end of file diff --git a/abstractions/typescript/src/middlewareOption.ts b/abstractions/typescript/src/middlewareOption.ts new file mode 100644 index 0000000000..fa1a47ee1d --- /dev/null +++ b/abstractions/typescript/src/middlewareOption.ts @@ -0,0 +1,5 @@ +/** Represents a middleware option. */ +export interface MiddlewareOption { + /** Gets the option key for when adding it to a request. Must be unique. */ + getKey(): string; +} \ No newline at end of file diff --git a/abstractions/typescript/src/requestInfo.ts b/abstractions/typescript/src/requestInfo.ts index 1846ed074b..5973f39961 100644 --- a/abstractions/typescript/src/requestInfo.ts +++ b/abstractions/typescript/src/requestInfo.ts @@ -2,6 +2,7 @@ import { HttpMethod } from "./httpMethod"; import { ReadableStream } from 'web-streams-polyfill/es2018'; import { Parsable } from "./serialization"; import { HttpCore } from "./httpCore"; +import { MiddlewareOption } from "./middlewareOption"; /** This class represents an abstract HTTP request. */ export class RequestInfo { @@ -15,6 +16,22 @@ export class RequestInfo { public queryParameters: Map = new Map(); //TODO: case insensitive /** The Request Headers. */ public headers: Map = new Map(); //TODO: case insensitive + private _middlewareOptions = new Map(); //TODO: case insensitive + /** Gets the middleware options for the request. */ + public getMiddlewareOptions() { return this._middlewareOptions.values(); } + public addMiddlewareOptions(...options: MiddlewareOption[]) { + if(!options || options.length === 0) return; + options.forEach(option => { + this._middlewareOptions.set(option.getKey(), option); + }); + } + /** Removes the middleware options for the request. */ + public removeMiddlewareOptions(...options: MiddlewareOption[]) { + if(!options || options.length === 0) return; + options.forEach(option => { + this._middlewareOptions.delete(option.getKey()); + }); + } private static binaryContentType = "application/octet-stream"; private static contentTypeHeader = "Content-Type"; /** From 500e03f54918f8363b28f2c9a9a229c481b6b41e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 15 Jul 2021 15:01:18 -0400 Subject: [PATCH 12/19] - adds middleware options support to typescript generation --- .../Refiners/TypeScriptRefiner.cs | 10 +++++-- .../Writers/TypeScript/CodeMethodWriter.cs | 30 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 42ba71d12a..2c8801c86c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -42,6 +42,7 @@ private static void AddParsableInheritanceForModelClasses(CodeElement currentEle new ("HttpMethod", "@microsoft/kiota-abstractions"), new ("RequestInfo", "@microsoft/kiota-abstractions"), new ("ResponseHandler", "@microsoft/kiota-abstractions"), + new ("MiddlewareOption", "@microsoft/kiota-abstractions"), }; private static readonly Tuple[] defaultNamespacesForModels = new Tuple[] { new ("SerializationWriter", "@microsoft/kiota-abstractions"), @@ -69,10 +70,13 @@ private static void CorrectCoreType(CodeElement currentElement) { } } if (currentElement is CodeMethod currentMethod) { - if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("IResponseHandler")).ToList().ForEach(x => x.Type.Name = "ResponseHandler"); + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.ResponseHandler) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "MiddlewareOption[]"); + } else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("ISerializationWriter")).ToList().ForEach(x => x.Type.Name = "SerializationWriter"); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) currentMethod.ReturnType.Name = $"Map void>"; else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 0e5e434c1b..3529c6b6a7 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -30,6 +30,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); + var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options); if(!codeElement.IsOfKind(CodeMethodKind.Setter)) foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) { writer.WriteLine($"if(!{parameter.Name}) throw new Error(\"{parameter.Name} cannot be undefined\");"); @@ -47,10 +48,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteSerializerBody(inherits, parentClass, writer); break; case CodeMethodKind.RequestGenerator: - WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, writer); + WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer); break; case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, isVoid, returnType, writer); + WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, isVoid, returnType, writer); break; case CodeMethodKind.Getter: WriteGetterBody(codeElement, writer, parentClass); @@ -153,7 +154,7 @@ private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass writer.DecreaseIndent(); writer.WriteLine("]);"); } - private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, bool isVoid, string returnType, LanguageWriter writer) { + private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, bool isVoid, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var generatorMethodName = (codeElement.Parent as CodeClass) @@ -163,7 +164,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ ?.Name ?.ToFirstCharacterLowerCase(); writer.WriteLine($"const requestInfo = this.{generatorMethodName}("); - var requestInfoParameters = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name }.Where(x => x != null); + var requestInfoParameters = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name, optionsParam?.Name }.Where(x => x != null); if(requestInfoParameters.Any()) { writer.IncreaseIndent(); writer.WriteLine(requestInfoParameters.Aggregate((x,y) => $"{x}, {y}")); @@ -175,23 +176,26 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ var newFactoryParameter = GetTypeFactory(isVoid, isStream, returnType); writer.WriteLine($"return this.httpCore?.{genericTypeForSendMethod}(requestInfo,{newFactoryParameter} responseHandler) ?? Promise.reject(new Error('http core is null'));"); } - private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, LanguageWriter writer) { + private const string requestInfoVarName = "requestInfo"; + private void WriteRequestGeneratorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - writer.WriteLines("const requestInfo = new RequestInfo();", - $"requestInfo.URI = (this.{localConventions.CurrentPathPropertyName} ?? '') + this.{localConventions.PathSegmentPropertyName},", - $"requestInfo.httpMethod = HttpMethod.{codeElement.HttpMethod.ToString().ToUpperInvariant()},"); + writer.WriteLines($"const {requestInfoVarName} = new RequestInfo();", + $"{requestInfoVarName}.URI = (this.{localConventions.CurrentPathPropertyName} ?? '') + this.{localConventions.PathSegmentPropertyName},", + $"{requestInfoVarName}.httpMethod = HttpMethod.{codeElement.HttpMethod.ToString().ToUpperInvariant()},"); if(headersParam != null) - writer.WriteLine($"{headersParam.Name} && requestInfo.setHeadersFromRawObject(h);"); + writer.WriteLine($"{headersParam.Name} && {requestInfoVarName}.setHeadersFromRawObject(h);"); if(queryStringParam != null) - writer.WriteLines($"{queryStringParam.Name} && requestInfo.setQueryStringParametersFromRawObject(q);"); + writer.WriteLines($"{queryStringParam.Name} && {requestInfoVarName}.setQueryStringParametersFromRawObject(q);"); if(requestBodyParam != null) { if(requestBodyParam.Type.Name.Equals(localConventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"requestInfo.setStreamContent({requestBodyParam.Name});"); + writer.WriteLine($"{requestInfoVarName}.setStreamContent({requestBodyParam.Name});"); else - writer.WriteLine($"requestInfo.setContentFromParsable({requestBodyParam.Name}, this.{localConventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); + writer.WriteLine($"{requestInfoVarName}.setContentFromParsable({requestBodyParam.Name}, this.{localConventions.HttpCorePropertyName}, \"{codeElement.ContentType}\");"); } - writer.WriteLine("return requestInfo;"); + if(optionsParam != null) + writer.WriteLine($"{optionsParam.Name} && {requestInfoVarName}.addMiddlewareOptions(...{optionsParam.Name});"); + writer.WriteLine($"return {requestInfoVarName};"); } private void WriteSerializerBody(bool inherits, CodeClass parentClass, LanguageWriter writer) { var additionalDataProperty = parentClass.GetPropertiesOfKind(CodePropertyKind.AdditionalData).FirstOrDefault(); From 0561f88d3440792b3aa41950e807a95323570140 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 16 Jul 2021 10:07:00 -0400 Subject: [PATCH 13/19] - adds a middleware pipeline for http core typescript --- http/typescript/fetch/src/httpClient.ts | 52 +++++++++++++++++++++++++ http/typescript/fetch/src/httpCore.ts | 34 ++++++++++------ http/typescript/fetch/src/index.ts | 4 +- http/typescript/fetch/src/middleware.ts | 12 ++++++ 4 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 http/typescript/fetch/src/httpClient.ts create mode 100644 http/typescript/fetch/src/middleware.ts diff --git a/http/typescript/fetch/src/httpClient.ts b/http/typescript/fetch/src/httpClient.ts new file mode 100644 index 0000000000..8ab01d9ff0 --- /dev/null +++ b/http/typescript/fetch/src/httpClient.ts @@ -0,0 +1,52 @@ +import { Middleware } from "./middleware"; +import { fetch } from 'cross-fetch'; + +/** Default fetch client with options and a middleware pipleline for requests execution. */ +export class HttpClient { + /** + * Instantiates a new HttpClient. + * @param middlewares middlewares to be used for requests execution. + * @param defaultRequestSettings default request settings to be used for requests execution. + */ + public constructor(private readonly middlewares: Middleware[] = HttpClient.getDefaultMiddlewares(), private readonly defaultRequestSettings: RequestInit = HttpClient.getDefaultRequestSettings()) { + this.middlewares = [...this.middlewares, new FetchMiddleware()]; + this.middlewares.forEach((middleware, idx) => { + if(idx < this.middlewares.length) + middleware.next = this.middlewares[idx + 1]; + }); + } + /** + * Executes a request and returns a promise resolving the response. + * @param url the request url. + * @param options request options. + * @returns the promise resolving the response. + */ + public fetch(url: string, options?: RequestInit): Promise { + const finalOptions = {...this.defaultRequestSettings, ...options} as RequestInit; + if(this.middlewares.length > 0 && this.middlewares[0]) + return this.middlewares[0].execute(url, finalOptions); + else + throw new Error("No middlewares found"); + } + /** + * Gets the default middlewares in use for the client. + * @returns the default middlewares. + */ + public static getDefaultMiddlewares(): Middleware[] { + return []; //TODO add default middlewares + } + /** + * Gets the default request settings to be used for the client. + * @returns the default request settings. + */ + public static getDefaultRequestSettings(): RequestInit { + return {}; //TODO add default request settings + } +} +/** Default middleware executing a request. Internal use only. */ +class FetchMiddleware implements Middleware { + next: Middleware | undefined; + execute(url: string, req: RequestInit): Promise { + return fetch(url, req); + } +} \ No newline at end of file diff --git a/http/typescript/fetch/src/httpCore.ts b/http/typescript/fetch/src/httpCore.ts index 6c48b6f230..f51091ee75 100644 --- a/http/typescript/fetch/src/httpCore.ts +++ b/http/typescript/fetch/src/httpCore.ts @@ -1,21 +1,33 @@ import { AuthenticationProvider, HttpCore as IHttpCore, Parsable, ParseNodeFactory, RequestInfo, ResponseHandler, ParseNodeFactoryRegistry, enableBackingStoreForParseNodeFactory, SerializationWriterFactoryRegistry, enableBackingStoreForSerializationWriterFactory, SerializationWriterFactory } from '@microsoft/kiota-abstractions'; -import { fetch, Headers as FetchHeadersCtor } from 'cross-fetch'; +import { Headers as FetchHeadersCtor } from 'cross-fetch'; import { ReadableStream } from 'web-streams-polyfill'; import { URLSearchParams } from 'url'; +import { HttpClient } from './httpClient'; export class HttpCore implements IHttpCore { - private _serializationWriterFactory: SerializationWriterFactory; public getSerializationWriterFactory(): SerializationWriterFactory { - return this._serializationWriterFactory; + return this.serializationWriterFactory; } private static readonly authorizationHeaderKey = "Authorization"; /** - * + * Instantiates a new http core service + * @param authenticationProvider the authentication provider to use. + * @param parseNodeFactory the parse node factory to deserialize responses. + * @param serializationWriterFactory the serialization writer factory to use to serialize request bodies. + * @param httpClient the http client to use to execute requests. */ - public constructor(public readonly authenticationProvider: AuthenticationProvider, private parseNodeFactory: ParseNodeFactory = ParseNodeFactoryRegistry.defaultInstance, serializationWriterFactory: SerializationWriterFactory = SerializationWriterFactoryRegistry.defaultInstance) { + public constructor(public readonly authenticationProvider: AuthenticationProvider, private parseNodeFactory: ParseNodeFactory = ParseNodeFactoryRegistry.defaultInstance, private serializationWriterFactory: SerializationWriterFactory = SerializationWriterFactoryRegistry.defaultInstance, private readonly httpClient: HttpClient = new HttpClient()) { if(!authenticationProvider) { throw new Error('authentication provider cannot be null'); } - this._serializationWriterFactory = serializationWriterFactory; + if(!parseNodeFactory) { + throw new Error('parse node factory cannot be null'); + } + if(!serializationWriterFactory) { + throw new Error('serialization writer factory cannot be null'); + } + if(!httpClient) { + throw new Error('http client cannot be null'); + } } private getResponseContentType = (response: Response): string | undefined => { const header = response.headers.get("content-type")?.toLowerCase(); @@ -31,7 +43,7 @@ export class HttpCore implements IHttpCore { await this.addBearerIfNotPresent(requestInfo); const request = this.getRequestFromRequestInfo(requestInfo); - const response = await fetch(this.getRequestUrl(requestInfo), request); + const response = await this.httpClient.fetch(this.getRequestUrl(requestInfo), request); if(responseHandler) { return await responseHandler.handleResponseAsync(response); } else { @@ -52,7 +64,7 @@ export class HttpCore implements IHttpCore { await this.addBearerIfNotPresent(requestInfo); const request = this.getRequestFromRequestInfo(requestInfo); - const response = await fetch(this.getRequestUrl(requestInfo), request); + const response = await this.httpClient.fetch(this.getRequestUrl(requestInfo), request); if(responseHandler) { return await responseHandler.handleResponseAsync(response); } else { @@ -100,15 +112,15 @@ export class HttpCore implements IHttpCore { await this.addBearerIfNotPresent(requestInfo); const request = this.getRequestFromRequestInfo(requestInfo); - const response = await fetch(this.getRequestUrl(requestInfo), request); + const response = await this.httpClient.fetch(this.getRequestUrl(requestInfo), request); if(responseHandler) { return await responseHandler.handleResponseAsync(response); } } public enableBackingStore = (): void => { this.parseNodeFactory = enableBackingStoreForParseNodeFactory(this.parseNodeFactory); - this._serializationWriterFactory = enableBackingStoreForSerializationWriterFactory(this._serializationWriterFactory); - if(!this._serializationWriterFactory || !this.parseNodeFactory) + this.serializationWriterFactory = enableBackingStoreForSerializationWriterFactory(this.serializationWriterFactory); + if(!this.serializationWriterFactory || !this.parseNodeFactory) throw new Error("unable to enable backing store"); } private addBearerIfNotPresent = async (requestInfo: RequestInfo): Promise => { diff --git a/http/typescript/fetch/src/index.ts b/http/typescript/fetch/src/index.ts index e6f69e7c19..01e1f085f8 100644 --- a/http/typescript/fetch/src/index.ts +++ b/http/typescript/fetch/src/index.ts @@ -1 +1,3 @@ -export * from './httpCore'; \ No newline at end of file +export * from './httpCore'; +export * from './httpClient'; +export * from './middleware'; \ No newline at end of file diff --git a/http/typescript/fetch/src/middleware.ts b/http/typescript/fetch/src/middleware.ts new file mode 100644 index 0000000000..af8f18ccdb --- /dev/null +++ b/http/typescript/fetch/src/middleware.ts @@ -0,0 +1,12 @@ +/** Defines the contract for a middleware in the request execution pipeline. */ +export interface Middleware { + /** Next middleware to be executed. The current middleware must execute it in its implementation. */ + next: Middleware | undefined; + /** + * Main method of the middleware. + * @param req The request object. + * @param url The URL of the request. + * @return A promise that resolves to the response object. + */ + execute(url: string, req: RequestInit): Promise; +} \ No newline at end of file From 1892de2b64d56e2871a37ad79b80c68240f8f217 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 16 Jul 2021 10:25:16 -0400 Subject: [PATCH 14/19] - bumps versions for abstractions and http libs --- abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj | 2 +- abstractions/java/lib/build.gradle | 2 +- abstractions/typescript/package-lock.json | 2 +- abstractions/typescript/package.json | 2 +- .../httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj | 4 ++-- http/java/okhttp/lib/build.gradle | 4 ++-- http/typescript/fetch/package-lock.json | 2 +- http/typescript/fetch/package.json | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj index 19f0dbe9f9..c8689b793a 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.13 + 1.0.14 diff --git a/abstractions/java/lib/build.gradle b/abstractions/java/lib/build.gradle index e2a39ac1d0..efff7f9aa9 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.13' + version '1.0.14' from(components.java) } } diff --git a/abstractions/typescript/package-lock.json b/abstractions/typescript/package-lock.json index 848f2f4272..58e42ba014 100644 --- a/abstractions/typescript/package-lock.json +++ b/abstractions/typescript/package-lock.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.13", + "version": "1.0.14", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/abstractions/typescript/package.json b/abstractions/typescript/package.json index 1b3e30c81b..139040ac53 100644 --- a/abstractions/typescript/package.json +++ b/abstractions/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.13", + "version": "1.0.14", "description": "Core abstractions for kiota generated libraries in TypeScript and JavaScript", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj index 2af059605f..f2b8fbbeac 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.3 + 1.0.4 - + diff --git a/http/java/okhttp/lib/build.gradle b/http/java/okhttp/lib/build.gradle index 6604725815..ee79fd1316 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.13' + api 'com.microsoft.kiota:kiota-abstractions:1.0.14' } publishing { @@ -53,7 +53,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-http-okhttp' - version '1.0.3' + version '1.0.4' from(components.java) } } diff --git a/http/typescript/fetch/package-lock.json b/http/typescript/fetch/package-lock.json index cf36fbd779..320dd3e81d 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.3", + "version": "1.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/http/typescript/fetch/package.json b/http/typescript/fetch/package.json index 134bb295c0..bd5e4fa32f 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.3", + "version": "1.0.4", "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.13", + "@microsoft/kiota-abstractions": "^1.0.14", "cross-fetch": "^3.1.4", "web-streams-polyfill": "^3.1.0" }, From 825494072e2cad582f67d20c6aba2b89042c27d3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 16 Jul 2021 13:35:16 -0400 Subject: [PATCH 15/19] - code linting --- .../Refiners/CommonLanguageRefiner.cs | 11 +++ src/Kiota.Builder/Refiners/JavaRefiner.cs | 73 +++++++++---------- .../Refiners/TypeScriptRefiner.cs | 57 +++++++-------- .../Writers/CSharp/CodeMethodWriter.cs | 6 +- .../Writers/TypeScript/CodeMethodWriter.cs | 6 +- 5 files changed, 78 insertions(+), 75 deletions(-) diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 8e5a286276..2f69c2e9bc 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -465,5 +465,16 @@ protected static void CrawlTree(CodeElement currentElement, Action foreach(var childElement in currentElement.GetChildElements()) function.Invoke(childElement); } + protected static void CorrectCoreType(CodeElement currentElement, Action correctMethodType, Action correctPropertyType) { + switch(currentElement) { + case CodeProperty property: + correctPropertyType.Invoke(property); + break; + case CodeMethod method: + correctMethodType.Invoke(method); + break; + } + CrawlTree(currentElement, x => CorrectCoreType(x, correctMethodType, correctPropertyType)); + } } } diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index 804768d92a..f5c15164b3 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -17,7 +17,7 @@ public override void Refine(CodeNamespace generatedCode) FixReferencesToEntityType(generatedCode); AddPropertiesAndMethodTypesImports(generatedCode, true, false, true); AddDefaultImports(generatedCode, defaultNamespaces, defaultNamespacesForModels, defaultNamespacesForRequestBuilders, defaultSymbolsForApiClient); - CorrectCoreType(generatedCode); + CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType); PatchHeaderParametersType(generatedCode); AddListImport(generatedCode); AddParsableInheritanceForModelClasses(generatedCode); @@ -101,46 +101,43 @@ private static void AddListImport(CodeElement currentElement) { new ("SerializationWriterFactoryRegistry", "com.microsoft.kiota.serialization"), new ("ParseNodeFactoryRegistry", "com.microsoft.kiota.serialization"), }; - private static void CorrectCoreType(CodeElement currentElement) { - if (currentElement is CodeProperty currentProperty && currentProperty.Type != null) { - if(currentProperty.IsOfKind(CodePropertyKind.HttpCore)) + private static void CorrectPropertyType(CodeProperty currentProperty) { + if(currentProperty.IsOfKind(CodePropertyKind.HttpCore)) currentProperty.Type.Name = "HttpCore"; - else if(currentProperty.IsOfKind(CodePropertyKind.BackingStore)) - currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I" - else if("DateTimeOffset".Equals(currentProperty.Type.Name, StringComparison.OrdinalIgnoreCase)) { - currentProperty.Type.Name = $"OffsetDateTime"; - var nUsing = new CodeUsing(currentProperty.Parent) { - Name = "OffsetDateTime", - }; - nUsing.Declaration = new CodeType(nUsing) { - Name = "java.time", - IsExternal = true, - }; - (currentProperty.Parent as CodeClass).AddUsing(nUsing); - } else if(currentProperty.IsOfKind(CodePropertyKind.AdditionalData)) { - currentProperty.Type.Name = "Map"; - currentProperty.DefaultValue = "new HashMap<>()"; - } + else if(currentProperty.IsOfKind(CodePropertyKind.BackingStore)) + currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I" + else if("DateTimeOffset".Equals(currentProperty.Type.Name, StringComparison.OrdinalIgnoreCase)) { + currentProperty.Type.Name = $"OffsetDateTime"; + var nUsing = new CodeUsing(currentProperty.Parent) { + Name = "OffsetDateTime", + }; + nUsing.Declaration = new CodeType(nUsing) { + Name = "java.time", + IsExternal = true, + }; + (currentProperty.Parent as CodeClass).AddUsing(nUsing); + } else if(currentProperty.IsOfKind(CodePropertyKind.AdditionalData)) { + currentProperty.Type.Name = "Map"; + currentProperty.DefaultValue = "new HashMap<>()"; } - if (currentElement is CodeMethod currentMethod) { - if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { - if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.ResponseHandler) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "Collection"); - } - else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); - else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) { - currentMethod.ReturnType.Name = $"Map>"; - currentMethod.Name = "getFieldDeserializers"; - } - else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) - .Where(x => x.Type.Name.StartsWith("I", StringComparison.OrdinalIgnoreCase)) - .ToList() - .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" + } + private static void CorrectMethodType(CodeMethod currentMethod) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.ResponseHandler) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "Collection"); + } + else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) { + currentMethod.ReturnType.Name = $"Map>"; + currentMethod.Name = "getFieldDeserializers"; } - CrawlTree(currentElement, CorrectCoreType); + else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) + .Where(x => x.Type.Name.StartsWith("I", StringComparison.OrdinalIgnoreCase)) + .ToList() + .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" } private static void AddRequireNonNullImports(CodeElement currentElement) { if(currentElement is CodeMethod currentMethod && currentMethod.Parameters.Any(x => !x.Optional)) { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 2c8801c86c..7f3f2016b9 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -10,7 +10,7 @@ public override void Refine(CodeNamespace generatedCode) PatchResponseHandlerType(generatedCode); AddDefaultImports(generatedCode, Array.Empty>(), defaultNamespacesForModels, defaultNamespacesForRequestBuilders, defaultSymbolsForApiClient); ReplaceIndexersByMethodsWithParameter(generatedCode, generatedCode, "ById"); - CorrectCoreType(generatedCode); + CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType); CorrectCoreTypesForBackingStoreUsings(generatedCode, "@microsoft/kiota-abstractions"); FixReferencesToEntityType(generatedCode); AddPropertiesAndMethodTypesImports(generatedCode, true, true, true); @@ -56,38 +56,33 @@ private static void AddParsableInheritanceForModelClasses(CodeElement currentEle new ("SerializationWriterFactoryRegistry", "@microsoft/kiota-abstractions"), new ("ParseNodeFactoryRegistry", "@microsoft/kiota-abstractions"), }; - private static void CorrectCoreType(CodeElement currentElement) { - if (currentElement is CodeProperty currentProperty) { - if(currentProperty.IsOfKind(CodePropertyKind.HttpCore)) - currentProperty.Type.Name = "HttpCore"; - else if(currentProperty.IsOfKind(CodePropertyKind.BackingStore)) - currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I" - else if("DateTimeOffset".Equals(currentProperty.Type.Name, StringComparison.OrdinalIgnoreCase)) - currentProperty.Type.Name = $"Date"; - else if(currentProperty.IsOfKind(CodePropertyKind.AdditionalData)) { - currentProperty.Type.Name = "Map"; - currentProperty.DefaultValue = "new Map()"; - } + private static void CorrectPropertyType(CodeProperty currentProperty) { + if(currentProperty.IsOfKind(CodePropertyKind.HttpCore)) + currentProperty.Type.Name = "HttpCore"; + else if(currentProperty.IsOfKind(CodePropertyKind.BackingStore)) + currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I" + else if("DateTimeOffset".Equals(currentProperty.Type.Name, StringComparison.OrdinalIgnoreCase)) + currentProperty.Type.Name = $"Date"; + else if(currentProperty.IsOfKind(CodePropertyKind.AdditionalData)) { + currentProperty.Type.Name = "Map"; + currentProperty.DefaultValue = "new Map()"; } - if (currentElement is CodeMethod currentMethod) { - if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { - if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.ResponseHandler) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "MiddlewareOption[]"); - } - else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); - else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) - currentMethod.ReturnType.Name = $"Map void>"; - else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) - .Where(x => x.Type.Name.StartsWith("I", StringComparison.InvariantCultureIgnoreCase)) - .ToList() - .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" + } + private static void CorrectMethodType(CodeMethod currentMethod) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.ResponseHandler) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Options)).ToList().ForEach(x => x.Type.Name = "MiddlewareOption[]"); } - - - CrawlTree(currentElement, CorrectCoreType); + else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer) && x.Type.Name.StartsWith("i", StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Type.Name = x.Type.Name[1..]); + else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) + currentMethod.ReturnType.Name = $"Map void>"; + else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) + .Where(x => x.Type.Name.StartsWith("I", StringComparison.InvariantCultureIgnoreCase)) + .ToList() + .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" } private static void PatchResponseHandlerType(CodeElement current) { if(current is CodeMethod currentMethod && currentMethod.Name.Equals("defaultResponseHandler", StringComparison.OrdinalIgnoreCase)) diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 3ef607c6bf..01e8166a03 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -42,7 +42,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer); break; case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, isVoid, returnType, writer); + WriteRequestExecutorBody(codeElement, new List { requestBodyParam, queryStringParam, headersParam, optionsParam }, isVoid, returnType, writer); break; case CodeMethodKind.Deserializer: WriteDeserializerBody(codeElement, parentClass, writer); @@ -140,7 +140,7 @@ private string GetDeserializationMethodName(CodeTypeBase propType) { return $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>"; } } - private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, bool isVoid, string returnType, LanguageWriter writer) { + private void WriteRequestExecutorBody(CodeMethod codeElement, IEnumerable parameters, bool isVoid, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); @@ -149,7 +149,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ .OfType() .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) ?.Name; - var parametersList = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name, optionsParam?.Name }.Where(x => x != null).Aggregate((x,y) => $"{x}, {y}"); + var parametersList = parameters.Select(x => 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);"); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 3529c6b6a7..775e8886be 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -51,7 +51,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, writer); break; case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, isVoid, returnType, writer); + WriteRequestExecutorBody(codeElement, new List {requestBodyParam, queryStringParam, headersParam, optionsParam}, isVoid, returnType, writer); break; case CodeMethodKind.Getter: WriteGetterBody(codeElement, writer, parentClass); @@ -154,7 +154,7 @@ private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass writer.DecreaseIndent(); writer.WriteLine("]);"); } - private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requestBodyParam, CodeParameter queryStringParam, CodeParameter headersParam, CodeParameter optionsParam, bool isVoid, string returnType, LanguageWriter writer) { + private void WriteRequestExecutorBody(CodeMethod codeElement, IEnumerable parameters, bool isVoid, string returnType, LanguageWriter writer) { if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); var generatorMethodName = (codeElement.Parent as CodeClass) @@ -164,7 +164,7 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeParameter requ ?.Name ?.ToFirstCharacterLowerCase(); writer.WriteLine($"const requestInfo = this.{generatorMethodName}("); - var requestInfoParameters = new List { requestBodyParam?.Name, queryStringParam?.Name, headersParam?.Name, optionsParam?.Name }.Where(x => x != null); + var requestInfoParameters = parameters.Select(x => x?.Name).Where(x => x != null); if(requestInfoParameters.Any()) { writer.IncreaseIndent(); writer.WriteLine(requestInfoParameters.Aggregate((x,y) => $"{x}, {y}")); From 98d32fbe802d53dc0890180d9f0aaf3f8af6dcad Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 16 Jul 2021 13:41:17 -0400 Subject: [PATCH 16/19] - fixes existing unit tests --- tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs | 2 +- .../Refiners/TypeScriptLanguageRefinerTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs index fd102aaede..dab210c386 100644 --- a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs @@ -237,7 +237,7 @@ public void CorrectsCoreType() { }).First(); serializationMethod.AddParameter(new CodeParameter(serializationMethod) { Name = "handler", - ParameterKind = CodeParameterKind.ResponseHandler, + ParameterKind = CodeParameterKind.Serializer, Type = new CodeType(executorMethod) { Name = serializerDefaultName, } diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index a45375466a..260fdacec5 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -137,7 +137,7 @@ public void CorrectsCoreType() { }).First(); serializationMethod.AddParameter(new CodeParameter(serializationMethod) { Name = "handler", - ParameterKind = CodeParameterKind.ResponseHandler, + ParameterKind = CodeParameterKind.Serializer, Type = new CodeType(executorMethod) { Name = serializerDefaultName, } From c367881e2b55847b31dd52a818e0109f02b4615c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 16 Jul 2021 14:26:27 -0400 Subject: [PATCH 17/19] - adds unit test for java overloads cross reference - adds unit tests for options parameter --- .../Refiners/JavaLanguageRefinerTests.cs | 70 +++++++++++++++++++ .../Writers/CSharp/CodeMethodWriterTests.cs | 16 +++++ .../Writers/Java/CodeMethodWriterTests.cs | 24 +++++++ .../TypeScript/CodeMethodWriterTests.cs | 6 ++ 4 files changed, 116 insertions(+) diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs index dab210c386..05d68b8731 100644 --- a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs @@ -252,6 +252,76 @@ public void CorrectsCoreType() { Assert.Empty(model.GetChildElements(true).OfType().SelectMany(x => x.Parameters).Where(x => headersDefaultName.Equals(x.Type.Name))); Assert.Empty(model.GetChildElements(true).OfType().SelectMany(x => x.Parameters).Where(x => serializerDefaultName.Equals(x.Type.Name))); } + [Fact] + public void AddsMethodsOverloads() { + var builder = root.AddClass(new CodeClass (root) { + Name = "model", + ClassKind = CodeClassKind.RequestBuilder + }).First(); + var executor = builder.AddMethod(new CodeMethod(builder) { + Name = "executor", + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType(builder) { + Name = "string" + } + }).First(); + executor.Parameters.Add(new CodeParameter(executor) { + Name = "handler", + ParameterKind = CodeParameterKind.ResponseHandler, + Type = new CodeType(executor) { + Name = "string" + } + }); + executor.AddParameter(new CodeParameter(executor) { + Name = "headers", + ParameterKind = CodeParameterKind.Headers, + Type = new CodeType(executor) { + Name = "string" + } + }); + executor.AddParameter(new CodeParameter(executor) { + Name = "query", + ParameterKind = CodeParameterKind.QueryParameter, + Type = new CodeType(executor) { + Name = "string" + } + }); + executor.AddParameter(new CodeParameter(executor) { + Name = "body", + ParameterKind = CodeParameterKind.RequestBody, + Type = new CodeType(executor) { + Name = "string" + } + }); + executor.AddParameter(new CodeParameter(executor) { + Name = "options", + ParameterKind = CodeParameterKind.Options, + Type = new CodeType(executor) { + Name = "string" + } + }); + var generator = builder.AddMethod(new CodeMethod(builder) { + Name = "generator", + MethodKind = CodeMethodKind.RequestGenerator, + ReturnType = new CodeType(builder) { + Name = "string" + } + }).First(); + generator.Parameters.AddRange(executor.Parameters.Where(x => !x.IsOfKind(CodeParameterKind.ResponseHandler))); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Java }, root); + var childMethods = builder.GetChildElements(true).OfType(); + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestExecutor) && x.Parameters.Count == 1));//only the body + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestGenerator) && x.Parameters.Count == 1));//only the body + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestExecutor) && x.Parameters.Count == 2));// body + query params + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestGenerator) && x.Parameters.Count == 2));// body + query params + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestExecutor) && x.Parameters.Count == 3));// body + query params + headers + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestGenerator) && x.Parameters.Count == 3));// body + query params + headers + Assert.True(childMethods.Any(x => x.IsOverload && x.IsOfKind(CodeMethodKind.RequestExecutor) && x.Parameters.Count == 4));// body + query params + headers + options + Assert.True(childMethods.Any(x => !x.IsOverload && x.IsOfKind(CodeMethodKind.RequestGenerator) && x.Parameters.Count == 4));// body + query params + headers + options + Assert.True(childMethods.Any(x => !x.IsOverload && x.IsOfKind(CodeMethodKind.RequestExecutor) && x.Parameters.Count == 5));// body + query params + headers + options + response handler + Assert.Equal(9, childMethods.Count()); + Assert.Equal(7, childMethods.Count(x => x.IsOverload)); + } #endregion } } diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs index 12bd4eea42..c93aea9ea9 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs @@ -110,6 +110,11 @@ private void AddRequestBodyParameters() { ParameterKind = CodeParameterKind.ResponseHandler, Type = stringType, }); + method.AddParameter(new CodeParameter(method) { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); } [Fact] public void WritesRequestBodiesThrowOnNullHttpMethod() { @@ -143,6 +148,7 @@ public void WritesRequestGeneratorBody() { Assert.Contains("h?.Invoke", result); Assert.Contains("AddQueryParameters", result); Assert.Contains("SetContentFromParsable", result); + Assert.Contains("AddMiddlewareOptions", result); Assert.Contains("return requestInfo;", result); AssertExtensions.CurlyBracesAreClosed(result); } @@ -370,5 +376,15 @@ public void WritesApiConstructorWithBackingStore() { var result = tw.ToString(); Assert.Contains("EnableBackingStore", result); } + [Fact] + public void ThrowsOnGetter() { + method.MethodKind = CodeMethodKind.Getter; + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void ThrowsOnSetter() { + method.MethodKind = CodeMethodKind.Setter; + Assert.Throws(() => writer.Write(method)); + } } } diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index 60ef07e4a8..8e2ebf6d15 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -110,6 +110,11 @@ private void AddRequestBodyParameters() { ParameterKind = CodeParameterKind.ResponseHandler, Type = stringType, }); + method.AddParameter(new CodeParameter(method) { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); } [Fact] public void WritesNullableVoidTypeForExecutor(){ @@ -154,10 +159,29 @@ public void WritesRequestGeneratorBody() { Assert.Contains("h.accept(requestInfo.headers)", result); Assert.Contains("AddQueryParameters", result); Assert.Contains("setContentFromParsable", result); + Assert.Contains("addMiddlewareOptions", result); Assert.Contains("return requestInfo;", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestGeneratorOverloadBody() { + method.MethodKind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + method.OriginalMethod = method; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("final RequestInfo requestInfo = new RequestInfo()", result); + Assert.DoesNotContain("httpMethod = HttpMethod.GET", result); + Assert.DoesNotContain("h.accept(requestInfo.headers)", result); + Assert.DoesNotContain("AddQueryParameters", result); + Assert.DoesNotContain("setContentFromParsable", result); + Assert.DoesNotContain("addMiddlewareOptions", result); + Assert.DoesNotContain("return requestInfo;", result); + Assert.Contains("return methodName(b, q, h, o)", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesInheritedDeSerializerBody() { method.MethodKind = CodeMethodKind.Deserializer; method.IsAsync = false; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 77e5a3e6dd..f127cf3be8 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -110,6 +110,11 @@ private void AddRequestBodyParameters() { ParameterKind = CodeParameterKind.ResponseHandler, Type = stringType, }); + method.AddParameter(new CodeParameter(method) { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); } [Fact] public void WritesRequestBodiesThrowOnNullHttpMethod() { @@ -142,6 +147,7 @@ public void WritesRequestGeneratorBody() { Assert.Contains("setHeadersFromRawObject", result); Assert.Contains("setQueryStringParametersFromRawObject", result); Assert.Contains("setContentFromParsable", result); + Assert.Contains("addMiddlewareOptions", result); Assert.Contains("return requestInfo;", result); AssertExtensions.CurlyBracesAreClosed(result); } From dbd5a7eaeb1b41a51616c1038ca4e53d882d6ad6 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 16 Jul 2021 14:37:22 -0400 Subject: [PATCH 18/19] - adds missing middleware options parameters to middlewares --- http/typescript/fetch/src/httpClient.ts | 7 ++++--- http/typescript/fetch/src/middleware.ts | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/http/typescript/fetch/src/httpClient.ts b/http/typescript/fetch/src/httpClient.ts index 8ab01d9ff0..98a59d4a58 100644 --- a/http/typescript/fetch/src/httpClient.ts +++ b/http/typescript/fetch/src/httpClient.ts @@ -1,5 +1,6 @@ import { Middleware } from "./middleware"; import { fetch } from 'cross-fetch'; +import { MiddlewareOption } from "@microsoft/kiota-abstractions"; /** Default fetch client with options and a middleware pipleline for requests execution. */ export class HttpClient { @@ -21,10 +22,10 @@ export class HttpClient { * @param options request options. * @returns the promise resolving the response. */ - public fetch(url: string, options?: RequestInit): Promise { + public fetch(url: string, options?: RequestInit, middlewareOptions?: MiddlewareOption[]): Promise { const finalOptions = {...this.defaultRequestSettings, ...options} as RequestInit; if(this.middlewares.length > 0 && this.middlewares[0]) - return this.middlewares[0].execute(url, finalOptions); + return this.middlewares[0].execute(url, finalOptions, middlewareOptions); else throw new Error("No middlewares found"); } @@ -46,7 +47,7 @@ export class HttpClient { /** Default middleware executing a request. Internal use only. */ class FetchMiddleware implements Middleware { next: Middleware | undefined; - execute(url: string, req: RequestInit): Promise { + public execute(url: string, req: RequestInit, _?: MiddlewareOption[]): Promise { return fetch(url, req); } } \ No newline at end of file diff --git a/http/typescript/fetch/src/middleware.ts b/http/typescript/fetch/src/middleware.ts index af8f18ccdb..abb6ab84ee 100644 --- a/http/typescript/fetch/src/middleware.ts +++ b/http/typescript/fetch/src/middleware.ts @@ -1,3 +1,5 @@ +import { MiddlewareOption } from "@microsoft/kiota-abstractions"; + /** Defines the contract for a middleware in the request execution pipeline. */ export interface Middleware { /** Next middleware to be executed. The current middleware must execute it in its implementation. */ @@ -8,5 +10,5 @@ export interface Middleware { * @param url The URL of the request. * @return A promise that resolves to the response object. */ - execute(url: string, req: RequestInit): Promise; + execute(url: string, req: RequestInit, middlewareOptions?: MiddlewareOption[]): Promise; } \ No newline at end of file From 1d088a600be4e22c8c8523e176b02b76fc21b85a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 4 Aug 2021 09:51:26 -0400 Subject: [PATCH 19/19] - performance fix for handlers chaining for dotnet --- http/dotnet/httpclient/src/HttpClientBuilder.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/http/dotnet/httpclient/src/HttpClientBuilder.cs b/http/dotnet/httpclient/src/HttpClientBuilder.cs index 4a7ff51c4b..ee2f0426b6 100644 --- a/http/dotnet/httpclient/src/HttpClientBuilder.cs +++ b/http/dotnet/httpclient/src/HttpClientBuilder.cs @@ -34,14 +34,15 @@ public static IList CreateDefaultHandlers(IAuthenticationProv /// The created . public static DelegatingHandler ChainHandlersCollectionAndGetFirstLink(params DelegatingHandler[] handlers) { if(handlers == null || !handlers.Any()) return default; - var handlersAsList = handlers.ToList(); - handlersAsList.ForEach(h => { - var previousItemIndex = handlersAsList.IndexOf(h) - 1; + var handlersCount = handlers.Count(); + for(var i = 0; i < handlersCount; i++) { + var handler = handlers[i]; + var previousItemIndex = i - 1; if(previousItemIndex >= 0) { - var previousItem = handlersAsList.ElementAt(previousItemIndex); - previousItem.InnerHandler = h; + var previousHandler = handlers[previousItemIndex]; + previousHandler.InnerHandler = handler; } - }); + } return handlers.First(); } }