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 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/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/dotnet/src/RequestInfo.cs b/abstractions/dotnet/src/RequestInfo.cs index 42c20d2665..e26c814b51 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) 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; + } + /// + /// 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/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/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..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 @@ -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"; /** 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/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"; /** diff --git a/http/dotnet/httpclient/src/HttpClientBuilder.cs b/http/dotnet/httpclient/src/HttpClientBuilder.cs new file mode 100644 index 0000000000..ee2f0426b6 --- /dev/null +++ b/http/dotnet/httpclient/src/HttpClientBuilder.cs @@ -0,0 +1,49 @@ +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 = ChainHandlersCollectionAndGetFirstLink(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 ChainHandlersCollectionAndGetFirstLink(params DelegatingHandler[] handlers) { + if(handlers == null || !handlers.Any()) return default; + var handlersCount = handlers.Count(); + for(var i = 0; i < handlersCount; i++) { + var handler = handlers[i]; + var previousItemIndex = i - 1; + if(previousItemIndex >= 0) { + var previousHandler = handlers[previousItemIndex]; + previousHandler.InnerHandler = handler; + } + } + return handlers.First(); + } + } +} diff --git a/http/dotnet/httpclient/src/HttpCore.cs b/http/dotnet/httpclient/src/HttpCore.cs index db276f4b48..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; @@ -28,7 +29,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; } @@ -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) { 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/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 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" }, diff --git a/http/typescript/fetch/src/httpClient.ts b/http/typescript/fetch/src/httpClient.ts new file mode 100644 index 0000000000..98a59d4a58 --- /dev/null +++ b/http/typescript/fetch/src/httpClient.ts @@ -0,0 +1,53 @@ +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 { + /** + * 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, 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, middlewareOptions); + 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; + 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/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..abb6ab84ee --- /dev/null +++ b/http/typescript/fetch/src/middleware.ts @@ -0,0 +1,14 @@ +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. */ + 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, middlewareOptions?: MiddlewareOption[]): Promise; +} \ No newline at end of file diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index fb2c4fb180..5529fabce1 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -54,6 +54,14 @@ 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 { 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() { @@ -72,6 +80,7 @@ public object Clone() PathSegment = PathSegment?.Clone() as string, SerializerModules = SerializerModules == null ? null : new (SerializerModules), DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules), + OriginalMethod = OriginalMethod }; } diff --git a/src/Kiota.Builder/CodeDOM/CodeParameter.cs b/src/Kiota.Builder/CodeDOM/CodeParameter.cs index 703ee7a679..be3f1480fa 100644 --- a/src/Kiota.Builder/CodeDOM/CodeParameter.cs +++ b/src/Kiota.Builder/CodeDOM/CodeParameter.cs @@ -12,7 +12,9 @@ public enum CodeParameterKind RequestBody, SetterValue, HttpCore, - CurrentPath + CurrentPath, + Options, + Serializer } public class CodeParameter : CodeTerminal, ICloneable, IDocumentedElement diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index b474ebd966..19f1b1e642 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)); @@ -772,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/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 867ec220aa..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); @@ -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"), @@ -99,43 +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) { + } + private static void CorrectMethodType(CodeMethod currentMethod) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("IResponseHandler")).ToList().ForEach(x => x.Type.Name = "ResponseHandler"); - else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("ISerializationWriter")).ToList().ForEach(x => x.Type.Name = "SerializationWriter"); - 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)) - .ToList() - .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" + 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)) { @@ -157,17 +159,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.QueryParameter, CodeParameterKind.Headers))) + .Select(x => GetMethodClone(x, CodeParameterKind.Options, CodeParameterKind.ResponseHandler))) .Union(originalExecutorMethods - .Select(x => GetMethodClone(x, CodeParameterKind.QueryParameter, CodeParameterKind.Headers, CodeParameterKind.ResponseHandler))) + .Select(x => GetMethodClone(x, CodeParameterKind.Headers, CodeParameterKind.Options, CodeParameterKind.ResponseHandler))) + .Union(originalExecutorMethods + .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 +193,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.OriginalMethod = currentMethod; return cloneMethod; } else return null; diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 42ba71d12a..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); @@ -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"), @@ -55,35 +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) { + } + private static void CorrectMethodType(CodeMethod currentMethod) { + if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) { if(currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("IResponseHandler")).ToList().ForEach(x => x.Type.Name = "ResponseHandler"); - else if(currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("ISerializationWriter")).ToList().ForEach(x => x.Type.Name = "SerializationWriter"); - 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" + 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 35adae3f80..01e8166a03 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, new List { 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, 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); @@ -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 = 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);"); } - 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)); diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index b70bb19c7c..ab607349e7 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});"); } @@ -46,11 +47,14 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri case CodeMethodKind.IndexerBackwardCompatibility: WriteIndexerBody(codeElement, writer, returnType); break; - case CodeMethodKind.RequestGenerator: - WriteRequestGeneratorBody(codeElement, requestBodyParam, queryStringParam, headersParam, writer); + 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: - WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, returnType, writer); + WriteRequestExecutorBody(codeElement, requestBodyParam, queryStringParam, headersParam, optionsParam, returnType, writer); break; case CodeMethodKind.Getter: WriteGetterBody(codeElement, writer, parentClass); @@ -161,25 +165,11 @@ 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) - .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 }.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);"); @@ -192,10 +182,31 @@ 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 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()};"); @@ -203,27 +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($"{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(); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 0e5e434c1b..775e8886be 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, new List {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, 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) @@ -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 = parameters.Select(x => x?.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(); diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs index fd102aaede..05d68b8731 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, } @@ -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/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, } 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); }