diff --git a/.vscode/settings.json b/.vscode/settings.json index 601a42d3c4..aa309383b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,6 @@ "cSpell.words": [ "npmrc", "serializers" - ] + ], + "java.configuration.updateBuildConfiguration": "automatic" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 871911466e..da44f7d58b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Modified the TypeScript RequestInformation content data type to ArrayBuffer. - Updated PHP abstractions to make property keys and values nullable in `SerializationWriter.php`. - Fixed an issue where enum collections parsing would fail in Go. +- Breaking. Kiota clients generate error types and throw when the target API returns a failed response (dotnet, go, java, typescript). #1100 ## [0.0.15] - 2021-12-17 diff --git a/abstractions/dotnet/src/ApiException.cs b/abstractions/dotnet/src/ApiException.cs new file mode 100644 index 0000000000..1af85e0e48 --- /dev/null +++ b/abstractions/dotnet/src/ApiException.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ +using System; + +namespace Microsoft.Kiota.Abstractions; + +/// +/// Parent type for exceptions thrown by the client when receiving failed responses to its requests. +/// +public class ApiException : Exception +{ + /// + public ApiException(): base() + { + + } + /// + public ApiException(string message) : base(message) + { + } + /// + public ApiException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/abstractions/dotnet/src/IRequestAdapter.cs b/abstractions/dotnet/src/IRequestAdapter.cs index 61da556f49..b7980f6b10 100644 --- a/abstractions/dotnet/src/IRequestAdapter.cs +++ b/abstractions/dotnet/src/IRequestAdapter.cs @@ -2,6 +2,7 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -29,41 +30,46 @@ public interface IRequestAdapter /// /// The RequestInformation object to use for the HTTP request. /// The response handler to use for the HTTP request instead of the default handler. + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the requests. /// The deserialized response model. - Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) where ModelType : IParsable; + Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable; /// /// Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. /// /// The RequestInformation object to use for the HTTP request. /// The response handler to use for the HTTP request instead of the default handler. + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the requests. /// The deserialized response model collection. - Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) where ModelType : IParsable; + Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable; /// /// Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. /// /// The RequestInformation object to use for the HTTP request. /// The response handler to use for the HTTP request instead of the default handler. + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the requests. /// The deserialized primitive response model. - Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default); + Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default); /// /// Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model collection. /// /// The RequestInformation object to use for the HTTP request. /// The response handler to use for the HTTP request instead of the default handler. + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the requests. /// The deserialized primitive response model collection. - Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default); + Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default); /// /// Executes the HTTP request specified by the given RequestInformation with no return content. /// /// The RequestInformation object to use for the HTTP request. /// The response handler to use for the HTTP request instead of the default handler. + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the requests. /// A Task to await completion. - Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default); + Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default); /// /// The base url for every request. /// diff --git a/abstractions/dotnet/src/IResponseHandler.cs b/abstractions/dotnet/src/IResponseHandler.cs index c2012208b2..e46c3d8c0c 100644 --- a/abstractions/dotnet/src/IResponseHandler.cs +++ b/abstractions/dotnet/src/IResponseHandler.cs @@ -3,6 +3,9 @@ // ------------------------------------------------------------------------------ using System.Threading.Tasks; +using System.Collections.Generic; +using System; +using Microsoft.Kiota.Abstractions.Serialization; namespace Microsoft.Kiota.Abstractions { @@ -15,9 +18,10 @@ public interface IResponseHandler /// Callback method that is invoked when a response is received. /// /// The native response object. + /// The error mappings for the response to use when deserializing failed responses bodies. Where an error code like 401 applies specifically to that status code, a class code like 4XX applies to all status codes within the range if an the specific error code is not present. /// The type of the native response object. /// The type of the response model object. /// A task that represents the asynchronous operation and contains the deserialized response. - Task HandleResponseAsync(NativeResponseType response); + Task HandleResponseAsync(NativeResponseType response, Dictionary> errorMappings); } } diff --git a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj index 104f8a5660..a93b15b929 100644 --- a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj +++ b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj @@ -6,7 +6,7 @@ latest true https://github.com/microsoft/kiota - 1.0.28 + 1.0.29 true true diff --git a/abstractions/dotnet/src/NativeResponseHandler.cs b/abstractions/dotnet/src/NativeResponseHandler.cs index e15cf42191..111e4c2196 100644 --- a/abstractions/dotnet/src/NativeResponseHandler.cs +++ b/abstractions/dotnet/src/NativeResponseHandler.cs @@ -3,6 +3,9 @@ // ------------------------------------------------------------------------------ using System.Threading.Tasks; +using System.Collections.Generic; +using System; +using Microsoft.Kiota.Abstractions.Serialization; namespace Microsoft.Kiota.Abstractions { @@ -17,13 +20,15 @@ public class NativeResponseHandler : IResponseHandler public object Value; /// - /// Handles the response of type and return an instance of + /// The error mappings for the response to use when deserializing failed responses bodies. Where an error code like 401 applies specifically to that status code, a class code like 4XX applies to all status codes within the range if an the specific error code is not present. /// - /// The response to be handled - /// - public Task HandleResponseAsync(NativeResponseType response) + public Dictionary> ErrorMappings { get; set; } + + /// + public Task HandleResponseAsync(NativeResponseType response, Dictionary> errorMappings) { Value = response; + ErrorMappings = errorMappings; return Task.FromResult(default(ModelType)); } } diff --git a/abstractions/dotnet/src/serialization/IParseNode.cs b/abstractions/dotnet/src/serialization/IParseNode.cs index 314c49f8ae..e96a2db892 100644 --- a/abstractions/dotnet/src/serialization/IParseNode.cs +++ b/abstractions/dotnet/src/serialization/IParseNode.cs @@ -99,6 +99,12 @@ public interface IParseNode /// The model object value of the node. T GetObjectValue() where T : IParsable; /// + /// Gets the resulting error from the node. + /// + /// The error object value of the node. + /// The error factory. + IParsable GetErrorValue(Func factory); + /// /// Callback called before the node is deserialized. /// Action OnBeforeAssignFieldValues { get; set; } diff --git a/abstractions/go/api_error.go b/abstractions/go/api_error.go new file mode 100644 index 0000000000..90c3c0fb30 --- /dev/null +++ b/abstractions/go/api_error.go @@ -0,0 +1,21 @@ +package abstractions + +import "fmt" + +// ApiError is the parent type for errors thrown by the client when receiving failed responses to its requests +type ApiError struct { + Message string +} + +func (e *ApiError) Error() string { + if len(e.Message) > 0 { + return fmt.Sprint(e.Message) + } else { + return "error status code received from the API" + } +} + +// NewApiError creates a new ApiError instance +func NewApiError() *ApiError { + return &ApiError{} +} diff --git a/abstractions/go/request_adapter.go b/abstractions/go/request_adapter.go index 4891c81033..7d75e5b799 100644 --- a/abstractions/go/request_adapter.go +++ b/abstractions/go/request_adapter.go @@ -12,18 +12,21 @@ import ( s "github.com/microsoft/kiota/abstractions/go/serialization" ) +// ErrorMappings is a mapping of status codes to error types factories. +type ErrorMappings map[string]s.ParsableFactory + // RequestAdapter is the service responsible for translating abstract RequestInformation into native HTTP requests. type RequestAdapter interface { // SendAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized response model. - SendAsync(requestInfo RequestInformation, constructor func() s.Parsable, responseHandler ResponseHandler) (s.Parsable, error) + SendAsync(requestInfo RequestInformation, constructor s.ParsableFactory, responseHandler ResponseHandler, errorMappings ErrorMappings) (s.Parsable, error) // SendCollectionAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. - SendCollectionAsync(requestInfo RequestInformation, constructor func() s.Parsable, responseHandler ResponseHandler) ([]s.Parsable, error) + SendCollectionAsync(requestInfo RequestInformation, constructor s.ParsableFactory, responseHandler ResponseHandler, errorMappings ErrorMappings) ([]s.Parsable, error) // SendPrimitiveAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. - SendPrimitiveAsync(requestInfo RequestInformation, typeName string, responseHandler ResponseHandler) (interface{}, error) + SendPrimitiveAsync(requestInfo RequestInformation, typeName string, responseHandler ResponseHandler, errorMappings ErrorMappings) (interface{}, error) // SendPrimitiveCollectionAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model collection. - SendPrimitiveCollectionAsync(requestInfo RequestInformation, typeName string, responseHandler ResponseHandler) ([]interface{}, error) + SendPrimitiveCollectionAsync(requestInfo RequestInformation, typeName string, responseHandler ResponseHandler, errorMappings ErrorMappings) ([]interface{}, error) // SendNoContentAsync executes the HTTP request specified by the given RequestInformation with no return content. - SendNoContentAsync(requestInfo RequestInformation, responseHandler ResponseHandler) error + SendNoContentAsync(requestInfo RequestInformation, responseHandler ResponseHandler, errorMappings ErrorMappings) error // GetSerializationWriterFactory returns the serialization writer factory currently in use for the request adapter service. GetSerializationWriterFactory() s.SerializationWriterFactory // EnableBackingStore enables the backing store proxies for the SerializationWriters and ParseNodes in use. diff --git a/abstractions/go/response_handler.go b/abstractions/go/response_handler.go index 7623213ced..7c729219ba 100644 --- a/abstractions/go/response_handler.go +++ b/abstractions/go/response_handler.go @@ -1,4 +1,4 @@ package abstractions // ResponseHandler handler to implement when a request's response should be handled a specific way. -type ResponseHandler func(response interface{}) (interface{}, error) +type ResponseHandler func(response interface{}, errorMappings ErrorMappings) (interface{}, error) diff --git a/abstractions/go/serialization/parsable.go b/abstractions/go/serialization/parsable.go index 320873c453..3a4ca7696f 100644 --- a/abstractions/go/serialization/parsable.go +++ b/abstractions/go/serialization/parsable.go @@ -13,3 +13,6 @@ type Parsable interface { // IsNil returns whether the current object is nil or not. IsNil() bool } + +// ParsableFactory is a factory for creating Parsable. +type ParsableFactory func() Parsable diff --git a/abstractions/java/lib/build.gradle b/abstractions/java/lib/build.gradle index 305581f03c..458ed41c1a 100644 --- a/abstractions/java/lib/build.gradle +++ b/abstractions/java/lib/build.gradle @@ -48,7 +48,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-abstractions' - version '1.0.25' + version '1.0.26' from(components.java) } } diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/ApiException.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/ApiException.java new file mode 100644 index 0000000000..78c0a926b7 --- /dev/null +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/ApiException.java @@ -0,0 +1,21 @@ +package com.microsoft.kiota; + +/** Parent type for exceptions thrown by the client when receiving failed responses to its requests. */ +public class ApiException extends Exception { + /** {@inheritdoc} */ + public ApiException() { + super(); + } + /** {@inheritdoc} */ + public ApiException(String message) { + super(message); + } + /** {@inheritdoc} */ + public ApiException(String message, Throwable cause) { + super(message, cause); + } + /** {@inheritdoc} */ + public ApiException(Throwable cause) { + super(cause); + } +} diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/NativeResponseHandler.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/NativeResponseHandler.java index 366f19b2c1..b370c6b36a 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/NativeResponseHandler.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/NativeResponseHandler.java @@ -1,21 +1,31 @@ package com.microsoft.kiota; +import java.util.HashMap; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.microsoft.kiota.serialization.Parsable; + /** Default response handler to access the native response object. */ public class NativeResponseHandler implements ResponseHandler { /** Native response object as returned by the core service */ @Nullable public Object value; + /** The error mappings for the response to use when deserializing failed responses bodies. Where an error code like 401 applies specifically to that status code, a class code like 4XX applies to all status codes within the range if an the specific error code is not present. */ + @Nullable + public HashMap> errorMappings; + + /** {@inheritdoc} */ @Nonnull @Override public CompletableFuture handleResponseAsync( - NativeResponseType response) { + NativeResponseType response, + HashMap> errorMappings) { this.value = response; + this.errorMappings = errorMappings; return CompletableFuture.completedFuture(null); } diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestAdapter.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestAdapter.java index 62121bb6d2..828387fa94 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestAdapter.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/RequestAdapter.java @@ -1,6 +1,7 @@ package com.microsoft.kiota; import java.util.concurrent.CompletableFuture; +import java.util.HashMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -23,41 +24,45 @@ public interface RequestAdapter { @Nonnull SerializationWriterFactory getSerializationWriterFactory(); /** - * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model. + * Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. * @param targetClass the class of the response model to deserialize the response into. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param the type of the response model to deserialize the response into. * @return a {@link CompletableFuture} with the deserialized response model. */ - CompletableFuture sendAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler); + CompletableFuture sendAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings); /** - * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. + * Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. * @param targetClass the class of the response model to deserialize the response into. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param the type of the response model to deserialize the response into. * @return a {@link CompletableFuture} with the deserialized response model collection. */ - CompletableFuture> sendCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler); + CompletableFuture> sendCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings); /** - * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. + * Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. * @param targetClass the class of the response model to deserialize the response into. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param the type of the response model to deserialize the response into. * @return a {@link CompletableFuture} with the deserialized primitive response model. */ - CompletableFuture sendPrimitiveAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler); + CompletableFuture sendPrimitiveAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings); /** - * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive collection response model. + * Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive collection response model. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. * @param targetClass the class of the response model to deserialize the response into. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param the type of the response model to deserialize the response into. * @return a {@link CompletableFuture} with the deserialized primitive collection response model. */ - CompletableFuture> sendPrimitiveCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler); + CompletableFuture> sendPrimitiveCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings); /** * Sets The base url for every request. * @param baseUrl The base url for every request. diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/ResponseHandler.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/ResponseHandler.java index 9505518093..bcb99d3801 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/ResponseHandler.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/ResponseHandler.java @@ -1,18 +1,23 @@ package com.microsoft.kiota; +import java.util.HashMap; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.microsoft.kiota.serialization.Parsable; /** Defines the contract for a response handler. */ public interface ResponseHandler { /** * Callback method that is invoked when a response is received. * @param response The native response object. + * @param errorMappings the error mappings for the response to use when deserializing failed responses bodies. Where an error code like 401 applies specifically to that status code, a class code like 4XX applies to all status codes within the range if an the specific error code is not present. * @param The type of the native response object. * @param The type of the response model object. * @return A CompletableFuture that represents the asynchronous operation and contains the deserialized response. */ @Nonnull - CompletableFuture handleResponseAsync(@Nonnull final NativeResponseType response); + CompletableFuture handleResponseAsync(@Nonnull final NativeResponseType response, @Nullable final HashMap> errorMappings); } \ No newline at end of file diff --git a/abstractions/typescript/package-lock.json b/abstractions/typescript/package-lock.json index 2081e46ea5..2f1a2633f8 100644 --- a/abstractions/typescript/package-lock.json +++ b/abstractions/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.28", + "version": "1.0.29", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@microsoft/kiota-abstractions", - "version": "1.0.28", + "version": "1.0.29", "license": "MIT", "dependencies": { "tinyduration": "^3.2.2", diff --git a/abstractions/typescript/package.json b/abstractions/typescript/package.json index df0995dadc..7b16aa4878 100644 --- a/abstractions/typescript/package.json +++ b/abstractions/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.28", + "version": "1.0.29", "description": "Core abstractions for kiota generated libraries in TypeScript and JavaScript", "main": "dist/cjs/index.js", "module": "dist/es/index.js", diff --git a/abstractions/typescript/src/apiError.ts b/abstractions/typescript/src/apiError.ts new file mode 100644 index 0000000000..96ae787ffa --- /dev/null +++ b/abstractions/typescript/src/apiError.ts @@ -0,0 +1,7 @@ +/** Parent interface for errors thrown by the client when receiving failed responses to its requests. */ +export class ApiError extends Error { + public constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, ApiError.prototype); + } +} \ No newline at end of file diff --git a/abstractions/typescript/src/index.ts b/abstractions/typescript/src/index.ts index 65f1372969..33e1f86925 100644 --- a/abstractions/typescript/src/index.ts +++ b/abstractions/typescript/src/index.ts @@ -1,4 +1,5 @@ export * from "./apiClientBuilder"; +export * from "./apiError"; export * from "./authentication"; export * from "./dateOnly"; export * from "./duration"; diff --git a/abstractions/typescript/src/nativeResponseHandler.ts b/abstractions/typescript/src/nativeResponseHandler.ts index dda482fe2f..171a3d1fee 100644 --- a/abstractions/typescript/src/nativeResponseHandler.ts +++ b/abstractions/typescript/src/nativeResponseHandler.ts @@ -1,13 +1,18 @@ import { ResponseHandler } from "./responseHandler"; +import { Parsable } from "./serialization"; /** Default response handler to access the native response object. */ export class NativeResponseHandler implements ResponseHandler { /** Native response object as returned by the core service */ public value?: any; + /** The error mappings for the response to use when deserializing failed responses bodies. Where an error code like 401 applies specifically to that status code, a class code like 4XX applies to all status codes within the range if a specific error code is not present. */ + public errorMappings: Record Parsable> | undefined public handleResponseAsync( - response: NativeResponseType + response: NativeResponseType, + errorMappings: Record Parsable> | undefined ): Promise { this.value = response; + this.errorMappings = errorMappings; return Promise.resolve(undefined as any); } } diff --git a/abstractions/typescript/src/requestAdapter.ts b/abstractions/typescript/src/requestAdapter.ts index f90a95eb1e..ec1e452c54 100644 --- a/abstractions/typescript/src/requestAdapter.ts +++ b/abstractions/typescript/src/requestAdapter.ts @@ -14,6 +14,7 @@ export interface RequestAdapter { * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param type the class of the response model to deserialize the response into. * @typeParam ModelType the type of the response model to deserialize the response into. * @return a {@link Promise} with the deserialized response model. @@ -21,12 +22,14 @@ export interface RequestAdapter { sendAsync( requestInfo: RequestInformation, type: new () => ModelType, - responseHandler: ResponseHandler | undefined + responseHandler: ResponseHandler | undefined, + errorMappings: Record Parsable> | undefined ): Promise; /** * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param type the class of the response model to deserialize the response into. * @typeParam ModelType the type of the response model to deserialize the response into. * @return a {@link Promise} with the deserialized response model collection. @@ -34,13 +37,15 @@ export interface RequestAdapter { sendCollectionAsync( requestInfo: RequestInformation, type: new () => ModelType, - responseHandler: ResponseHandler | undefined + responseHandler: ResponseHandler | undefined, + errorMappings: Record Parsable> | undefined ): Promise; /** * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. * @param requestInfo the request info to execute. * @param responseType the class of the response model to deserialize the response into. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param type the class of the response model to deserialize the response into. * @typeParam ResponseType the type of the response model to deserialize the response into. * @return a {@link Promise} with the deserialized response model collection. @@ -48,12 +53,14 @@ export interface RequestAdapter { sendCollectionOfPrimitiveAsync( requestInfo: RequestInformation, responseType: "string" | "number" | "boolean" | "Date", - responseHandler: ResponseHandler | undefined + responseHandler: ResponseHandler | undefined, + errorMappings: Record Parsable> | undefined ): Promise; /** * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param errorMappings the error factories mapping to use in case of a failed request. * @param responseType the class of the response model to deserialize the response into. * @typeParam ResponseType the type of the response model to deserialize the response into. * @return a {@link Promise} with the deserialized primitive response model. @@ -61,17 +68,20 @@ export interface RequestAdapter { sendPrimitiveAsync( requestInfo: RequestInformation, responseType: "string" | "number" | "boolean" | "Date" | "ArrayBuffer", - responseHandler: ResponseHandler | undefined + responseHandler: ResponseHandler | undefined, + errorMappings: Record Parsable> | undefined ): Promise; /** * Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. * @param requestInfo the request info to execute. * @param responseHandler The response handler to use for the HTTP request instead of the default handler. + * @param errorMappings the error factories mapping to use in case of a failed request. * @return a {@link Promise} of void. */ sendNoResponseContentAsync( requestInfo: RequestInformation, - responseHandler: ResponseHandler | undefined + responseHandler: ResponseHandler | undefined, + errorMappings: Record Parsable> | undefined ): Promise; /** * Enables the backing store proxies for the SerializationWriters and ParseNodes in use. diff --git a/abstractions/typescript/src/responseHandler.ts b/abstractions/typescript/src/responseHandler.ts index 84ab703b86..4e39734f79 100644 --- a/abstractions/typescript/src/responseHandler.ts +++ b/abstractions/typescript/src/responseHandler.ts @@ -1,13 +1,17 @@ +import { Parsable } from "./serialization"; + /** Defines the contract for a response handler. */ export interface ResponseHandler { /** * Callback method that is invoked when a response is received. * @param response The native response object. + * @param errorMappings the error factories mapping to use in case of a failed request. * @typeParam NativeResponseType The type of the native response object. * @typeParam ModelType The type of the response model object. * @return A {@link Promise} that represents the asynchronous operation and contains the deserialized response. */ handleResponseAsync( - response: NativeResponseType + response: NativeResponseType, + errorMappings: Record Parsable> | undefined ): Promise; } diff --git a/docs/extending/kiotaabstractions.md b/docs/extending/kiotaabstractions.md index 187afe0bea..ebd694aa49 100644 --- a/docs/extending/kiotaabstractions.md +++ b/docs/extending/kiotaabstractions.md @@ -23,19 +23,23 @@ public interface IRequestAdapter Task SendAsync( RequestInformation requestInfo, - IResponseHandler responseHandler = default) where ModelType : IParsable; + IResponseHandler responseHandler = default, + Dictionary> errorMappings = default) where ModelType : IParsable; Task> SendCollectionAsync( RequestInformation requestInfo, - IResponseHandler responseHandler = default) where ModelType : IParsable; + IResponseHandler responseHandler = default, + Dictionary> errorMappings = default) where ModelType : IParsable; Task SendPrimitiveAsync( RequestInformation requestInfo, - IResponseHandler responseHandler = default); + IResponseHandler responseHandler = default, + Dictionary> errorMappings = default); Task SendNoContentAsync( RequestInformation requestInfo, - IResponseHandler responseHandler = default); + IResponseHandler responseHandler = default, + Dictionary> errorMappings = default); } ``` @@ -69,10 +73,23 @@ When passed to the execution method from the fluent style API, this allows core ```csharp public interface IResponseHandler { - Task HandleResponseAsync(NativeResponseType response); + Task HandleResponseAsync(NativeResponseType response, Dictionary> errorMappings); } ``` +### Failed responses handling + +A Kiota API client will handle failed http responses (status code ∈ [400, 600[) as an exception/error. If error types are described for the operation, Kiota will generate those and attempt to deserialize a failed response to an instance of the corresponding error type with the following sequence: + +1. If the response is successful, deserialize it to the expected model, otherwise move to the next step. +1. If an error factory is registered for the corresponding code (e.g. 403), deserialize to that type and throw, otherwise move to the next step. +1. If an error factory is registered for the error class (e.g. 4XX or 5XX), deserialize to that type and throw, otherwise move to the next step. +1. Throw the generic **ApiException** type defined in the abstractions. + +Additionally all generated error types inherit from the **ApiException** type defined in the abstractions to enable cross cutting implementations and returning an error when no error types are defined for an operation. This type inherits itself from **Exception** (or the native error type on the platform). + +> Note: if a response handler is passed, the error detection logic is bypassed to allow the caller to implement whichever custom handling they desire. + ## Serialization This section provides information about the types offered in the abstractions library the generated result depends on which are related to serializing and deserializing payloads. diff --git a/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs b/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs index 95a82600cb..f648c90846 100644 --- a/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs +++ b/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs @@ -58,67 +58,74 @@ public ISerializationWriterFactory SerializationWriterFactory /// /// The instance to send /// The to use with the response + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the request. - public async Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) where ModelType : IParsable + public async Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable { var response = await GetHttpResponseMessage(requestInfo, cancellationToken); requestInfo.Content?.Dispose(); if(responseHandler == null) { + await ThrowFailedResponse(response, errorMapping); var rootNode = await GetRootParseNode(response); var result = rootNode.GetCollectionOfObjectValues(); return result; } else - return await responseHandler.HandleResponseAsync>(response); + return await responseHandler.HandleResponseAsync>(response, errorMapping); } /// /// Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model collection. /// /// The RequestInformation object to use for the HTTP request. /// The response handler to use for the HTTP request instead of the default handler. + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the request. /// The deserialized primitive response model collection. - public async Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) { + public async Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) { var response = await GetHttpResponseMessage(requestInfo, cancellationToken); requestInfo.Content?.Dispose(); if(responseHandler == null) { + await ThrowFailedResponse(response, errorMapping); var rootNode = await GetRootParseNode(response); var result = rootNode.GetCollectionOfPrimitiveValues(); return result; } else - return await responseHandler.HandleResponseAsync>(response); + return await responseHandler.HandleResponseAsync>(response, errorMapping); } /// /// Send a instance with an instance of /// /// The instance to send /// The to use with the response + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the request. /// The deserialized response model. - public async Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, CancellationToken cancellationToken = default) where ModelType : IParsable + public async Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable { var response = await GetHttpResponseMessage(requestInfo, cancellationToken); requestInfo.Content?.Dispose(); if(responseHandler == null) { + await ThrowFailedResponse(response, errorMapping); var rootNode = await GetRootParseNode(response); var result = rootNode.GetObjectValue(); return result; } else - return await responseHandler.HandleResponseAsync(response); + return await responseHandler.HandleResponseAsync(response, errorMapping); } /// /// Send a instance with a primitive instance of /// /// The instance to send /// The to use with the response + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the request. /// The deserialized primitive response model. - public async Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) + public async Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) { var response = await GetHttpResponseMessage(requestInfo, cancellationToken); requestInfo.Content?.Dispose(); @@ -131,6 +138,7 @@ public async Task SendPrimitiveAsync(RequestInformation re } else { + await ThrowFailedResponse(response, errorMapping); var rootNode = await GetRootParseNode(response); object result; if(modelType == typeof(bool)) @@ -166,23 +174,44 @@ public async Task SendPrimitiveAsync(RequestInformation re } } else - return await responseHandler.HandleResponseAsync(response); + return await responseHandler.HandleResponseAsync(response, errorMapping); } /// /// Send a instance with an empty request body /// /// The instance to send /// The to use with the response + /// The error factories mapping to use in case of a failed request. /// The to use for cancelling the request. /// - public async Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, CancellationToken cancellationToken = default) + public async Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) { var response = await GetHttpResponseMessage(requestInfo, cancellationToken); + await ThrowFailedResponse(response, errorMapping); requestInfo.Content?.Dispose(); if(responseHandler == null) response.Dispose(); else - await responseHandler.HandleResponseAsync(response); + await responseHandler.HandleResponseAsync(response, errorMapping); + } + private async Task ThrowFailedResponse(HttpResponseMessage response, Dictionary> errorMapping) + { + if(response.IsSuccessStatusCode) return; + + var statusCodeAsInt = (int)response.StatusCode; + var statusCodeAsString = statusCodeAsInt.ToString(); + Func errorFactory; + if(errorMapping == null || + !errorMapping.TryGetValue(statusCodeAsString, out errorFactory) && + !(statusCodeAsInt >= 400 && statusCodeAsInt < 500 && errorMapping.TryGetValue("4XX", out errorFactory)) && + !(statusCodeAsInt >= 500 && statusCodeAsInt < 600 && errorMapping.TryGetValue("5XX", out errorFactory))) + throw new ApiException($"The server returned an unexpected status code and no error factory is registered for this code: {statusCodeAsString}"); + + var rootNode = await GetRootParseNode(response); + var result = rootNode.GetErrorValue(errorFactory); + if(result is not Exception ex) + throw new ApiException($"The server returned an unexpected status code and the error registered for this code failed to deserialize: {statusCodeAsString}"); + else throw ex; } private async Task GetRootParseNode(HttpResponseMessage response) { diff --git a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj index d7693d5a5f..1b88fb2621 100644 --- a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj +++ b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj @@ -6,7 +6,7 @@ latest true https://github.com/microsoft/kiota - 1.0.19 + 1.0.20 true @@ -14,7 +14,7 @@ - + diff --git a/http/go/nethttp/nethttp_request_adapter.go b/http/go/nethttp/nethttp_request_adapter.go index 1936e8a7a1..cb6c731258 100644 --- a/http/go/nethttp/nethttp_request_adapter.go +++ b/http/go/nethttp/nethttp_request_adapter.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "io/ioutil" + "strconv" "strings" ctx "context" @@ -138,23 +139,23 @@ func (a *NetHttpRequestAdapter) getRequestFromRequestInformation(requestInfo abs } // SendAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized response model. -func (a *NetHttpRequestAdapter) SendAsync(requestInfo abs.RequestInformation, constructor func() absser.Parsable, responseHandler abs.ResponseHandler) (absser.Parsable, error) { +func (a *NetHttpRequestAdapter) SendAsync(requestInfo abs.RequestInformation, constructor absser.ParsableFactory, responseHandler abs.ResponseHandler, errorMappings abs.ErrorMappings) (absser.Parsable, error) { response, err := a.getHttpResponseMessage(requestInfo) if err != nil { return nil, err } if responseHandler != nil { - result, err := responseHandler(response) + result, err := responseHandler(response, errorMappings) if err != nil { return nil, err } return result.(absser.Parsable), nil } else if response != nil { - body, err := ioutil.ReadAll(response.Body) + err = a.throwFailedResponses(response, errorMappings) if err != nil { return nil, err } - parseNode, err := a.parseNodeFactory.GetRootParseNode(a.getResponsePrimaryContentType(response), body) + parseNode, err := a.getRootParseNode(response) if err != nil { return nil, err } @@ -166,23 +167,23 @@ func (a *NetHttpRequestAdapter) SendAsync(requestInfo abs.RequestInformation, co } // SendCollectionAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. -func (a *NetHttpRequestAdapter) SendCollectionAsync(requestInfo abs.RequestInformation, constructor func() absser.Parsable, responseHandler abs.ResponseHandler) ([]absser.Parsable, error) { +func (a *NetHttpRequestAdapter) SendCollectionAsync(requestInfo abs.RequestInformation, constructor absser.ParsableFactory, responseHandler abs.ResponseHandler, errorMappings abs.ErrorMappings) ([]absser.Parsable, error) { response, err := a.getHttpResponseMessage(requestInfo) if err != nil { return nil, err } if responseHandler != nil { - result, err := responseHandler(response) + result, err := responseHandler(response, errorMappings) if err != nil { return nil, err } return result.([]absser.Parsable), nil } else if response != nil { - body, err := ioutil.ReadAll(response.Body) + err = a.throwFailedResponses(response, errorMappings) if err != nil { return nil, err } - parseNode, err := a.parseNodeFactory.GetRootParseNode(a.getResponsePrimaryContentType(response), body) + parseNode, err := a.getRootParseNode(response) if err != nil { return nil, err } @@ -194,23 +195,26 @@ func (a *NetHttpRequestAdapter) SendCollectionAsync(requestInfo abs.RequestInfor } // SendPrimitiveAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. -func (a *NetHttpRequestAdapter) SendPrimitiveAsync(requestInfo abs.RequestInformation, typeName string, responseHandler abs.ResponseHandler) (interface{}, error) { +func (a *NetHttpRequestAdapter) SendPrimitiveAsync(requestInfo abs.RequestInformation, typeName string, responseHandler abs.ResponseHandler, errorMappings abs.ErrorMappings) (interface{}, error) { response, err := a.getHttpResponseMessage(requestInfo) if err != nil { return nil, err } if responseHandler != nil { - result, err := responseHandler(response) + result, err := responseHandler(response, errorMappings) if err != nil { return nil, err } return result.(absser.Parsable), nil } else if response != nil { - body, err := ioutil.ReadAll(response.Body) + err = a.throwFailedResponses(response, errorMappings) if err != nil { return nil, err } - parseNode, err := a.parseNodeFactory.GetRootParseNode(a.getResponsePrimaryContentType(response), body) + if typeName == "[]byte" { + return ioutil.ReadAll(response.Body) + } + parseNode, err := a.getRootParseNode(response) if err != nil { return nil, err } @@ -240,23 +244,23 @@ func (a *NetHttpRequestAdapter) SendPrimitiveAsync(requestInfo abs.RequestInform } // SendPrimitiveCollectionAsync executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model collection. -func (a *NetHttpRequestAdapter) SendPrimitiveCollectionAsync(requestInfo abs.RequestInformation, typeName string, responseHandler abs.ResponseHandler) ([]interface{}, error) { +func (a *NetHttpRequestAdapter) SendPrimitiveCollectionAsync(requestInfo abs.RequestInformation, typeName string, responseHandler abs.ResponseHandler, errorMappings abs.ErrorMappings) ([]interface{}, error) { response, err := a.getHttpResponseMessage(requestInfo) if err != nil { return nil, err } if responseHandler != nil { - result, err := responseHandler(response) + result, err := responseHandler(response, errorMappings) if err != nil { return nil, err } return result.([]interface{}), nil } else if response != nil { - body, err := ioutil.ReadAll(response.Body) + err = a.throwFailedResponses(response, errorMappings) if err != nil { return nil, err } - parseNode, err := a.parseNodeFactory.GetRootParseNode(a.getResponsePrimaryContentType(response), body) + parseNode, err := a.getRootParseNode(response) if err != nil { return nil, err } @@ -267,13 +271,13 @@ func (a *NetHttpRequestAdapter) SendPrimitiveCollectionAsync(requestInfo abs.Req } // SendNoContentAsync executes the HTTP request specified by the given RequestInformation with no return content. -func (a *NetHttpRequestAdapter) SendNoContentAsync(requestInfo abs.RequestInformation, responseHandler abs.ResponseHandler) error { +func (a *NetHttpRequestAdapter) SendNoContentAsync(requestInfo abs.RequestInformation, responseHandler abs.ResponseHandler, errorMappings abs.ErrorMappings) error { response, err := a.getHttpResponseMessage(requestInfo) if err != nil { return err } if responseHandler != nil { - _, err := responseHandler(response) + _, err := responseHandler(response, errorMappings) return err } else if response != nil { return nil @@ -281,3 +285,47 @@ func (a *NetHttpRequestAdapter) SendNoContentAsync(requestInfo abs.RequestInform return errors.New("response is nil") } } + +func (a *NetHttpRequestAdapter) getRootParseNode(response *nethttp.Response) (absser.ParseNode, error) { + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + return a.parseNodeFactory.GetRootParseNode(a.getResponsePrimaryContentType(response), body) +} + +func (a *NetHttpRequestAdapter) throwFailedResponses(response *nethttp.Response, errorMappings abs.ErrorMappings) error { + if response.StatusCode < 400 { + return nil + } + + statusAsString := strconv.Itoa(response.StatusCode) + var errorCtor absser.ParsableFactory = nil + if len(errorMappings) != 0 { + if errorMappings[statusAsString] != nil { + errorCtor = errorMappings[statusAsString] + } else if response.StatusCode >= 400 && response.StatusCode < 500 && errorMappings["4XX"] != nil { + errorCtor = errorMappings["4XX"] + } else if response.StatusCode >= 500 && response.StatusCode < 600 && errorMappings["5XX"] != nil { + errorCtor = errorMappings["5XX"] + } + } + + if errorCtor == nil { + return &abs.ApiError{ + Message: "The server returned an unexpected status code and no error factory is registered for this code: " + statusAsString, + } + } + + rootNode, err := a.getRootParseNode(response) + if err != nil { + return err + } + + errValue, err := rootNode.GetObjectValue(errorCtor) + if err != nil { + return err + } + + return errValue.(error) +} diff --git a/http/java/okhttp/lib/build.gradle b/http/java/okhttp/lib/build.gradle index 4ce4a9fa50..0a6590d943 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:31.0.1-jre' api 'com.squareup.okhttp3:okhttp:4.9.3' - api 'com.microsoft.kiota:kiota-abstractions:1.0.23' + api 'com.microsoft.kiota:kiota-abstractions:1.0.26' } publishing { @@ -53,7 +53,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-http-okhttplibrary' - version '1.0.13' + version '1.0.14' from(components.java) } } diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 0444635ba1..c91373631e 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -5,6 +5,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.time.OffsetDateTime; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; @@ -15,6 +16,7 @@ import javax.annotation.Nullable; import com.microsoft.kiota.ApiClientBuilder; +import com.microsoft.kiota.ApiException; import com.microsoft.kiota.RequestInformation; import com.microsoft.kiota.RequestOption; import com.microsoft.kiota.ResponseHandler; @@ -91,48 +93,50 @@ public void enableBackingStore(@Nullable final BackingStoreFactory backingStoreF } } @Nonnull - public CompletableFuture> sendCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { + public CompletableFuture> sendCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); - return this.getHttpResponseMessage(requestInfo).thenCompose(response -> { + return this.getHttpResponseMessage(requestInfo) + .thenCompose(response -> { if(responseHandler == null) { - final ResponseBody body = response.body(); try { - try (final InputStream rawInputStream = body.byteStream()) { - final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); - final Iterable result = rootNode.getCollectionOfObjectValues(targetClass); - return CompletableFuture.completedStage(result); - } + this.throwFailedResponse(response, errorMappings); + final ParseNode rootNode = getRootParseNode(response); + final Iterable result = rootNode.getCollectionOfObjectValues(targetClass); + return CompletableFuture.completedStage(result); + } catch(ApiException ex) { + return CompletableFuture.failedFuture(ex); } catch(IOException ex) { return CompletableFuture.failedFuture(new RuntimeException("failed to read the response body", ex)); } finally { response.close(); } } else { - return responseHandler.handleResponseAsync(response); + return responseHandler.handleResponseAsync(response, errorMappings); } }); } @Nonnull - public CompletableFuture sendAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { + public CompletableFuture sendAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); - return this.getHttpResponseMessage(requestInfo).thenCompose(response -> { + return this.getHttpResponseMessage(requestInfo) + .thenCompose(response -> { if(responseHandler == null) { - final ResponseBody body = response.body(); try { - try (final InputStream rawInputStream = body.byteStream()) { - final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); - final ModelType result = rootNode.getObjectValue(targetClass); - return CompletableFuture.completedStage(result); - } + this.throwFailedResponse(response, errorMappings); + final ParseNode rootNode = getRootParseNode(response); + final ModelType result = rootNode.getObjectValue(targetClass); + return CompletableFuture.completedStage(result); + } catch(ApiException ex) { + return CompletableFuture.failedFuture(ex); } catch(IOException ex) { return CompletableFuture.failedFuture(new RuntimeException("failed to read the response body", ex)); } finally { response.close(); } } else { - return responseHandler.handleResponseAsync(response); + return responseHandler.handleResponseAsync(response, errorMappings); } }); } @@ -140,20 +144,21 @@ private String getMediaTypeAndSubType(final MediaType mediaType) { return mediaType.type() + "/" + mediaType.subtype(); } @Nonnull - public CompletableFuture sendPrimitiveAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { - return this.getHttpResponseMessage(requestInfo).thenCompose(response -> { + public CompletableFuture sendPrimitiveAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings) { + return this.getHttpResponseMessage(requestInfo) + .thenCompose(response -> { if(responseHandler == null) { - final ResponseBody body = response.body(); try { + this.throwFailedResponse(response, errorMappings); if(targetClass == Void.class) { return CompletableFuture.completedStage(null); } else { - final InputStream rawInputStream = body.byteStream(); if(targetClass == InputStream.class) { + final ResponseBody body = response.body(); + final InputStream rawInputStream = body.byteStream(); return CompletableFuture.completedStage((ModelType)rawInputStream); } - final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); - rawInputStream.close(); + final ParseNode rootNode = getRootParseNode(response); Object result; if(targetClass == Boolean.class) { result = rootNode.getBooleanValue(); @@ -174,38 +179,76 @@ public CompletableFuture sendPrimitiveAsync(@Nonnull fina } return CompletableFuture.completedStage((ModelType)result); } + } catch(ApiException ex) { + return CompletableFuture.failedFuture(ex); } catch(IOException ex) { return CompletableFuture.failedFuture(new RuntimeException("failed to read the response body", ex)); } finally { response.close(); } } else { - return responseHandler.handleResponseAsync(response); + return responseHandler.handleResponseAsync(response, errorMappings); } }); } - public CompletableFuture> sendPrimitiveCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { + public CompletableFuture> sendPrimitiveCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); - return this.getHttpResponseMessage(requestInfo).thenCompose(response -> { + return this.getHttpResponseMessage(requestInfo) + .thenCompose(response -> { if(responseHandler == null) { - final ResponseBody body = response.body(); try { - try (final InputStream rawInputStream = body.byteStream()) { - final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); - final Iterable result = rootNode.getCollectionOfPrimitiveValues(targetClass); - return CompletableFuture.completedStage(result); - } + this.throwFailedResponse(response, errorMappings); + final ParseNode rootNode = getRootParseNode(response); + final Iterable result = rootNode.getCollectionOfPrimitiveValues(targetClass); + return CompletableFuture.completedStage(result); + } catch(ApiException ex) { + return CompletableFuture.failedFuture(ex); } catch(IOException ex) { return CompletableFuture.failedFuture(new RuntimeException("failed to read the response body", ex)); } finally { response.close(); } } else { - return responseHandler.handleResponseAsync(response); + return responseHandler.handleResponseAsync(response, errorMappings); } }); } + private ParseNode getRootParseNode(final Response response) throws IOException { + final ResponseBody body = response.body(); + try (final InputStream rawInputStream = body.byteStream()) { + final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); + return rootNode; + } + } + private Response throwFailedResponse(final Response response, final HashMap> errorMappings) throws IOException, ApiException { + if (response.isSuccessful()) return response; + + final String statusCodeAsString = Integer.toString(response.code()); + final Integer statusCode = response.code(); + if (errorMappings == null || + !errorMappings.containsKey(statusCodeAsString) && + !(statusCode >= 400 && statusCode < 500 && errorMappings.containsKey("4XX")) && + !(statusCode >= 500 && statusCode < 600 && errorMappings.containsKey("5XX"))) { + throw new ApiException("the server returned an unexpected status code and no error class is registered for this code " + statusCode); + } + final Class errorClass = errorMappings.containsKey(statusCodeAsString) ? + errorMappings.get(statusCodeAsString) : + (statusCode >= 400 && statusCode < 500 ? + errorMappings.get("4XX") : + errorMappings.get("5XX")); + try { + final ParseNode rootNode = getRootParseNode(response); + final Parsable error = rootNode.getObjectValue(errorClass); + if (error instanceof ApiException) { + throw (ApiException)error; + } else { + throw new ApiException("unexpected error type " + error.getClass().getName()); + } + } finally { + response.close(); + } + } private CompletableFuture getHttpResponseMessage(@Nonnull final RequestInformation requestInfo) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); this.setBaseUrlForRequestInformation(requestInfo); diff --git a/http/typescript/fetch/package-lock.json b/http/typescript/fetch/package-lock.json index 395d78efe2..4da4498345 100644 --- a/http/typescript/fetch/package-lock.json +++ b/http/typescript/fetch/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/kiota-http-fetchlibrary", - "version": "1.0.15", + "version": "1.0.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@microsoft/kiota-http-fetchlibrary", - "version": "1.0.15", + "version": "1.0.16", "license": "MIT", "dependencies": { "@microsoft/kiota-abstractions": "^1.0.27", diff --git a/http/typescript/fetch/package.json b/http/typescript/fetch/package.json index 10d786bd7d..fac76880bd 100644 --- a/http/typescript/fetch/package.json +++ b/http/typescript/fetch/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-http-fetchlibrary", - "version": "1.0.15", + "version": "1.0.16", "description": "Kiota request adapter implementation with fetch", "main": "dist/cjs/index.js", "module": "dist/es/index.js", @@ -34,7 +34,7 @@ "registry": "https://npm.pkg.github.com" }, "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.27", + "@microsoft/kiota-abstractions": "^1.0.29", "cross-fetch": "^3.1.5", "tslib": "^2.3.1" }, diff --git a/http/typescript/fetch/src/fetchRequestAdapter.ts b/http/typescript/fetch/src/fetchRequestAdapter.ts index 1fb8ecec95..9daabcc2fa 100644 --- a/http/typescript/fetch/src/fetchRequestAdapter.ts +++ b/http/typescript/fetch/src/fetchRequestAdapter.ts @@ -1,4 +1,4 @@ -import { AuthenticationProvider, BackingStoreFactory, BackingStoreFactorySingleton, RequestAdapter, Parsable, ParseNodeFactory, RequestInformation, ResponseHandler, ParseNodeFactoryRegistry, enableBackingStoreForParseNodeFactory, SerializationWriterFactoryRegistry, enableBackingStoreForSerializationWriterFactory, SerializationWriterFactory } from '@microsoft/kiota-abstractions'; +import { ApiError, AuthenticationProvider, BackingStoreFactory, BackingStoreFactorySingleton, RequestAdapter, Parsable, ParseNodeFactory, RequestInformation, ResponseHandler, ParseNodeFactoryRegistry, enableBackingStoreForParseNodeFactory, SerializationWriterFactoryRegistry, enableBackingStoreForSerializationWriterFactory, SerializationWriterFactory, ParseNode } from '@microsoft/kiota-abstractions'; import { HttpClient } from './httpClient'; export class FetchRequestAdapter implements RequestAdapter { @@ -35,25 +35,21 @@ export class FetchRequestAdapter implements RequestAdapter { if (segments.length === 0) return undefined; else return segments[0]; } - public sendCollectionOfPrimitiveAsync = async (requestInfo: RequestInformation, responseType: "string" | "number" | "boolean" | "Date", responseHandler: ResponseHandler | undefined): Promise => { + public sendCollectionOfPrimitiveAsync = async (requestInfo: RequestInformation, responseType: "string" | "number" | "boolean" | "Date", responseHandler: ResponseHandler | undefined, errorMappings: Record Parsable> | undefined): Promise => { if (!requestInfo) { throw new Error('requestInfo cannot be null'); } const response = await this.getHttpResponseMessage(requestInfo); if (responseHandler) { - return await responseHandler.handleResponseAsync(response); + return await responseHandler.handleResponseAsync(response, errorMappings); } else { + await this.throwFailedResponses(response, errorMappings); switch (responseType) { case 'string': case 'number': case 'boolean': case 'Date': - const payload = await response.arrayBuffer(); - const responseContentType = this.getResponseContentType(response); - if (!responseContentType) - throw new Error("no response content type found for deserialization"); - - const rootNode = this.parseNodeFactory.getRootParseNode(responseContentType, payload); + const rootNode = await this.getRootParseNode(response); if (responseType === 'string') { return rootNode.getCollectionOfPrimitiveValues() as unknown as ResponseType[]; } else if (responseType === 'number') { @@ -68,50 +64,43 @@ export class FetchRequestAdapter implements RequestAdapter { } } } - public sendCollectionAsync = async (requestInfo: RequestInformation, type: new () => ModelType, responseHandler: ResponseHandler | undefined): Promise => { + public sendCollectionAsync = async (requestInfo: RequestInformation, type: new () => ModelType, responseHandler: ResponseHandler | undefined, errorMappings: Record Parsable> | undefined): Promise => { if (!requestInfo) { throw new Error('requestInfo cannot be null'); } const response = await this.getHttpResponseMessage(requestInfo); if (responseHandler) { - return await responseHandler.handleResponseAsync(response); + return await responseHandler.handleResponseAsync(response, errorMappings); } else { - const payload = await response.arrayBuffer(); - const responseContentType = this.getResponseContentType(response); - if (!responseContentType) - throw new Error("no response content type found for deserialization"); - - const rootNode = this.parseNodeFactory.getRootParseNode(responseContentType, payload); + await this.throwFailedResponses(response, errorMappings); + const rootNode = await this.getRootParseNode(response); const result = rootNode.getCollectionOfObjectValues(type); return result as unknown as ModelType[]; } } - public sendAsync = async (requestInfo: RequestInformation, type: new () => ModelType, responseHandler: ResponseHandler | undefined): Promise => { + public sendAsync = async (requestInfo: RequestInformation, type: new () => ModelType, responseHandler: ResponseHandler | undefined, errorMappings: Record Parsable> | undefined): Promise => { if (!requestInfo) { throw new Error('requestInfo cannot be null'); } const response = await this.getHttpResponseMessage(requestInfo); if (responseHandler) { - return await responseHandler.handleResponseAsync(response); + return await responseHandler.handleResponseAsync(response, errorMappings); } else { - const payload = await response.arrayBuffer(); - const responseContentType = this.getResponseContentType(response); - if (!responseContentType) - throw new Error("no response content type found for deserialization"); - - const rootNode = this.parseNodeFactory.getRootParseNode(responseContentType, payload); + await this.throwFailedResponses(response, errorMappings); + const rootNode = await this.getRootParseNode(response); const result = rootNode.getObjectValue(type); return result as unknown as ModelType; } } - public sendPrimitiveAsync = async (requestInfo: RequestInformation, responseType: "string" | "number" | "boolean" | "Date" | "ArrayBuffer", responseHandler: ResponseHandler | undefined): Promise => { + public sendPrimitiveAsync = async (requestInfo: RequestInformation, responseType: "string" | "number" | "boolean" | "Date" | "ArrayBuffer", responseHandler: ResponseHandler | undefined, errorMappings: Record Parsable> | undefined): Promise => { if (!requestInfo) { throw new Error('requestInfo cannot be null'); } const response = await this.getHttpResponseMessage(requestInfo); if (responseHandler) { - return await responseHandler.handleResponseAsync(response); + return await responseHandler.handleResponseAsync(response, errorMappings); } else { + await this.throwFailedResponses(response, errorMappings); switch (responseType) { case "ArrayBuffer": return await response.arrayBuffer() as unknown as ResponseType; @@ -119,12 +108,7 @@ export class FetchRequestAdapter implements RequestAdapter { case 'number': case 'boolean': case 'Date': - const payload = await response.arrayBuffer(); - const responseContentType = this.getResponseContentType(response); - if (!responseContentType) - throw new Error("no response content type found for deserialization"); - - const rootNode = this.parseNodeFactory.getRootParseNode(responseContentType, payload); + const rootNode = await this.getRootParseNode(response); if (responseType === 'string') { return rootNode.getStringValue() as unknown as ResponseType; } else if (responseType === 'number') { @@ -139,14 +123,15 @@ export class FetchRequestAdapter implements RequestAdapter { } } } - public sendNoResponseContentAsync = async (requestInfo: RequestInformation, responseHandler: ResponseHandler | undefined): Promise => { + public sendNoResponseContentAsync = async (requestInfo: RequestInformation, responseHandler: ResponseHandler | undefined, errorMappings: Record Parsable> | undefined): Promise => { if (!requestInfo) { throw new Error('requestInfo cannot be null'); } const response = await this.getHttpResponseMessage(requestInfo); if (responseHandler) { - return await responseHandler.handleResponseAsync(response); + return await responseHandler.handleResponseAsync(response, errorMappings); } + await this.throwFailedResponses(response, errorMappings); } public enableBackingStore = (backingStoreFactory?: BackingStoreFactory | undefined): void => { this.parseNodeFactory = enableBackingStoreForParseNodeFactory(this.parseNodeFactory); @@ -157,6 +142,35 @@ export class FetchRequestAdapter implements RequestAdapter { BackingStoreFactorySingleton.instance = backingStoreFactory; } } + private getRootParseNode = async (response: Response) : Promise => { + const payload = await response.arrayBuffer(); + const responseContentType = this.getResponseContentType(response); + if (!responseContentType) + throw new Error("no response content type found for deserialization"); + + return this.parseNodeFactory.getRootParseNode(responseContentType, payload); + } + private throwFailedResponses = async (response: Response, errorMappings: Record Parsable> | undefined): Promise => { + if(response.ok) return; + + const statusCode = response.status; + const statusCodeAsString = statusCode.toString(); + if(!errorMappings || + !errorMappings[statusCodeAsString] && + !(statusCode >= 400 && statusCode < 500 && errorMappings['4XX']) && + !(statusCode >= 500 && statusCode < 600 && errorMappings['5XX'])) + throw new ApiError("the server returned an unexpected status code and no error class is registered for this code " + statusCode); + + const factory = errorMappings[statusCodeAsString] ?? + (statusCode >= 400 && statusCode < 500 ? errorMappings['4XX'] : undefined) ?? + (statusCode >= 500 && statusCode < 600 ? errorMappings['5XX'] : undefined); + + const rootNode = await this.getRootParseNode(response); + const error = rootNode.getObjectValue(factory); + + if(error) throw error; + else throw new ApiError("unexpected error type" + typeof(error)) + } private getHttpResponseMessage = async (requestInfo: RequestInformation): Promise => { if (!requestInfo) { throw new Error('requestInfo cannot be null'); diff --git a/serialization/dotnet/json/src/JsonParseNode.cs b/serialization/dotnet/json/src/JsonParseNode.cs index ec44a7228c..378196da94 100644 --- a/serialization/dotnet/json/src/JsonParseNode.cs +++ b/serialization/dotnet/json/src/JsonParseNode.cs @@ -256,12 +256,30 @@ public IEnumerable GetCollectionOfPrimitiveValues() public T GetObjectValue() where T : IParsable { var item = (T)(typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { })); + return GetObjectValueInternal(item); + } + private T GetObjectValueInternal(T item) where T : IParsable + { var fieldDeserializers = item.GetFieldDeserializers(); OnBeforeAssignFieldValues?.Invoke(item); AssignFieldValues(item, fieldDeserializers); OnAfterAssignFieldValues?.Invoke(item); return item; } + /// + /// Gets the resulting error from the node. + /// + /// The error object value of the node. + /// The error factory. + public IParsable GetErrorValue(Func factory) + { + if (factory == null) throw new ArgumentNullException(nameof(factory)); + + var instance = factory.Invoke(); + if (instance == null) throw new InvalidOperationException("factory returned null"); + + return GetObjectValueInternal(instance); + } private void AssignFieldValues(T item, IDictionary> fieldDeserializers) where T : IParsable { if(_jsonNode.ValueKind != JsonValueKind.Object) return; diff --git a/serialization/java/json/lib/.classpath b/serialization/java/json/lib/.classpath index 5933c3f21a..ae30f860ac 100644 --- a/serialization/java/json/lib/.classpath +++ b/serialization/java/json/lib/.classpath @@ -13,7 +13,7 @@ - + diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs index 9b9ed2a998..3d553f9581 100644 --- a/src/Kiota.Builder/CodeDOM/CodeClass.cs +++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs @@ -2,128 +2,129 @@ using System.Collections.Generic; using System.Linq; -namespace Kiota.Builder +namespace Kiota.Builder; + +public enum CodeClassKind { + Custom, + RequestBuilder, + Model, + QueryParameters, + /// + /// A single parameter to be provided by the SDK user which will contain query parameters, request body, options, etc. + /// Only used for languages that do not support overloads or optional parameters like go. + /// + ParameterSet, +} +/// +/// CodeClass represents an instance of a Class to be generated in source code +/// +public class CodeClass : CodeBlock, IDocumentedElement, ITypeDefinition { - public enum CodeClassKind { - Custom, - RequestBuilder, - Model, - QueryParameters, - /// - /// A single parameter to be provided by the SDK user which will contain query parameters, request body, options, etc. - /// Only used for languages that do not support overloads or optional parameters like go. - /// - ParameterSet, + private string name; + public CodeClass():base() + { + StartBlock = new Declaration() { Parent = this}; + EndBlock = new End() { Parent = this }; } + public CodeClassKind ClassKind { get; set; } = CodeClassKind.Custom; + + public bool IsErrorDefinition { get; set; } + + public string Description {get; set;} /// - /// CodeClass represents an instance of a Class to be generated in source code + /// Name of Class /// - public class CodeClass : CodeBlock, IDocumentedElement, ITypeDefinition + public override string Name { - private string name; - public CodeClass():base() + get => name; + set { - StartBlock = new Declaration() { Parent = this}; - EndBlock = new End() { Parent = this }; - } - public CodeClassKind ClassKind { get; set; } = CodeClassKind.Custom; - - public string Description {get; set;} - /// - /// Name of Class - /// - public override string Name - { - get => name; - set - { - name = value; - StartBlock.Name = name; - } + name = value; + StartBlock.Name = name; } + } - public void SetIndexer(CodeIndexer indexer) - { - if(indexer == null) - throw new ArgumentNullException(nameof(indexer)); - if(InnerChildElements.Values.OfType().Any()) - throw new InvalidOperationException("this class already has an indexer, remove it first"); - AddRange(indexer); - } + public void SetIndexer(CodeIndexer indexer) + { + if(indexer == null) + throw new ArgumentNullException(nameof(indexer)); + if(InnerChildElements.Values.OfType().Any()) + throw new InvalidOperationException("this class already has an indexer, remove it first"); + AddRange(indexer); + } - public IEnumerable AddProperty(params CodeProperty[] properties) - { - if(properties == null || properties.Any(x => x == null)) - throw new ArgumentNullException(nameof(properties)); - if(!properties.Any()) - throw new ArgumentOutOfRangeException(nameof(properties)); - return AddRange(properties); - } - public CodeProperty GetPropertyOfKind(CodePropertyKind kind) => - Properties.FirstOrDefault(x => x.IsOfKind(kind)); - public IEnumerable Properties => InnerChildElements.Values.OfType(); - public IEnumerable Methods => InnerChildElements.Values.OfType(); + public IEnumerable AddProperty(params CodeProperty[] properties) + { + if(properties == null || properties.Any(x => x == null)) + throw new ArgumentNullException(nameof(properties)); + if(!properties.Any()) + throw new ArgumentOutOfRangeException(nameof(properties)); + return AddRange(properties); + } + public CodeProperty GetPropertyOfKind(CodePropertyKind kind) => + Properties.FirstOrDefault(x => x.IsOfKind(kind)); + public IEnumerable Properties => InnerChildElements.Values.OfType(); + public IEnumerable Methods => InnerChildElements.Values.OfType(); - public bool ContainsMember(string name) - { - return InnerChildElements.ContainsKey(name); - } + public bool ContainsMember(string name) + { + return InnerChildElements.ContainsKey(name); + } - public IEnumerable AddMethod(params CodeMethod[] methods) - { - if(methods == null || methods.Any(x => x == null)) - throw new ArgumentNullException(nameof(methods)); - if(!methods.Any()) - throw new ArgumentOutOfRangeException(nameof(methods)); - return AddRange(methods); - } + public IEnumerable AddMethod(params CodeMethod[] methods) + { + if(methods == null || methods.Any(x => x == null)) + throw new ArgumentNullException(nameof(methods)); + if(!methods.Any()) + throw new ArgumentOutOfRangeException(nameof(methods)); + return AddRange(methods); + } - public IEnumerable AddInnerClass(params CodeClass[] codeClasses) - { - if(codeClasses == null || codeClasses.Any(x => x == null)) - throw new ArgumentNullException(nameof(codeClasses)); - if(!codeClasses.Any()) - throw new ArgumentOutOfRangeException(nameof(codeClasses)); - return AddRange(codeClasses); - } - public CodeClass GetParentClass() { - if(StartBlock is Declaration declaration) - return declaration.Inherits?.TypeDefinition as CodeClass; - else return null; - } - - public CodeClass GetGreatestGrandparent(CodeClass startClassToSkip = null) { - var parentClass = GetParentClass(); - if(parentClass == null) - return startClassToSkip != null && startClassToSkip == this ? null : this; - // we don't want to return the current class if this is the start node in the inheritance tree and doesn't have parent - else - return parentClass.GetGreatestGrandparent(startClassToSkip); - } + public IEnumerable AddInnerClass(params CodeClass[] codeClasses) + { + if(codeClasses == null || codeClasses.Any(x => x == null)) + throw new ArgumentNullException(nameof(codeClasses)); + if(!codeClasses.Any()) + throw new ArgumentOutOfRangeException(nameof(codeClasses)); + return AddRange(codeClasses); + } + public CodeClass GetParentClass() { + if(StartBlock is Declaration declaration) + return declaration.Inherits?.TypeDefinition as CodeClass; + else return null; + } + + public CodeClass GetGreatestGrandparent(CodeClass startClassToSkip = null) { + var parentClass = GetParentClass(); + if(parentClass == null) + return startClassToSkip != null && startClassToSkip == this ? null : this; + // we don't want to return the current class if this is the start node in the inheritance tree and doesn't have parent + else + return parentClass.GetGreatestGrandparent(startClassToSkip); + } - public bool IsOfKind(params CodeClassKind[] kinds) { - return kinds?.Contains(ClassKind) ?? false; - } + public bool IsOfKind(params CodeClassKind[] kinds) { + return kinds?.Contains(ClassKind) ?? false; + } public class Declaration : BlockDeclaration - { - private CodeType inherits; - public CodeType Inherits { get => inherits; set { - EnsureElementsAreChildren(value); - inherits = value; - } } - private readonly List implements = new (); - public void AddImplements(params CodeType[] types) { - if(types == null || types.Any(x => x == null)) - throw new ArgumentNullException(nameof(types)); - EnsureElementsAreChildren(types); - implements.AddRange(types); - } - public IEnumerable Implements => implements; + { + private CodeType inherits; + public CodeType Inherits { get => inherits; set { + EnsureElementsAreChildren(value); + inherits = value; + } } + private readonly List implements = new (); + public void AddImplements(params CodeType[] types) { + if(types == null || types.Any(x => x == null)) + throw new ArgumentNullException(nameof(types)); + EnsureElementsAreChildren(types); + implements.AddRange(types); } + public IEnumerable Implements => implements; + } - public class End : BlockEnd - { - } + public class End : BlockEnd + { } } diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index a281a7fb5d..048b437b30 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -2,122 +2,126 @@ using System.Collections.Generic; using System.Linq; -namespace Kiota.Builder +namespace Kiota.Builder; + +public enum CodeMethodKind { - public enum CodeMethodKind - { - Custom, - IndexerBackwardCompatibility, - RequestExecutor, - RequestGenerator, - Serializer, - Deserializer, - Constructor, - Getter, - Setter, - ClientConstructor, - RequestBuilderBackwardCompatibility, - RequestBuilderWithParameters, - RawUrlConstructor, - NullCheck, + Custom, + IndexerBackwardCompatibility, + RequestExecutor, + RequestGenerator, + Serializer, + Deserializer, + Constructor, + Getter, + Setter, + ClientConstructor, + RequestBuilderBackwardCompatibility, + RequestBuilderWithParameters, + RawUrlConstructor, + NullCheck, +} +public enum HttpMethod { + Get, + Post, + Patch, + Put, + Delete, + Options, + Connect, + Head, + Trace +} + +public class CodeMethod : CodeTerminal, ICloneable, IDocumentedElement +{ + public HttpMethod? HttpMethod {get;set;} + public CodeMethodKind MethodKind {get;set;} = CodeMethodKind.Custom; + public string ContentType { get; set; } + public AccessModifier Access {get;set;} = AccessModifier.Public; + private CodeTypeBase returnType; + public CodeTypeBase ReturnType {get => returnType;set { + EnsureElementsAreChildren(value); + returnType = value; + }} + private readonly List parameters = new (); + public void RemoveParametersByKind(params CodeParameterKind[] kinds) { + parameters.RemoveAll(p => p.IsOfKind(kinds)); } - public enum HttpMethod { - Get, - Post, - Patch, - Put, - Delete, - Options, - Connect, - Head, - Trace + public IEnumerable Parameters { get => parameters; } + public bool IsStatic {get;set;} = false; + public bool IsAsync {get;set;} = true; + public string Description {get; set;} + /// + /// The property this method accesses to when it's a getter or setter. + /// + public CodeProperty AccessedProperty { get; set; } + public bool IsOfKind(params CodeMethodKind[] kinds) { + return kinds?.Contains(MethodKind) ?? false; } + public bool IsAccessor { + get => IsOfKind(CodeMethodKind.Getter, CodeMethodKind.Setter); + } + public bool IsSerializationMethod { + get => IsOfKind(CodeMethodKind.Serializer, CodeMethodKind.Deserializer); + } + 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; } + /// + /// The original indexer codedom element this method replaces when it is of kind IndexerBackwardCompatibility. + /// + public CodeIndexer OriginalIndexer { get; set; } + /// + /// The base url for every request read from the servers property on the description. + /// Only provided for constructor on Api client + /// + public string BaseUrl { get; set; } + /// + /// Mapping of the error code and response types for this method. + /// + public Dictionary ErrorMappings { get; set; } = new (); - public class CodeMethod : CodeTerminal, ICloneable, IDocumentedElement + public object Clone() { - public HttpMethod? HttpMethod {get;set;} - public CodeMethodKind MethodKind {get;set;} = CodeMethodKind.Custom; - public string ContentType { get; set; } - public AccessModifier Access {get;set;} = AccessModifier.Public; - private CodeTypeBase returnType; - public CodeTypeBase ReturnType {get => returnType;set { - EnsureElementsAreChildren(value); - returnType = value; - }} - private readonly List parameters = new (); - public void RemoveParametersByKind(params CodeParameterKind[] kinds) { - parameters.RemoveAll(p => p.IsOfKind(kinds)); - } - public IEnumerable Parameters { get => parameters; } - public bool IsStatic {get;set;} = false; - public bool IsAsync {get;set;} = true; - public string Description {get; set;} - /// - /// The property this method accesses to when it's a getter or setter. - /// - public CodeProperty AccessedProperty { get; set; } - public bool IsOfKind(params CodeMethodKind[] kinds) { - return kinds?.Contains(MethodKind) ?? false; - } - public bool IsAccessor { - get => IsOfKind(CodeMethodKind.Getter, CodeMethodKind.Setter); - } - public bool IsSerializationMethod { - get => IsOfKind(CodeMethodKind.Serializer, CodeMethodKind.Deserializer); - } - 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; } - /// - /// The original indexer codedom element this method replaces when it is of kind IndexerBackwardCompatibility. - /// - public CodeIndexer OriginalIndexer { get; set; } - /// - /// The base url for every request read from the servers property on the description. - /// Only provided for constructor on Api client - /// - public string BaseUrl { get; set; } - - public object Clone() - { - var method = new CodeMethod { - MethodKind = MethodKind, - ReturnType = ReturnType?.Clone() as CodeTypeBase, - Name = Name.Clone() as string, - HttpMethod = HttpMethod, - IsAsync = IsAsync, - Access = Access, - IsStatic = IsStatic, - Description = Description?.Clone() as string, - ContentType = ContentType?.Clone() as string, - BaseUrl = BaseUrl?.Clone() as string, - AccessedProperty = AccessedProperty, - SerializerModules = SerializerModules == null ? null : new (SerializerModules), - DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules), - OriginalMethod = OriginalMethod, - Parent = Parent, - OriginalIndexer = OriginalIndexer, - }; - if(Parameters?.Any() ?? false) - method.AddParameter(Parameters.Select(x => x.Clone() as CodeParameter).ToArray()); - return method; - } + var method = new CodeMethod { + MethodKind = MethodKind, + ReturnType = ReturnType?.Clone() as CodeTypeBase, + Name = Name.Clone() as string, + HttpMethod = HttpMethod, + IsAsync = IsAsync, + Access = Access, + IsStatic = IsStatic, + Description = Description?.Clone() as string, + ContentType = ContentType?.Clone() as string, + BaseUrl = BaseUrl?.Clone() as string, + AccessedProperty = AccessedProperty, + SerializerModules = SerializerModules == null ? null : new (SerializerModules), + DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules), + OriginalMethod = OriginalMethod, + Parent = Parent, + OriginalIndexer = OriginalIndexer, + ErrorMappings = ErrorMappings == null ? null : new (ErrorMappings) + }; + if(Parameters?.Any() ?? false) + method.AddParameter(Parameters.Select(x => x.Clone() as CodeParameter).ToArray()); + return method; + } - public void AddParameter(params CodeParameter[] methodParameters) - { - if(methodParameters == null || methodParameters.Any(x => x == null)) - throw new ArgumentNullException(nameof(methodParameters)); - if(!methodParameters.Any()) - throw new ArgumentOutOfRangeException(nameof(methodParameters)); - EnsureElementsAreChildren(methodParameters); - parameters.AddRange(methodParameters); - } + public void AddParameter(params CodeParameter[] methodParameters) + { + if(methodParameters == null || methodParameters.Any(x => x == null)) + throw new ArgumentNullException(nameof(methodParameters)); + if(!methodParameters.Any()) + throw new ArgumentOutOfRangeException(nameof(methodParameters)); + EnsureElementsAreChildren(methodParameters); + parameters.AddRange(methodParameters); } } diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 81daafa970..6de35a4e79 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -17,5 +17,15 @@ public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation) return schemas.FirstOrDefault(); } + public static OpenApiSchema GetResponseSchema(this OpenApiResponse response) + { + // For the moment assume application/json + var schemas = response.Content + .Where(c => c.Key == "application/json") + .Select(co => co.Value.Schema) + .Where(s => s is not null); + + return schemas.FirstOrDefault(); + } } } diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index 54e7f92667..929d30beb9 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -62,8 +62,10 @@ public static IEnumerable GetPathParametersForCurrentSegment(t /// /// Returns the class name for the node with more or less precision depending on the provided arguments /// - public static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) { - var rawClassName = (operation?.GetResponseSchema()?.Reference?.GetClassName() ?? + public static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default, OpenApiResponse response = default, OpenApiSchema schema = default) { + var rawClassName = (schema?.Reference?.GetClassName() ?? + response?.GetResponseSchema()?.Reference?.GetClassName() ?? + operation?.GetResponseSchema()?.Reference?.GetClassName() ?? CleanupParametersFromPath(currentNode.Segment)?.ReplaceValueIdentifier()) .TrimEnd(requestParametersEndChar) .TrimStart(requestParametersChar) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 24cc559418..79a7a73bdc 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -15,955 +15,975 @@ using System.Security; using Microsoft.OpenApi.Services; -namespace Kiota.Builder +namespace Kiota.Builder; + +public class KiotaBuilder { - public class KiotaBuilder + private readonly ILogger logger; + private readonly GenerationConfiguration config; + + public KiotaBuilder(ILogger logger, GenerationConfiguration config) { - private readonly ILogger logger; - private readonly GenerationConfiguration config; + this.logger = logger; + this.config = config; + } - public KiotaBuilder(ILogger logger, GenerationConfiguration config) - { - this.logger = logger; - this.config = config; - } + public async Task GenerateSDK() + { + var sw = new Stopwatch(); + // Step 1 - Read input stream + string inputPath = config.OpenAPIFilePath; + + try { + // doing this verification at the begining to give immediate feedback to the user + Directory.CreateDirectory(config.OutputPath); + } catch (Exception ex) { + logger.LogError($"Could not open/create output directory {config.OutputPath}, reason: {ex.Message}"); + return; + } + + sw.Start(); + using var input = await LoadStream(inputPath); + if(input == null) + return; + StopLogAndReset(sw, "step 1 - reading the stream - took"); + + // Step 2 - Parse OpenAPI + sw.Start(); + var doc = CreateOpenApiDocument(input); + StopLogAndReset(sw, "step 2 - parsing the document - took"); + + SetApiRootUrl(doc); + + // Step 3 - Create Uri Space of API + sw.Start(); + var openApiTree = CreateUriSpace(doc); + StopLogAndReset(sw, "step 3 - create uri space - took"); + + // Step 4 - Create Source Model + sw.Start(); + var generatedCode = CreateSourceModel(openApiTree); + StopLogAndReset(sw, "step 4 - create source model - took"); + + // Step 5 - RefineByLanguage + sw.Start(); + ApplyLanguageRefinement(config, generatedCode); + StopLogAndReset(sw, "step 5 - refine by language - took"); + + // Step 6 - Write language source + sw.Start(); + await CreateLanguageSourceFilesAsync(config.Language, generatedCode); + StopLogAndReset(sw, "step 6 - writing files - took"); + } + private void SetApiRootUrl(OpenApiDocument doc) { + config.ApiRootUrl = doc.Servers.FirstOrDefault()?.Url.TrimEnd('/'); + if(string.IsNullOrEmpty(config.ApiRootUrl)) + throw new InvalidOperationException("A servers entry (v3) or host + basePath + schems properties (v2) must be present in the OpenAPI description."); + } + private void StopLogAndReset(Stopwatch sw, string prefix) { + sw.Stop(); + logger.LogDebug($"{prefix} {sw.Elapsed}"); + sw.Reset(); + } - public async Task GenerateSDK() - { - var sw = new Stopwatch(); - // Step 1 - Read input stream - string inputPath = config.OpenAPIFilePath; + private async Task LoadStream(string inputPath) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + Stream input; + if (inputPath.StartsWith("http")) try { - // doing this verification at the begining to give immediate feedback to the user - Directory.CreateDirectory(config.OutputPath); - } catch (Exception ex) { - logger.LogError($"Could not open/create output directory {config.OutputPath}, reason: {ex.Message}"); - return; + using var httpClient = new HttpClient(); + input = await httpClient.GetStreamAsync(inputPath); + } catch (HttpRequestException ex) { + logger.LogError($"Could not download the file at {inputPath}, reason: {ex.Message}"); + return null; } - - sw.Start(); - using var input = await LoadStream(inputPath); - if(input == null) - return; - StopLogAndReset(sw, "step 1 - reading the stream - took"); - - // Step 2 - Parse OpenAPI - sw.Start(); - var doc = CreateOpenApiDocument(input); - StopLogAndReset(sw, "step 2 - parsing the document - took"); - - SetApiRootUrl(doc); - - // Step 3 - Create Uri Space of API - sw.Start(); - var openApiTree = CreateUriSpace(doc); - StopLogAndReset(sw, "step 3 - create uri space - took"); - - // Step 4 - Create Source Model - sw.Start(); - var generatedCode = CreateSourceModel(openApiTree); - StopLogAndReset(sw, "step 4 - create source model - took"); - - // Step 5 - RefineByLanguage - sw.Start(); - ApplyLanguageRefinement(config, generatedCode); - StopLogAndReset(sw, "step 5 - refine by language - took"); - - // Step 6 - Write language source - sw.Start(); - await CreateLanguageSourceFilesAsync(config.Language, generatedCode); - StopLogAndReset(sw, "step 6 - writing files - took"); - } - private void SetApiRootUrl(OpenApiDocument doc) { - config.ApiRootUrl = doc.Servers.FirstOrDefault()?.Url.TrimEnd('/'); - if(string.IsNullOrEmpty(config.ApiRootUrl)) - throw new InvalidOperationException("A servers entry (v3) or host + basePath + schems properties (v2) must be present in the OpenAPI description."); - } - private void StopLogAndReset(Stopwatch sw, string prefix) { - sw.Stop(); - logger.LogDebug($"{prefix} {sw.Elapsed}"); - sw.Reset(); - } + else + try { + input = new FileStream(inputPath, FileMode.Open); + } catch (Exception ex) when (ex is FileNotFoundException || + ex is PathTooLongException || + ex is DirectoryNotFoundException || + ex is IOException || + ex is UnauthorizedAccessException || + ex is SecurityException || + ex is NotSupportedException) { + logger.LogError($"Could not open the file at {inputPath}, reason: {ex.Message}"); + return null; + } + stopwatch.Stop(); + logger.LogTrace("{timestamp}ms: Read OpenAPI file {file}", stopwatch.ElapsedMilliseconds, inputPath); + return input; + } - private async Task LoadStream(string inputPath) + public OpenApiDocument CreateOpenApiDocument(Stream input) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + logger.LogTrace("Parsing OpenAPI file"); + var reader = new OpenApiStreamReader(); + var doc = reader.Read(input, out var diag); + stopwatch.Stop(); + if (diag.Errors.Count > 0) { - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - Stream input; - if (inputPath.StartsWith("http")) - try { - using var httpClient = new HttpClient(); - input = await httpClient.GetStreamAsync(inputPath); - } catch (HttpRequestException ex) { - logger.LogError($"Could not download the file at {inputPath}, reason: {ex.Message}"); - return null; - } - else - try { - input = new FileStream(inputPath, FileMode.Open); - } catch (Exception ex) when (ex is FileNotFoundException || - ex is PathTooLongException || - ex is DirectoryNotFoundException || - ex is IOException || - ex is UnauthorizedAccessException || - ex is SecurityException || - ex is NotSupportedException) { - logger.LogError($"Could not open the file at {inputPath}, reason: {ex.Message}"); - return null; - } - stopwatch.Stop(); - logger.LogTrace("{timestamp}ms: Read OpenAPI file {file}", stopwatch.ElapsedMilliseconds, inputPath); - return input; + logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, diag.Errors.Select(e => e.Message))}"); } - - - public OpenApiDocument CreateOpenApiDocument(Stream input) + else { - var stopwatch = new Stopwatch(); - stopwatch.Start(); - logger.LogTrace("Parsing OpenAPI file"); - var reader = new OpenApiStreamReader(); - var doc = reader.Read(input, out var diag); - stopwatch.Stop(); - if (diag.Errors.Count > 0) - { - logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, diag.Errors.Select(e => e.Message))}"); - } - else - { - logger.LogTrace("{timestamp}ms: Parsed OpenAPI successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, doc.Paths.Count); - } - - return doc; + logger.LogTrace("{timestamp}ms: Parsed OpenAPI successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, doc.Paths.Count); } - /// - /// Translate OpenApi PathItems into a tree structure that will define the classes - /// - /// OpenAPI Document of the API to be processed - /// Root node of the API URI space - public OpenApiUrlTreeNode CreateUriSpace(OpenApiDocument doc) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); - var node = OpenApiUrlTreeNode.Create(doc, Constants.DefaultOpenApiLabel); - stopwatch.Stop(); - logger.LogTrace("{timestamp}ms: Created UriSpace tree", stopwatch.ElapsedMilliseconds); - return node; - } - private CodeNamespace rootNamespace; - private CodeNamespace modelsNamespace; - - /// - /// Convert UriSpace of OpenApiPathItems into conceptual SDK Code model - /// - /// Root OpenApiUriSpaceNode of API to be generated - /// - public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode root) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); + return doc; + } - rootNamespace = CodeNamespace.InitRootNamespace(); - var codeNamespace = rootNamespace.AddNamespace(config.ClientNamespaceName); - modelsNamespace = rootNamespace.AddNamespace($"{codeNamespace.Name}.models"); - CreateRequestBuilderClass(codeNamespace, root, root); - StopLogAndReset(stopwatch, $"{nameof(CreateRequestBuilderClass)}"); - stopwatch.Start(); - MapTypeDefinitions(codeNamespace); - StopLogAndReset(stopwatch, $"{nameof(MapTypeDefinitions)}"); + /// + /// Translate OpenApi PathItems into a tree structure that will define the classes + /// + /// OpenAPI Document of the API to be processed + /// Root node of the API URI space + public OpenApiUrlTreeNode CreateUriSpace(OpenApiDocument doc) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + var node = OpenApiUrlTreeNode.Create(doc, Constants.DefaultOpenApiLabel); + stopwatch.Stop(); + logger.LogTrace("{timestamp}ms: Created UriSpace tree", stopwatch.ElapsedMilliseconds); + return node; + } + private CodeNamespace rootNamespace; + private CodeNamespace modelsNamespace; + + /// + /// Convert UriSpace of OpenApiPathItems into conceptual SDK Code model + /// + /// Root OpenApiUriSpaceNode of API to be generated + /// + public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode root) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); - logger.LogTrace("{timestamp}ms: Created source model with {count} classes", stopwatch.ElapsedMilliseconds, codeNamespace.GetChildElements(true).Count()); + rootNamespace = CodeNamespace.InitRootNamespace(); + var codeNamespace = rootNamespace.AddNamespace(config.ClientNamespaceName); + modelsNamespace = rootNamespace.AddNamespace($"{codeNamespace.Name}.models"); + CreateRequestBuilderClass(codeNamespace, root, root); + StopLogAndReset(stopwatch, $"{nameof(CreateRequestBuilderClass)}"); + stopwatch.Start(); + MapTypeDefinitions(codeNamespace); + StopLogAndReset(stopwatch, $"{nameof(MapTypeDefinitions)}"); - return rootNamespace; - } + logger.LogTrace("{timestamp}ms: Created source model with {count} classes", stopwatch.ElapsedMilliseconds, codeNamespace.GetChildElements(true).Count()); - /// - /// Manipulate CodeDOM for language specific issues - /// - /// - /// - public void ApplyLanguageRefinement(GenerationConfiguration config, CodeNamespace generatedCode) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); + return rootNamespace; + } + + /// + /// Manipulate CodeDOM for language specific issues + /// + /// + /// + public void ApplyLanguageRefinement(GenerationConfiguration config, CodeNamespace generatedCode) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); - ILanguageRefiner.Refine(config, generatedCode); + ILanguageRefiner.Refine(config, generatedCode); - stopwatch.Stop(); - logger.LogDebug("{timestamp}ms: Language refinement applied", stopwatch.ElapsedMilliseconds); - } + stopwatch.Stop(); + logger.LogDebug("{timestamp}ms: Language refinement applied", stopwatch.ElapsedMilliseconds); + } - /// - /// Iterate through Url Space and create request builder classes for each node in the tree - /// - /// Root node of URI space from the OpenAPI described API - /// A CodeNamespace object that contains request builder classes for the Uri Space + /// + /// Iterate through Url Space and create request builder classes for each node in the tree + /// + /// Root node of URI space from the OpenAPI described API + /// A CodeNamespace object that contains request builder classes for the Uri Space - public async Task CreateLanguageSourceFilesAsync(GenerationLanguage language, CodeNamespace generatedCode) - { - var languageWriter = LanguageWriter.GetLanguageWriter(language, this.config.OutputPath, this.config.ClientNamespaceName); - var stopwatch = new Stopwatch(); - stopwatch.Start(); - await new CodeRenderer(config).RenderCodeNamespaceToFilePerClassAsync(languageWriter, generatedCode); - stopwatch.Stop(); - logger.LogTrace("{timestamp}ms: Files written to {path}", stopwatch.ElapsedMilliseconds, config.OutputPath); - } - private static readonly string requestBuilderSuffix = "RequestBuilder"; - private static readonly string voidType = "void"; - private static readonly string coreInterfaceType = "IRequestAdapter"; - private static readonly string requestAdapterParameterName = "requestAdapter"; - private static readonly string constructorMethodName = "constructor"; - /// - /// Create a CodeClass instance that is a request builder class for the OpenApiUrlTreeNode - /// - private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode rootNode) + public async Task CreateLanguageSourceFilesAsync(GenerationLanguage language, CodeNamespace generatedCode) + { + var languageWriter = LanguageWriter.GetLanguageWriter(language, this.config.OutputPath, this.config.ClientNamespaceName); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + await new CodeRenderer(config).RenderCodeNamespaceToFilePerClassAsync(languageWriter, generatedCode); + stopwatch.Stop(); + logger.LogTrace("{timestamp}ms: Files written to {path}", stopwatch.ElapsedMilliseconds, config.OutputPath); + } + private static readonly string requestBuilderSuffix = "RequestBuilder"; + private static readonly string voidType = "void"; + private static readonly string coreInterfaceType = "IRequestAdapter"; + private static readonly string requestAdapterParameterName = "requestAdapter"; + private static readonly string constructorMethodName = "constructor"; + /// + /// Create a CodeClass instance that is a request builder class for the OpenApiUrlTreeNode + /// + private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode rootNode) + { + // Determine Class Name + CodeClass codeClass; + var isApiClientClass = currentNode == rootNode; + if (isApiClientClass) + codeClass = currentNamespace.AddClass(new CodeClass { + Name = config.ClientClassName, + ClassKind = CodeClassKind.RequestBuilder, + Description = "The main entry point of the SDK, exposes the configuration and the fluent API." + }).First(); + else { - // Determine Class Name - CodeClass codeClass; - var isApiClientClass = currentNode == rootNode; - if (isApiClientClass) - codeClass = currentNamespace.AddClass(new CodeClass { - Name = config.ClientClassName, + var targetNS = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace; + var className = currentNode.GetClassName(requestBuilderSuffix); + codeClass = targetNS.AddClass(new CodeClass { + Name = className, ClassKind = CodeClassKind.RequestBuilder, - Description = "The main entry point of the SDK, exposes the configuration and the fluent API." + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"), }).First(); - else - { - var targetNS = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace; - var className = currentNode.GetClassName(requestBuilderSuffix); - codeClass = targetNS.AddClass(new CodeClass { - Name = className, - ClassKind = CodeClassKind.RequestBuilder, - Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"), - }).First(); - } + } - logger.LogTrace("Creating class {class}", codeClass.Name); + logger.LogTrace("Creating class {class}", codeClass.Name); - // Add properties for children - foreach (var child in currentNode.Children) + // Add properties for children + foreach (var child in currentNode.Children) + { + var propIdentifier = child.Value.GetClassName(); + var propType = propIdentifier + requestBuilderSuffix; + if (child.Value.IsPathSegmentWithSingleSimpleParameter()) { - var propIdentifier = child.Value.GetClassName(); - var propType = propIdentifier + requestBuilderSuffix; - if (child.Value.IsPathSegmentWithSingleSimpleParameter()) - { - var prop = CreateIndexer($"{propIdentifier}-indexer", propType, child.Value, currentNode); - codeClass.SetIndexer(prop); - } - else if (child.Value.IsComplexPathWithAnyNumberOfParameters()) - { - CreateMethod(propIdentifier, propType, codeClass, child.Value); - } - else - { - var prop = CreateProperty(propIdentifier, propType, kind: CodePropertyKind.RequestBuilder); // we should add the type definition here but we can't as it might not have been generated yet - codeClass.AddProperty(prop); - } + var prop = CreateIndexer($"{propIdentifier}-indexer", propType, child.Value, currentNode); + codeClass.SetIndexer(prop); } - - // Add methods for Operations - if (currentNode.HasOperations(Constants.DefaultOpenApiLabel)) + else if (child.Value.IsComplexPathWithAnyNumberOfParameters()) { - foreach(var operation in currentNode - .PathItems[Constants.DefaultOpenApiLabel] - .Operations - .Where(x => x.Value.RequestBody?.Content?.Any(y => !config.IgnoredRequestContentTypes.Contains(y.Key)) ?? true)) - CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass); + CreateMethod(propIdentifier, propType, codeClass, child.Value); } - CreateUrlManagement(codeClass, currentNode, isApiClientClass); - - Parallel.ForEach(currentNode.Children.Values, childNode => + else { - var targetNamespaceName = childNode.GetNodeNamespaceFromPath(config.ClientNamespaceName); - var targetNamespace = rootNamespace.FindNamespaceByName(targetNamespaceName) ?? rootNamespace.AddNamespace(targetNamespaceName); - CreateRequestBuilderClass(targetNamespace, childNode, rootNode); - }); - } - private static void CreateMethod(string propIdentifier, string propType, CodeClass codeClass, OpenApiUrlTreeNode currentNode) - { - var methodToAdd = new CodeMethod { - Name = propIdentifier, - MethodKind = CodeMethodKind.RequestBuilderWithParameters, - Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"), - Access = AccessModifier.Public, - IsAsync = false, - IsStatic = false, - }; - methodToAdd.ReturnType = new CodeType { - Name = propType, - ActionOf = false, - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.None, - IsExternal = false, - IsNullable = false, - }; - AddPathParametersToMethod(currentNode, methodToAdd, false); - codeClass.AddMethod(methodToAdd); - } - private static void AddPathParametersToMethod(OpenApiUrlTreeNode currentNode, CodeMethod methodToAdd, bool asOptional) { - foreach(var parameter in currentNode.GetPathParametersForCurrentSegment()) { - var mParameter = new CodeParameter { - Name = parameter.Name, - Optional = asOptional, - Description = parameter.Description, - ParameterKind = CodeParameterKind.Path, - UrlTemplateParameterName = parameter.Name, - }; - mParameter.Type = GetPrimitiveType(parameter.Schema); - methodToAdd.AddParameter(mParameter); + var prop = CreateProperty(propIdentifier, propType, kind: CodePropertyKind.RequestBuilder); // we should add the type definition here but we can't as it might not have been generated yet + codeClass.AddProperty(prop); } } - private static readonly string PathParametersParameterName = "pathParameters"; - private void CreateUrlManagement(CodeClass currentClass, OpenApiUrlTreeNode currentNode, bool isApiClientClass) { - var pathProperty = new CodeProperty { - Access = AccessModifier.Private, - Name = "urlTemplate", - DefaultValue = $"\"{currentNode.GetUrlTemplate()}\"", - ReadOnly = true, - Description = "Url template to use to build the URL for the current request builder", - PropertyKind = CodePropertyKind.UrlTemplate, - Type = new CodeType { - Name = "string", - IsNullable = false, - IsExternal = true, - }, - }; - currentClass.AddProperty(pathProperty); - var requestAdapterProperty = new CodeProperty { - Name = requestAdapterParameterName, - Description = "The request adapter to use to execute the requests.", - PropertyKind = CodePropertyKind.RequestAdapter, - Access = AccessModifier.Private, - ReadOnly = true, + // Add methods for Operations + if (currentNode.HasOperations(Constants.DefaultOpenApiLabel)) + { + foreach(var operation in currentNode + .PathItems[Constants.DefaultOpenApiLabel] + .Operations + .Where(x => x.Value.RequestBody?.Content?.Any(y => !config.IgnoredRequestContentTypes.Contains(y.Key)) ?? true)) + CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass); + } + CreateUrlManagement(codeClass, currentNode, isApiClientClass); + + Parallel.ForEach(currentNode.Children.Values, childNode => + { + var targetNamespaceName = childNode.GetNodeNamespaceFromPath(config.ClientNamespaceName); + var targetNamespace = rootNamespace.FindNamespaceByName(targetNamespaceName) ?? rootNamespace.AddNamespace(targetNamespaceName); + CreateRequestBuilderClass(targetNamespace, childNode, rootNode); + }); + } + private static void CreateMethod(string propIdentifier, string propType, CodeClass codeClass, OpenApiUrlTreeNode currentNode) + { + var methodToAdd = new CodeMethod { + Name = propIdentifier, + MethodKind = CodeMethodKind.RequestBuilderWithParameters, + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"), + Access = AccessModifier.Public, + IsAsync = false, + IsStatic = false, + }; + methodToAdd.ReturnType = new CodeType { + Name = propType, + ActionOf = false, + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.None, + IsExternal = false, + IsNullable = false, + }; + AddPathParametersToMethod(currentNode, methodToAdd, false); + codeClass.AddMethod(methodToAdd); + } + private static void AddPathParametersToMethod(OpenApiUrlTreeNode currentNode, CodeMethod methodToAdd, bool asOptional) { + foreach(var parameter in currentNode.GetPathParametersForCurrentSegment()) { + var mParameter = new CodeParameter { + Name = parameter.Name, + Optional = asOptional, + Description = parameter.Description, + ParameterKind = CodeParameterKind.Path, + UrlTemplateParameterName = parameter.Name, }; - requestAdapterProperty.Type = new CodeType { - Name = coreInterfaceType, + mParameter.Type = GetPrimitiveType(parameter.Schema); + methodToAdd.AddParameter(mParameter); + } + } + private static readonly string PathParametersParameterName = "pathParameters"; + private void CreateUrlManagement(CodeClass currentClass, OpenApiUrlTreeNode currentNode, bool isApiClientClass) { + var pathProperty = new CodeProperty { + Access = AccessModifier.Private, + Name = "urlTemplate", + DefaultValue = $"\"{currentNode.GetUrlTemplate()}\"", + ReadOnly = true, + Description = "Url template to use to build the URL for the current request builder", + PropertyKind = CodePropertyKind.UrlTemplate, + Type = new CodeType { + Name = "string", + IsNullable = false, + IsExternal = true, + }, + }; + currentClass.AddProperty(pathProperty); + + var requestAdapterProperty = new CodeProperty { + Name = requestAdapterParameterName, + Description = "The request adapter to use to execute the requests.", + PropertyKind = CodePropertyKind.RequestAdapter, + Access = AccessModifier.Private, + ReadOnly = true, + }; + requestAdapterProperty.Type = new CodeType { + Name = coreInterfaceType, + IsExternal = true, + IsNullable = false, + }; + currentClass.AddProperty(requestAdapterProperty); + var constructor = currentClass.AddMethod(new CodeMethod { + Name = constructorMethodName, + MethodKind = isApiClientClass ? CodeMethodKind.ClientConstructor : CodeMethodKind.Constructor, + IsAsync = false, + IsStatic = false, + Description = $"Instantiates a new {currentClass.Name.ToFirstCharacterUpperCase()} and sets the default values.", + Access = AccessModifier.Public, + }).First(); + constructor.ReturnType = new CodeType { Name = voidType, IsExternal = true }; + var pathParametersProperty = new CodeProperty { + Name = PathParametersParameterName, + Description = "Path parameters for the request", + PropertyKind = CodePropertyKind.PathParameters, + Access = AccessModifier.Private, + ReadOnly = true, + Type = new CodeType { + Name = "Dictionary", IsExternal = true, IsNullable = false, - }; - currentClass.AddProperty(requestAdapterProperty); - var constructor = currentClass.AddMethod(new CodeMethod { - Name = constructorMethodName, - MethodKind = isApiClientClass ? CodeMethodKind.ClientConstructor : CodeMethodKind.Constructor, - IsAsync = false, - IsStatic = false, - Description = $"Instantiates a new {currentClass.Name.ToFirstCharacterUpperCase()} and sets the default values.", - Access = AccessModifier.Public, - }).First(); - constructor.ReturnType = new CodeType { Name = voidType, IsExternal = true }; - var pathParametersProperty = new CodeProperty { - Name = PathParametersParameterName, - Description = "Path parameters for the request", - PropertyKind = CodePropertyKind.PathParameters, - Access = AccessModifier.Private, - ReadOnly = true, - Type = new CodeType { - Name = "Dictionary", - IsExternal = true, - IsNullable = false, - }, - }; - currentClass.AddProperty(pathParametersProperty); - if(isApiClientClass) { - constructor.SerializerModules = config.Serializers; - constructor.DeserializerModules = config.Deserializers; - constructor.BaseUrl = config.ApiRootUrl; - pathParametersProperty.DefaultValue = $"new {pathParametersProperty.Type.Name}()"; - } else { - constructor.AddParameter(new CodeParameter { - Name = PathParametersParameterName, - Type = pathParametersProperty.Type, - Optional = false, - Description = pathParametersProperty.Description, - ParameterKind = CodeParameterKind.PathParameters, - }); - AddPathParametersToMethod(currentNode, constructor, true); - } + }, + }; + currentClass.AddProperty(pathParametersProperty); + if(isApiClientClass) { + constructor.SerializerModules = config.Serializers; + constructor.DeserializerModules = config.Deserializers; + constructor.BaseUrl = config.ApiRootUrl; + pathParametersProperty.DefaultValue = $"new {pathParametersProperty.Type.Name}()"; + } else { constructor.AddParameter(new CodeParameter { - Name = requestAdapterParameterName, - Type = requestAdapterProperty.Type, + Name = PathParametersParameterName, + Type = pathParametersProperty.Type, Optional = false, - Description = requestAdapterProperty.Description, - ParameterKind = CodeParameterKind.RequestAdapter, - }); - if(isApiClientClass && config.UsesBackingStore) { - var factoryInterfaceName = $"{BackingStoreInterface}Factory"; - var backingStoreParam = new CodeParameter { - Name = "backingStore", - Optional = true, - Description = "The backing store to use for the models.", - ParameterKind = CodeParameterKind.BackingStore, - Type = new CodeType { - Name = factoryInterfaceName, - IsNullable = true, - } - }; - constructor.AddParameter(backingStoreParam); - } - } - private static readonly Func shortestNamespaceOrder = (x) => x.GetNamespaceDepth(); - /// - /// Remaps definitions to custom types so they can be used later in generation or in refiners - /// - private void MapTypeDefinitions(CodeElement codeElement) { - var unmappedTypes = GetUnmappedTypeDefinitions(codeElement).Distinct(); - - var unmappedTypesWithNoName = unmappedTypes.Where(x => string.IsNullOrEmpty(x.Name)).ToList(); - - unmappedTypesWithNoName.ForEach(x => { - logger.LogWarning($"Type with empty name and parent {x.Parent.Name}"); + Description = pathParametersProperty.Description, + ParameterKind = CodeParameterKind.PathParameters, }); - - var unmappedTypesWithName = unmappedTypes.Except(unmappedTypesWithNoName); - - var unmappedRequestBuilderTypes = unmappedTypesWithName - .Where(x => - x.Parent is CodeProperty property && property.IsOfKind(CodePropertyKind.RequestBuilder) || - x.Parent is CodeIndexer || - x.Parent is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestBuilderWithParameters)) - .ToList(); - - Parallel.ForEach(unmappedRequestBuilderTypes, x => { - var parentNS = x.Parent.Parent.Parent as CodeNamespace; - x.TypeDefinition = parentNS.FindChildrenByName(x.Name) - .OrderBy(shortestNamespaceOrder) - .FirstOrDefault(); - // searching down first because most request builder properties on a request builder are just sub paths on the API - if(x.TypeDefinition == null) { - parentNS = parentNS.Parent as CodeNamespace; - x.TypeDefinition = parentNS - .FindNamespaceByName($"{parentNS.Name}.{x.Name.Substring(0, x.Name.Length - requestBuilderSuffix.Length).ToFirstCharacterLowerCase()}".TrimEnd(nsNameSeparator)) - ?.FindChildrenByName(x.Name) - ?.OrderBy(shortestNamespaceOrder) - ?.FirstOrDefault(); - // in case of the .item namespace, going to the parent and then down to the target by convention - // this avoid getting the wrong request builder in case we have multiple request builders with the same name in the parent branch - // in both cases we always take the uppermost item (smaller numbers of segments in the namespace name) + AddPathParametersToMethod(currentNode, constructor, true); + } + constructor.AddParameter(new CodeParameter { + Name = requestAdapterParameterName, + Type = requestAdapterProperty.Type, + Optional = false, + Description = requestAdapterProperty.Description, + ParameterKind = CodeParameterKind.RequestAdapter, + }); + if(isApiClientClass && config.UsesBackingStore) { + var factoryInterfaceName = $"{BackingStoreInterface}Factory"; + var backingStoreParam = new CodeParameter { + Name = "backingStore", + Optional = true, + Description = "The backing store to use for the models.", + ParameterKind = CodeParameterKind.BackingStore, + Type = new CodeType { + Name = factoryInterfaceName, + IsNullable = true, } - }); - - Parallel.ForEach(unmappedTypesWithName.Where(x => x.TypeDefinition == null).GroupBy(x => x.Name), x => { - if (rootNamespace.FindChildByName(x.First().Name) is CodeElement definition) - foreach (var type in x) - { - type.TypeDefinition = definition; - logger.LogWarning($"Mapped type {type.Name} for {type.Parent.Name} using the fallback approach."); - } - }); - } - private static readonly char nsNameSeparator = '.'; - private static IEnumerable filterUnmappedTypeDefitions(IEnumerable source) => - source.OfType() - .Union(source - .OfType() - .SelectMany(x => x.Types)) - .Where(x => !x.IsExternal && x.TypeDefinition == null); - private IEnumerable GetUnmappedTypeDefinitions(CodeElement codeElement) { - var childElementsUnmappedTypes = codeElement.GetChildElements(true).SelectMany(x => GetUnmappedTypeDefinitions(x)); - return codeElement switch - { - CodeMethod method => filterUnmappedTypeDefitions(method.Parameters.Select(x => x.Type).Union(new CodeTypeBase[] { method.ReturnType })).Union(childElementsUnmappedTypes), - CodeProperty property => filterUnmappedTypeDefitions(new CodeTypeBase[] { property.Type }).Union(childElementsUnmappedTypes), - CodeIndexer indexer => filterUnmappedTypeDefitions(new CodeTypeBase[] { indexer.ReturnType }).Union(childElementsUnmappedTypes), - _ => childElementsUnmappedTypes, }; + constructor.AddParameter(backingStoreParam); } - private CodeIndexer CreateIndexer(string childIdentifier, string childType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode) + } + private static readonly Func shortestNamespaceOrder = (x) => x.GetNamespaceDepth(); + /// + /// Remaps definitions to custom types so they can be used later in generation or in refiners + /// + private void MapTypeDefinitions(CodeElement codeElement) { + var unmappedTypes = GetUnmappedTypeDefinitions(codeElement).Distinct(); + + var unmappedTypesWithNoName = unmappedTypes.Where(x => string.IsNullOrEmpty(x.Name)).ToList(); + + unmappedTypesWithNoName.ForEach(x => { + logger.LogWarning($"Type with empty name and parent {x.Parent.Name}"); + }); + + var unmappedTypesWithName = unmappedTypes.Except(unmappedTypesWithNoName); + + var unmappedRequestBuilderTypes = unmappedTypesWithName + .Where(x => + x.Parent is CodeProperty property && property.IsOfKind(CodePropertyKind.RequestBuilder) || + x.Parent is CodeIndexer || + x.Parent is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestBuilderWithParameters)) + .ToList(); + + Parallel.ForEach(unmappedRequestBuilderTypes, x => { + var parentNS = x.Parent.Parent.Parent as CodeNamespace; + x.TypeDefinition = parentNS.FindChildrenByName(x.Name) + .OrderBy(shortestNamespaceOrder) + .FirstOrDefault(); + // searching down first because most request builder properties on a request builder are just sub paths on the API + if(x.TypeDefinition == null) { + parentNS = parentNS.Parent as CodeNamespace; + x.TypeDefinition = parentNS + .FindNamespaceByName($"{parentNS.Name}.{x.Name.Substring(0, x.Name.Length - requestBuilderSuffix.Length).ToFirstCharacterLowerCase()}".TrimEnd(nsNameSeparator)) + ?.FindChildrenByName(x.Name) + ?.OrderBy(shortestNamespaceOrder) + ?.FirstOrDefault(); + // in case of the .item namespace, going to the parent and then down to the target by convention + // this avoid getting the wrong request builder in case we have multiple request builders with the same name in the parent branch + // in both cases we always take the uppermost item (smaller numbers of segments in the namespace name) + } + }); + + Parallel.ForEach(unmappedTypesWithName.Where(x => x.TypeDefinition == null).GroupBy(x => x.Name), x => { + if (rootNamespace.FindChildByName(x.First().Name) is CodeElement definition) + foreach (var type in x) + { + type.TypeDefinition = definition; + logger.LogWarning($"Mapped type {type.Name} for {type.Parent.Name} using the fallback approach."); + } + }); + } + private static readonly char nsNameSeparator = '.'; + private static IEnumerable filterUnmappedTypeDefitions(IEnumerable source) => + source.OfType() + .Union(source + .OfType() + .SelectMany(x => x.Types)) + .Where(x => !x.IsExternal && x.TypeDefinition == null); + private IEnumerable GetUnmappedTypeDefinitions(CodeElement codeElement) { + var childElementsUnmappedTypes = codeElement.GetChildElements(true).SelectMany(x => GetUnmappedTypeDefinitions(x)); + return codeElement switch { - logger.LogTrace("Creating indexer {name}", childIdentifier); - return new CodeIndexer - { - Name = childIdentifier, - Description = $"Gets an item from the {currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName)} collection", - IndexType = new CodeType { Name = "string", IsExternal = true, }, - ReturnType = new CodeType { Name = childType }, - ParameterName = currentNode.Segment.SanitizeUrlTemplateParameterName().TrimStart('{').TrimEnd('}'), - PathSegment = parentNode.GetNodeNamespaceFromPath(string.Empty).Split('.').Last(), - }; - } + CodeMethod method => filterUnmappedTypeDefitions(method.Parameters.Select(x => x.Type).Union(new CodeTypeBase[] { method.ReturnType })).Union(childElementsUnmappedTypes), + CodeProperty property => filterUnmappedTypeDefitions(new CodeTypeBase[] { property.Type }).Union(childElementsUnmappedTypes), + CodeIndexer indexer => filterUnmappedTypeDefitions(new CodeTypeBase[] { indexer.ReturnType }).Union(childElementsUnmappedTypes), + _ => childElementsUnmappedTypes, + }; + } + private CodeIndexer CreateIndexer(string childIdentifier, string childType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode) + { + logger.LogTrace("Creating indexer {name}", childIdentifier); + return new CodeIndexer + { + Name = childIdentifier, + Description = $"Gets an item from the {currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName)} collection", + IndexType = new CodeType { Name = "string", IsExternal = true, }, + ReturnType = new CodeType { Name = childType }, + ParameterName = currentNode.Segment.SanitizeUrlTemplateParameterName().TrimStart('{').TrimEnd('}'), + PathSegment = parentNode.GetNodeNamespaceFromPath(string.Empty).Split('.').Last(), + }; + } - private CodeProperty CreateProperty(string childIdentifier, string childType, string defaultValue = null, OpenApiSchema typeSchema = null, CodeElement typeDefinition = null, CodePropertyKind kind = CodePropertyKind.Custom) + private CodeProperty CreateProperty(string childIdentifier, string childType, string defaultValue = null, OpenApiSchema typeSchema = null, CodeElement typeDefinition = null, CodePropertyKind kind = CodePropertyKind.Custom) + { + var propertyName = childIdentifier; + config.PropertiesPrefixToStrip.ForEach(x => propertyName = propertyName.Replace(x, string.Empty)); + var prop = new CodeProperty { - var propertyName = childIdentifier; - config.PropertiesPrefixToStrip.ForEach(x => propertyName = propertyName.Replace(x, string.Empty)); - var prop = new CodeProperty - { - Name = propertyName, - DefaultValue = defaultValue, - PropertyKind = kind, - Description = typeSchema?.Description, - }; - if(propertyName != childIdentifier) - prop.SerializationName = childIdentifier; - - var propType = GetPrimitiveType(typeSchema, childType); - propType.TypeDefinition = typeDefinition; - propType.CollectionKind = typeSchema.IsArray() ? CodeType.CodeTypeCollectionKind.Complex : default; - prop.Type = propType; - logger.LogTrace("Creating property {name} of {type}", prop.Name, prop.Type.Name); - return prop; - } - private static readonly HashSet typeNamesToSkip = new() {"object", "array"}; - private static CodeType GetPrimitiveType(OpenApiSchema typeSchema, string childType = default) { - var typeNames = new List{typeSchema?.Items?.Type, childType, typeSchema?.Type}; - if(typeSchema?.AnyOf?.Any() ?? false) - typeNames.AddRange(typeSchema.AnyOf.Select(x => x.Type)); // double is sometimes an anyof string, number and enum - // first value that's not null, and not "object" for primitive collections, the items type matters - var typeName = typeNames.FirstOrDefault(x => !string.IsNullOrEmpty(x) && !typeNamesToSkip.Contains(x)); - - if(string.IsNullOrEmpty(typeName)) - return null; - var format = typeSchema?.Format ?? typeSchema?.Items?.Format; - var isExternal = false; - if (typeSchema?.Items?.Enum?.Any() ?? false) - typeName = childType; - else if("string".Equals(typeName, StringComparison.OrdinalIgnoreCase)) { - isExternal = true; - if("date-time".Equals(format, StringComparison.OrdinalIgnoreCase)) - typeName = "DateTimeOffset"; - else if("duration".Equals(format, StringComparison.OrdinalIgnoreCase)) - typeName = "TimeSpan"; - else if("date".Equals(format, StringComparison.OrdinalIgnoreCase)) - typeName = "DateOnly"; - else if("time".Equals(format, StringComparison.OrdinalIgnoreCase)) - typeName = "TimeOnly"; - else if ("base64url".Equals(format, StringComparison.OrdinalIgnoreCase)) - typeName = "binary"; - } else if ("double".Equals(format, StringComparison.OrdinalIgnoreCase) || - "float".Equals(format, StringComparison.OrdinalIgnoreCase) || - "int64".Equals(format, StringComparison.OrdinalIgnoreCase) || - "decimal".Equals(format, StringComparison.OrdinalIgnoreCase)) { - isExternal = true; - typeName = format.ToLowerInvariant(); - } else if ("boolean".Equals(typeName, StringComparison.OrdinalIgnoreCase) || - "integer".Equals(typeName, StringComparison.OrdinalIgnoreCase)) + Name = propertyName, + DefaultValue = defaultValue, + PropertyKind = kind, + Description = typeSchema?.Description, + }; + if(propertyName != childIdentifier) + prop.SerializationName = childIdentifier; + + var propType = GetPrimitiveType(typeSchema, childType); + propType.TypeDefinition = typeDefinition; + propType.CollectionKind = typeSchema.IsArray() ? CodeType.CodeTypeCollectionKind.Complex : default; + prop.Type = propType; + logger.LogTrace("Creating property {name} of {type}", prop.Name, prop.Type.Name); + return prop; + } + private static readonly HashSet typeNamesToSkip = new() {"object", "array"}; + private static CodeType GetPrimitiveType(OpenApiSchema typeSchema, string childType = default) { + var typeNames = new List{typeSchema?.Items?.Type, childType, typeSchema?.Type}; + if(typeSchema?.AnyOf?.Any() ?? false) + typeNames.AddRange(typeSchema.AnyOf.Select(x => x.Type)); // double is sometimes an anyof string, number and enum + // first value that's not null, and not "object" for primitive collections, the items type matters + var typeName = typeNames.FirstOrDefault(x => !string.IsNullOrEmpty(x) && !typeNamesToSkip.Contains(x)); + + if(string.IsNullOrEmpty(typeName)) + return null; + var format = typeSchema?.Format ?? typeSchema?.Items?.Format; + var isExternal = false; + if (typeSchema?.Items?.Enum?.Any() ?? false) + typeName = childType; + else if("string".Equals(typeName, StringComparison.OrdinalIgnoreCase)) { isExternal = true; - return new CodeType { - Name = typeName, - IsExternal = isExternal, - }; - } - private const string RequestBodyBinaryContentType = "application/octet-stream"; - private static readonly HashSet noContentStatusCodes = new() { "201", "202", "204" }; - private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) - { - var parameterClass = CreateOperationParameter(currentNode, operationType, operation); - - var schema = operation.GetResponseSchema(); - var method = (HttpMethod)Enum.Parse(typeof(HttpMethod), operationType.ToString()); - var executorMethod = new CodeMethod { - Name = operationType.ToString(), - MethodKind = CodeMethodKind.RequestExecutor, - HttpMethod = method, - Description = operation.Description ?? operation.Summary, - }; - parentClass.AddMethod(executorMethod); - if (schema != null) + if("date-time".Equals(format, StringComparison.OrdinalIgnoreCase)) + typeName = "DateTimeOffset"; + else if("duration".Equals(format, StringComparison.OrdinalIgnoreCase)) + typeName = "TimeSpan"; + else if("date".Equals(format, StringComparison.OrdinalIgnoreCase)) + typeName = "DateOnly"; + else if("time".Equals(format, StringComparison.OrdinalIgnoreCase)) + typeName = "TimeOnly"; + else if ("base64url".Equals(format, StringComparison.OrdinalIgnoreCase)) + typeName = "binary"; + } else if ("double".Equals(format, StringComparison.OrdinalIgnoreCase) || + "float".Equals(format, StringComparison.OrdinalIgnoreCase) || + "int64".Equals(format, StringComparison.OrdinalIgnoreCase) || + "decimal".Equals(format, StringComparison.OrdinalIgnoreCase)) { + isExternal = true; + typeName = format.ToLowerInvariant(); + } else if ("boolean".Equals(typeName, StringComparison.OrdinalIgnoreCase) || + "integer".Equals(typeName, StringComparison.OrdinalIgnoreCase)) + isExternal = true; + return new CodeType { + Name = typeName, + IsExternal = isExternal, + }; + } + private const string RequestBodyBinaryContentType = "application/octet-stream"; + private static readonly HashSet noContentStatusCodes = new() { "201", "202", "204" }; + private static readonly HashSet errorStatusCodes = new(Enumerable.Range(400, 599).Select(x => x.ToString()) + .Concat(new[] { "4XX", "5XX" }), StringComparer.OrdinalIgnoreCase); + + private void AddErrorMappingsForExecutorMethod(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeMethod executorMethod) { + foreach(var response in operation.Responses.Where(x => errorStatusCodes.Contains(x.Key))) { + var errorCode = response.Key.ToUpperInvariant(); + var errorSchema = response.Value.GetResponseSchema(); + var parentElement = string.IsNullOrEmpty(response.Value.Reference?.Id) && string.IsNullOrEmpty(errorSchema?.Reference?.Id) + ? executorMethod as CodeElement + : modelsNamespace; + var errorType = CreateModelDeclarations(currentNode, errorSchema, operation, parentElement, $"{errorCode}Error", response: response.Value); + if (errorType is CodeType codeType && + codeType.TypeDefinition is CodeClass codeClass && + !codeClass.IsErrorDefinition) { - var returnType = CreateModelDeclarations(currentNode, schema, operation, executorMethod, "Response"); - executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation"); - } else { - var returnType = voidType; - if(operation.Responses.Any(x => x.Value.Content.ContainsKey(RequestBodyBinaryContentType))) - returnType = "binary"; - else if(!operation.Responses.Any(x => noContentStatusCodes.Contains(x.Key))) - logger.LogWarning($"could not find operation return type {operationType} {currentNode.Path}"); - executorMethod.ReturnType = new CodeType { Name = returnType, IsExternal = true, }; + codeClass.IsErrorDefinition = true; } - - - AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, executorMethod); - - var handlerParam = new CodeParameter { - Name = "responseHandler", - Optional = true, - ParameterKind = CodeParameterKind.ResponseHandler, - Description = "Response handler to use in place of the default response handling provided by the core service", - Type = new CodeType { Name = "IResponseHandler", IsExternal = true }, - }; - executorMethod.AddParameter(handlerParam);// Add response handler parameter - - var cancellationParam = new CodeParameter{ - Name = "cancellationToken", - Optional = true, - ParameterKind = CodeParameterKind.Cancellation, - Description = "Cancellation token to use when cancelling requests", - Type = new CodeType { Name = "CancellationToken", IsExternal = true }, - }; - executorMethod.AddParameter(cancellationParam);// Add cancellation token parameter - logger.LogTrace("Creating method {name} of {type}", executorMethod.Name, executorMethod.ReturnType); - - var generatorMethod = new CodeMethod { - Name = $"Create{operationType.ToString().ToFirstCharacterUpperCase()}RequestInformation", - MethodKind = CodeMethodKind.RequestGenerator, - IsAsync = false, - HttpMethod = method, - Description = operation.Description ?? operation.Summary, - ReturnType = new CodeType { Name = "RequestInformation", IsNullable = false, IsExternal = true}, + executorMethod.ErrorMappings.Add(errorCode, errorType); + } + } + private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) + { + var parameterClass = CreateOperationParameter(currentNode, operationType, operation); + + var schema = operation.GetResponseSchema(); + var method = (HttpMethod)Enum.Parse(typeof(HttpMethod), operationType.ToString()); + var executorMethod = new CodeMethod { + Name = operationType.ToString(), + MethodKind = CodeMethodKind.RequestExecutor, + HttpMethod = method, + Description = operation.Description ?? operation.Summary, + }; + parentClass.AddMethod(executorMethod); + AddErrorMappingsForExecutorMethod(currentNode, operation, executorMethod); + if (schema != null) + { + var returnType = CreateModelDeclarations(currentNode, schema, operation, executorMethod, "Response"); + executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation"); + } else { + var returnType = voidType; + if(operation.Responses.Any(x => x.Value.Content.ContainsKey(RequestBodyBinaryContentType))) + returnType = "binary"; + else if(!operation.Responses.Any(x => noContentStatusCodes.Contains(x.Key))) + logger.LogWarning($"could not find operation return type {operationType} {currentNode.Path}"); + executorMethod.ReturnType = new CodeType { Name = returnType, IsExternal = true, }; + } + + + AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, executorMethod); + + var handlerParam = new CodeParameter { + Name = "responseHandler", + Optional = true, + ParameterKind = CodeParameterKind.ResponseHandler, + Description = "Response handler to use in place of the default response handling provided by the core service", + Type = new CodeType { Name = "IResponseHandler", IsExternal = true }, + }; + executorMethod.AddParameter(handlerParam);// Add response handler parameter + + var cancellationParam = new CodeParameter{ + Name = "cancellationToken", + Optional = true, + ParameterKind = CodeParameterKind.Cancellation, + Description = "Cancellation token to use when cancelling requests", + Type = new CodeType { Name = "CancellationToken", IsExternal = true }, + }; + executorMethod.AddParameter(cancellationParam);// Add cancellation token parameter + logger.LogTrace("Creating method {name} of {type}", executorMethod.Name, executorMethod.ReturnType); + + var generatorMethod = new CodeMethod { + Name = $"Create{operationType.ToString().ToFirstCharacterUpperCase()}RequestInformation", + MethodKind = CodeMethodKind.RequestGenerator, + IsAsync = false, + HttpMethod = method, + Description = operation.Description ?? operation.Summary, + ReturnType = new CodeType { Name = "RequestInformation", IsNullable = false, IsExternal = true}, + }; + parentClass.AddMethod(generatorMethod); + AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod); + logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType); + } + private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { + var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase)); + if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) + { + var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema; + var requestBodyType = CreateModelDeclarations(currentNode, requestBodySchema, operation, method, "RequestBody"); + method.AddParameter(new CodeParameter { + Name = "body", + Type = requestBodyType, + Optional = false, + ParameterKind = CodeParameterKind.RequestBody, + Description = requestBodySchema.Description + }); + method.ContentType = nonBinaryRequestBody.Value.Key; + } else if (operation.RequestBody?.Content?.ContainsKey(RequestBodyBinaryContentType) ?? false) { + var nParam = new CodeParameter { + Name = "body", + Optional = false, + ParameterKind = CodeParameterKind.RequestBody, + Description = $"Binary request body", + Type = new CodeType { + Name = "binary", + IsExternal = true, + IsNullable = false, + }, }; - parentClass.AddMethod(generatorMethod); - AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod); - logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType); + method.AddParameter(nParam); } - private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { - var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase)); - if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) + if(parameterClass != null) { + var qsParam = new CodeParameter { - var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema; - var requestBodyType = CreateModelDeclarations(currentNode, requestBodySchema, operation, method, "RequestBody"); - method.AddParameter(new CodeParameter { - Name = "body", - Type = requestBodyType, - Optional = false, - ParameterKind = CodeParameterKind.RequestBody, - Description = requestBodySchema.Description - }); - method.ContentType = nonBinaryRequestBody.Value.Key; - } else if (operation.RequestBody?.Content?.ContainsKey(RequestBodyBinaryContentType) ?? false) { - var nParam = new CodeParameter { - Name = "body", - Optional = false, - ParameterKind = CodeParameterKind.RequestBody, - Description = $"Binary request body", - Type = new CodeType { - Name = "binary", - IsExternal = true, - IsNullable = false, - }, - }; - method.AddParameter(nParam); - } - if(parameterClass != null) { - var qsParam = new CodeParameter - { - Name = "q", - Optional = true, - ParameterKind = CodeParameterKind.QueryParameter, - Description = "Request query parameters", - Type = new CodeType { Name = parameterClass.Name, ActionOf = true, TypeDefinition = parameterClass }, - }; - method.AddParameter(qsParam); - } - var headersParam = new CodeParameter { - Name = "h", - Optional = true, - ParameterKind = CodeParameterKind.Headers, - Description = "Request headers", - Type = new CodeType { Name = "IDictionary", ActionOf = true, IsExternal = true }, - }; - method.AddParameter(headersParam); - var optionsParam = new CodeParameter { - Name = "o", + Name = "q", Optional = true, - ParameterKind = CodeParameterKind.Options, - Description = "Request options", - Type = new CodeType { Name = "IEnumerable", ActionOf = false, IsExternal = true }, - }; - method.AddParameter(optionsParam); - } - private string GetModelsNamespaceNameFromReferenceId(string referenceId) { - if (string.IsNullOrEmpty(referenceId)) return referenceId; - if(referenceId.StartsWith(config.ClientClassName, StringComparison.OrdinalIgnoreCase)) - referenceId = referenceId[config.ClientClassName.Length..]; - referenceId = referenceId.Trim(nsNameSeparator); - var lastDotIndex = referenceId.LastIndexOf(nsNameSeparator); - var namespaceSuffix = lastDotIndex != -1 ? referenceId[..lastDotIndex] : referenceId; - return $"{modelsNamespace.Name}.{namespaceSuffix}"; - } - private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace codeNamespace, string classNameSuffix = "") { - var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix); - var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, schema, className, codeNamespace); - return new CodeType { - TypeDefinition = codeDeclaration, - Name = className, + ParameterKind = CodeParameterKind.QueryParameter, + Description = "Request query parameters", + Type = new CodeType { Name = parameterClass.Name, ActionOf = true, TypeDefinition = parameterClass }, }; - } - private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation) { - var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf); - CodeElement codeDeclaration = null; - var className = string.Empty; - foreach(var currentSchema in allOfs) { - var referenceId = GetReferenceIdFromOriginalSchema(currentSchema, schema); - var shortestNamespaceName = string.IsNullOrEmpty(referenceId) ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(referenceId); - var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); - if(shortestNamespace == null) - shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); - className = currentSchema.GetSchemaTitle() ?? currentNode.GetClassName(operation: operation); - codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace, codeDeclaration as CodeClass, true); - } - - return new CodeType { + method.AddParameter(qsParam); + } + var headersParam = new CodeParameter { + Name = "h", + Optional = true, + ParameterKind = CodeParameterKind.Headers, + Description = "Request headers", + Type = new CodeType { Name = "IDictionary", ActionOf = true, IsExternal = true }, + }; + method.AddParameter(headersParam); + var optionsParam = new CodeParameter { + Name = "o", + Optional = true, + ParameterKind = CodeParameterKind.Options, + Description = "Request options", + Type = new CodeType { Name = "IEnumerable", ActionOf = false, IsExternal = true }, + }; + method.AddParameter(optionsParam); + } + private string GetModelsNamespaceNameFromReferenceId(string referenceId) { + if (string.IsNullOrEmpty(referenceId)) return referenceId; + if(referenceId.StartsWith(config.ClientClassName, StringComparison.OrdinalIgnoreCase)) + referenceId = referenceId[config.ClientClassName.Length..]; + referenceId = referenceId.Trim(nsNameSeparator); + var lastDotIndex = referenceId.LastIndexOf(nsNameSeparator); + var namespaceSuffix = lastDotIndex != -1 ? referenceId[..lastDotIndex] : referenceId; + return $"{modelsNamespace.Name}.{namespaceSuffix}"; + } + private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace codeNamespace, string classNameSuffix = "", OpenApiResponse response = default) { + var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix, response: response, schema: schema); + var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, schema, className, codeNamespace); + return new CodeType { + TypeDefinition = codeDeclaration, + Name = className, + }; + } + private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation) { + var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf); + CodeElement codeDeclaration = null; + var className = string.Empty; + foreach(var currentSchema in allOfs) { + var referenceId = GetReferenceIdFromOriginalSchema(currentSchema, schema); + var shortestNamespaceName = string.IsNullOrEmpty(referenceId) ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(referenceId); + var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); + if(shortestNamespace == null) + shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); + className = currentSchema.GetSchemaTitle() ?? currentNode.GetClassName(operation: operation, schema: schema); + codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace, codeDeclaration as CodeClass, true); + } + + return new CodeType { + TypeDefinition = codeDeclaration, + Name = className, + }; + } + private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, OpenApiSchema parentSchema) { + var title = schema.Title; + if(!string.IsNullOrEmpty(schema.Reference?.Id)) return schema.Reference.Id; + if(parentSchema.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Reference.Id; + if(parentSchema.Items?.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Items.Reference.Id; + return (parentSchema. + AllOf + .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ?? + parentSchema. + AnyOf + .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ?? + parentSchema. + OneOf + .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false)) + ?.Reference?.Id; + } + private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string suffixForInlineSchema) { + var schemas = schema.AnyOf.Union(schema.OneOf); + var unionType = new CodeUnionType { + Name = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema, schema: schema), + }; + foreach(var currentSchema in schemas) { + var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id); + var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); + if(shortestNamespace == null) + shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); + var className = currentSchema.GetSchemaTitle(); + var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace); + unionType.AddType(new CodeType { TypeDefinition = codeDeclaration, Name = className, - }; + }); } - private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, OpenApiSchema parentSchema) { - var title = schema.Title; - if(!string.IsNullOrEmpty(schema.Reference?.Id)) return schema.Reference.Id; - if(parentSchema.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Reference.Id; - if(parentSchema.Items?.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Items.Reference.Id; - return (parentSchema. - AllOf - .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ?? - parentSchema. - AnyOf - .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ?? - parentSchema. - OneOf - .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false)) - ?.Reference?.Id; + return unionType; + } + private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, string suffixForInlineSchema, OpenApiResponse response = default) + { + var codeNamespace = parentElement.GetImmediateParentOfType(); + + if (!schema.IsReferencedSchema() && schema.Properties.Any()) { // Inline schema, i.e. specific to the Operation + return CreateModelDeclarationAndType(currentNode, schema, operation, codeNamespace, suffixForInlineSchema); + } else if(schema.IsAllOf()) { + return CreateInheritedModelDeclaration(currentNode, schema, operation); + } else if(schema.IsAnyOf() || schema.IsOneOf()) { + return CreateUnionModelDeclaration(currentNode, schema, operation, suffixForInlineSchema); + } else if(schema.IsObject()) { + // referenced schema, no inheritance or union type + var targetNamespace = GetShortestNamespace(codeNamespace, schema); + return CreateModelDeclarationAndType(currentNode, schema, operation, targetNamespace, response: response); + } else if (schema.IsArray()) { + // collections at root + var type = GetPrimitiveType(schema?.Items, string.Empty); + if(type == null) + type = CreateModelDeclarationAndType(currentNode, schema?.Items, operation, codeNamespace); + type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + return type; + } else if(!string.IsNullOrEmpty(schema.Type)) + return GetPrimitiveType(schema, string.Empty); + else throw new InvalidOperationException("un handled case, might be object type or array type"); + } + private CodeElement GetExistingDeclaration(bool checkInAllNamespaces, CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, string declarationName) { + var searchNameSpace = GetSearchNamespace(checkInAllNamespaces, currentNode, currentNamespace); + return searchNameSpace.FindChildByName(declarationName, checkInAllNamespaces) as CodeElement; + } + private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlTreeNode currentNode, CodeNamespace currentNamespace) { + if(checkInAllNamespaces) return rootNamespace; + else if (currentNode.DoesNodeBelongToItemSubnamespace()) return currentNamespace.EnsureItemNamespace(); + else return currentNamespace; + } + private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) { + var existingDeclaration = GetExistingDeclaration(checkInAllNamespaces, currentNamespace, currentNode, declarationName); + if(existingDeclaration == null) // we can find it in the components + { + if(schema.Enum.Any()) { + var newEnum = new CodeEnum { + Name = declarationName, + Options = schema.Enum.OfType().Select(x => x.Value).Where(x => !"null".Equals(x)).ToList(),//TODO set the flag property + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel), + }; + return currentNamespace.AddEnum(newEnum).First(); + } else + return AddModelClass(currentNode, schema, declarationName, currentNamespace, inheritsFrom); + } else + return existingDeclaration; + } + private CodeNamespace GetShortestNamespace(CodeNamespace currentNamespace, OpenApiSchema currentSchema) { + if(!string.IsNullOrEmpty(currentSchema.Reference?.Id)) { + var parentClassNamespaceName = GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id); + return rootNamespace.AddNamespace(parentClassNamespaceName); } - private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string suffixForInlineSchema) { - var schemas = schema.AnyOf.Union(schema.OneOf); - var unionType = new CodeUnionType { - Name = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema), - }; - foreach(var currentSchema in schemas) { - var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id); - var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); - if(shortestNamespace == null) - shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); - var className = currentSchema.GetSchemaTitle(); - var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace); - unionType.AddType(new CodeType { - TypeDefinition = codeDeclaration, - Name = className, - }); + return currentNamespace; + } + private CodeClass AddModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null) { + var referencedAllOfs = schema.AllOf.Where(x => x.Reference != null); + if(inheritsFrom == null && referencedAllOfs.Any()) {// any non-reference would be the current class in some description styles + var parentSchema = referencedAllOfs.FirstOrDefault(); + if(parentSchema != null) { + var parentClassNamespace = GetShortestNamespace(currentNamespace, parentSchema); + inheritsFrom = AddModelDeclarationIfDoesntExit(currentNode, parentSchema, parentSchema.GetSchemaTitle(), parentClassNamespace, null, true) as CodeClass; } - return unionType; } - private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, string suffixForInlineSchema) + var newClass = currentNamespace.AddClass(new CodeClass { + Name = declarationName, + ClassKind = CodeClassKind.Model, + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel) + }).First(); + if(inheritsFrom != null) { + var declaration = newClass.StartBlock as CodeClass.Declaration; + declaration.Inherits = new CodeType { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name }; + } + CreatePropertiesForModelClass(currentNode, schema, currentNamespace, newClass); + return newClass; + } + private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, CodeNamespace ns, CodeClass model) { + AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false, config.UsesBackingStore); + if(schema?.Properties?.Any() ?? false) { - var codeNamespace = parentElement.GetImmediateParentOfType(); - - if (!schema.IsReferencedSchema() && schema.Properties.Any()) { // Inline schema, i.e. specific to the Operation - return CreateModelDeclarationAndType(currentNode, schema, operation, codeNamespace, suffixForInlineSchema); - } else if(schema.IsAllOf()) { - return CreateInheritedModelDeclaration(currentNode, schema, operation); - } else if(schema.IsAnyOf() || schema.IsOneOf()) { - return CreateUnionModelDeclaration(currentNode, schema, operation, suffixForInlineSchema); - } else if(schema.IsObject()) { - // referenced schema, no inheritance or union type - var targetNamespace = GetShortestNamespace(codeNamespace, schema); - return CreateModelDeclarationAndType(currentNode, schema, operation, targetNamespace); - } else if (schema.IsArray()) { - // collections at root - var type = GetPrimitiveType(schema?.Items, string.Empty); - if(type == null) - type = CreateModelDeclarationAndType(currentNode, schema?.Items, operation, codeNamespace); - type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; - return type; - } else if(!string.IsNullOrEmpty(schema.Type)) - return GetPrimitiveType(schema, string.Empty); - else throw new InvalidOperationException("un handled case, might be object type or array type"); + model.AddProperty(schema + .Properties + .Select(x => { + var propertyDefinitionSchema = x.Value.GetNonEmptySchemas().FirstOrDefault(); + var className = propertyDefinitionSchema.GetSchemaTitle(); + CodeElement definition = default; + if(propertyDefinitionSchema != null) { + if(string.IsNullOrEmpty(className)) + className = $"{model.Name}_{x.Key}"; + var shortestNamespaceName = GetModelsNamespaceNameFromReferenceId(propertyDefinitionSchema.Reference?.Id); + var targetNamespace = string.IsNullOrEmpty(shortestNamespaceName) ? ns : + (rootNamespace.FindNamespaceByName(shortestNamespaceName) ?? rootNamespace.AddNamespace(shortestNamespaceName)); + definition = AddModelDeclarationIfDoesntExit(currentNode, propertyDefinitionSchema, className, targetNamespace, null, true); + } + return CreateProperty(x.Key, className ?? x.Value.Type, typeSchema: x.Value, typeDefinition: definition); + }) + .ToArray()); + } + else if(schema?.AllOf?.Any(x => x.IsObject()) ?? false) + CreatePropertiesForModelClass(currentNode, schema.AllOf.Last(x => x.IsObject()), ns, model); + } + private const string FieldDeserializersMethodName = "GetFieldDeserializers"; + private const string SerializeMethodName = "Serialize"; + private const string AdditionalDataPropName = "AdditionalData"; + private const string BackingStorePropertyName = "BackingStore"; + private const string BackingStoreInterface = "IBackingStore"; + private const string BackedModelInterface = "IBackedModel"; + internal static void AddSerializationMembers(CodeClass model, bool includeAdditionalProperties, bool usesBackingStore) { + var serializationPropsType = $"IDictionary>"; + if(!model.ContainsMember(FieldDeserializersMethodName)) { + var deserializeProp = new CodeMethod { + Name = FieldDeserializersMethodName, + MethodKind = CodeMethodKind.Deserializer, + Access = AccessModifier.Public, + Description = "The deserialization information for the current model", + IsAsync = false, + ReturnType = new CodeType { + Name = serializationPropsType, + IsNullable = false, + IsExternal = true, + }, + }; + model.AddMethod(deserializeProp); } - private CodeElement GetExistingDeclaration(bool checkInAllNamespaces, CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, string declarationName) { - var searchNameSpace = GetSearchNamespace(checkInAllNamespaces, currentNode, currentNamespace); - return searchNameSpace.FindChildByName(declarationName, checkInAllNamespaces) as CodeElement; + if(!model.ContainsMember(SerializeMethodName)) { + var serializeMethod = new CodeMethod { + Name = SerializeMethodName, + MethodKind = CodeMethodKind.Serializer, + IsAsync = false, + Description = $"Serializes information the current object", + ReturnType = new CodeType { Name = voidType, IsNullable = false, IsExternal = true }, + }; + var parameter = new CodeParameter { + Name = "writer", + Description = "Serialization writer to use to serialize this model", + ParameterKind = CodeParameterKind.Serializer, + Type = new CodeType { Name = "ISerializationWriter", IsExternal = true, IsNullable = false }, + }; + serializeMethod.AddParameter(parameter); + + model.AddMethod(serializeMethod); + } + if(!model.ContainsMember(AdditionalDataPropName) && + includeAdditionalProperties && + !(model.GetGreatestGrandparent(model)?.ContainsMember(AdditionalDataPropName) ?? false)) { + // we don't want to add the property if the parent already has it + var additionalDataProp = new CodeProperty { + Name = AdditionalDataPropName, + Access = AccessModifier.Public, + DefaultValue = "new Dictionary()", + PropertyKind = CodePropertyKind.AdditionalData, + Description = "Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.", + Type = new CodeType { + Name = "IDictionary", + IsNullable = false, + IsExternal = true, + }, + }; + model.AddProperty(additionalDataProp); } - private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlTreeNode currentNode, CodeNamespace currentNamespace) { - if(checkInAllNamespaces) return rootNamespace; - else if (currentNode.DoesNodeBelongToItemSubnamespace()) return currentNamespace.EnsureItemNamespace(); - else return currentNamespace; + if(!model.ContainsMember(BackingStorePropertyName) && + usesBackingStore && + !(model.GetGreatestGrandparent(model)?.ContainsMember(BackingStorePropertyName) ?? false)) { + var backingStoreProperty = new CodeProperty { + Name = BackingStorePropertyName, + Access = AccessModifier.Public, + DefaultValue = $"BackingStoreFactorySingleton.Instance.CreateBackingStore()", + PropertyKind = CodePropertyKind.BackingStore, + Description = "Stores model information.", + ReadOnly = true, + Type = new CodeType { + Name = BackingStoreInterface, + IsNullable = false, + IsExternal = true, + }, + }; + model.AddProperty(backingStoreProperty); + (model.StartBlock as CodeClass.Declaration).AddImplements(new CodeType { + Name = BackedModelInterface, + IsExternal = true, + }); } - private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) { - var existingDeclaration = GetExistingDeclaration(checkInAllNamespaces, currentNamespace, currentNode, declarationName); - if(existingDeclaration == null) // we can find it in the components + } + private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationType operationType, OpenApiOperation operation) + { + var parameters = node.PathItems[Constants.DefaultOpenApiLabel].Parameters.Union(operation.Parameters).Where(p => p.In == ParameterLocation.Query); + if(parameters.Any()) { + var parameterClass = new CodeClass { - if(schema.Enum.Any()) { - var newEnum = new CodeEnum { - Name = declarationName, - Options = schema.Enum.OfType().Select(x => x.Value).Where(x => !"null".Equals(x)).ToList(),//TODO set the flag property - Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel), - }; - return currentNamespace.AddEnum(newEnum).First(); - } else - return AddModelClass(currentNode, schema, declarationName, currentNamespace, inheritsFrom); - } else - return existingDeclaration; - } - private CodeNamespace GetShortestNamespace(CodeNamespace currentNamespace, OpenApiSchema currentSchema) { - if(!string.IsNullOrEmpty(currentSchema.Reference?.Id)) { - var parentClassNamespaceName = GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id); - return rootNamespace.AddNamespace(parentClassNamespaceName); - } - return currentNamespace; - } - private CodeClass AddModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null) { - var referencedAllOfs = schema.AllOf.Where(x => x.Reference != null); - if(inheritsFrom == null && referencedAllOfs.Any()) {// any non-reference would be the current class in some description styles - var parentSchema = referencedAllOfs.FirstOrDefault(); - if(parentSchema != null) { - var parentClassNamespace = GetShortestNamespace(currentNamespace, parentSchema); - inheritsFrom = AddModelDeclarationIfDoesntExit(currentNode, parentSchema, parentSchema.GetSchemaTitle(), parentClassNamespace, null, true) as CodeClass; - } - } - var newClass = currentNamespace.AddClass(new CodeClass { - Name = declarationName, - ClassKind = CodeClassKind.Model, - Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel) - }).First(); - if(inheritsFrom != null) { - var declaration = newClass.StartBlock as CodeClass.Declaration; - declaration.Inherits = new CodeType { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name }; - } - CreatePropertiesForModelClass(currentNode, schema, currentNamespace, newClass); - return newClass; - } - private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, CodeNamespace ns, CodeClass model) { - AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false, config.UsesBackingStore); - if(schema?.Properties?.Any() ?? false) + Name = operationType.ToString() + "QueryParameters", + ClassKind = CodeClassKind.QueryParameters, + Description = operation.Description ?? operation.Summary + }; + foreach (var parameter in parameters) { - model.AddProperty(schema - .Properties - .Select(x => { - var propertyDefinitionSchema = x.Value.GetNonEmptySchemas().FirstOrDefault(); - var className = propertyDefinitionSchema.GetSchemaTitle(); - CodeElement definition = default; - if(propertyDefinitionSchema != null) { - if(string.IsNullOrEmpty(className)) - className = $"{model.Name}_{x.Key}"; - var shortestNamespaceName = GetModelsNamespaceNameFromReferenceId(propertyDefinitionSchema.Reference?.Id); - var targetNamespace = string.IsNullOrEmpty(shortestNamespaceName) ? ns : - (rootNamespace.FindNamespaceByName(shortestNamespaceName) ?? rootNamespace.AddNamespace(shortestNamespaceName)); - definition = AddModelDeclarationIfDoesntExit(currentNode, propertyDefinitionSchema, className, targetNamespace, null, true); - } - return CreateProperty(x.Key, className ?? x.Value.Type, typeSchema: x.Value, typeDefinition: definition); - }) - .ToArray()); - } - else if(schema?.AllOf?.Any(x => x.IsObject()) ?? false) - CreatePropertiesForModelClass(currentNode, schema.AllOf.Last(x => x.IsObject()), ns, model); - } - private const string FieldDeserializersMethodName = "GetFieldDeserializers"; - private const string SerializeMethodName = "Serialize"; - private const string AdditionalDataPropName = "AdditionalData"; - private const string BackingStorePropertyName = "BackingStore"; - private const string BackingStoreInterface = "IBackingStore"; - private const string BackedModelInterface = "IBackedModel"; - internal static void AddSerializationMembers(CodeClass model, bool includeAdditionalProperties, bool usesBackingStore) { - var serializationPropsType = $"IDictionary>"; - if(!model.ContainsMember(FieldDeserializersMethodName)) { - var deserializeProp = new CodeMethod { - Name = FieldDeserializersMethodName, - MethodKind = CodeMethodKind.Deserializer, - Access = AccessModifier.Public, - Description = "The deserialization information for the current model", - IsAsync = false, - ReturnType = new CodeType { - Name = serializationPropsType, - IsNullable = false, - IsExternal = true, - }, - }; - model.AddMethod(deserializeProp); - } - if(!model.ContainsMember(SerializeMethodName)) { - var serializeMethod = new CodeMethod { - Name = SerializeMethodName, - MethodKind = CodeMethodKind.Serializer, - IsAsync = false, - Description = $"Serializes information the current object", - ReturnType = new CodeType { Name = voidType, IsNullable = false, IsExternal = true }, - }; - var parameter = new CodeParameter { - Name = "writer", - Description = "Serialization writer to use to serialize this model", - ParameterKind = CodeParameterKind.Serializer, - Type = new CodeType { Name = "ISerializationWriter", IsExternal = true, IsNullable = false }, - }; - serializeMethod.AddParameter(parameter); - - model.AddMethod(serializeMethod); - } - if(!model.ContainsMember(AdditionalDataPropName) && - includeAdditionalProperties && - !(model.GetGreatestGrandparent(model)?.ContainsMember(AdditionalDataPropName) ?? false)) { - // we don't want to add the property if the parent already has it - var additionalDataProp = new CodeProperty { - Name = AdditionalDataPropName, - Access = AccessModifier.Public, - DefaultValue = "new Dictionary()", - PropertyKind = CodePropertyKind.AdditionalData, - Description = "Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.", - Type = new CodeType { - Name = "IDictionary", - IsNullable = false, - IsExternal = true, - }, - }; - model.AddProperty(additionalDataProp); - } - if(!model.ContainsMember(BackingStorePropertyName) && - usesBackingStore && - !(model.GetGreatestGrandparent(model)?.ContainsMember(BackingStorePropertyName) ?? false)) { - var backingStoreProperty = new CodeProperty { - Name = BackingStorePropertyName, - Access = AccessModifier.Public, - DefaultValue = $"BackingStoreFactorySingleton.Instance.CreateBackingStore()", - PropertyKind = CodePropertyKind.BackingStore, - Description = "Stores model information.", - ReadOnly = true, - Type = new CodeType { - Name = BackingStoreInterface, - IsNullable = false, + var prop = new CodeProperty + { + Name = FixQueryParameterIdentifier(parameter), + Description = parameter.Description, + Type = new CodeType + { IsExternal = true, + Name = parameter.Schema.Items?.Type ?? parameter.Schema.Type, + CollectionKind = parameter.Schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default, }, }; - model.AddProperty(backingStoreProperty); - (model.StartBlock as CodeClass.Declaration).AddImplements(new CodeType { - Name = BackedModelInterface, - IsExternal = true, - }); - } - } - private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationType operationType, OpenApiOperation operation) - { - var parameters = node.PathItems[Constants.DefaultOpenApiLabel].Parameters.Union(operation.Parameters).Where(p => p.In == ParameterLocation.Query); - if(parameters.Any()) { - var parameterClass = new CodeClass + + if (!parameterClass.ContainsMember(parameter.Name)) { - Name = operationType.ToString() + "QueryParameters", - ClassKind = CodeClassKind.QueryParameters, - Description = operation.Description ?? operation.Summary - }; - foreach (var parameter in parameters) + parameterClass.AddProperty(prop); + } + else { - var prop = new CodeProperty - { - Name = FixQueryParameterIdentifier(parameter), - Description = parameter.Description, - Type = new CodeType - { - IsExternal = true, - Name = parameter.Schema.Items?.Type ?? parameter.Schema.Type, - CollectionKind = parameter.Schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default, - }, - }; - - if (!parameterClass.ContainsMember(parameter.Name)) - { - parameterClass.AddProperty(prop); - } - else - { - logger.LogWarning("Ignoring duplicate parameter {name}", parameter.Name); - } + logger.LogWarning("Ignoring duplicate parameter {name}", parameter.Name); } + } - return parameterClass; - } else return null; - } + return parameterClass; + } else return null; + } - private static string FixQueryParameterIdentifier(OpenApiParameter parameter) - { - // Replace with regexes pulled from settings that are API specific + private static string FixQueryParameterIdentifier(OpenApiParameter parameter) + { + // Replace with regexes pulled from settings that are API specific - return parameter.Name.Replace("$","").ToCamelCase(); - } + return parameter.Name.Replace("$","").ToCamelCase(); } } diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index a23d7b5ffa..2a73363c08 100644 --- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs +++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs @@ -33,6 +33,11 @@ public override void Refine(CodeNamespace generatedCode) DisambiguatePropertiesWithClassNames(generatedCode); AddConstructorsForDefaultValues(generatedCode, false); AddSerializationModulesImport(generatedCode); + AddParentClassToErrorClasses( + generatedCode, + "ApiException", + "Microsoft.Kiota.Abstractions" + ); } private static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) { if(currentElement is CodeClass currentClass) { diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 744713d95e..558d5a35d8 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -180,6 +180,8 @@ protected static void ReplaceReservedNames(CodeElement current, IReservedNamesPr !returnType.IsExternal && provider.ReservedNames.Contains(returnType.Name)) returnType.Name = replacement.Invoke(returnType.Name); + if(currentMethod.ErrorMappings.Values.Select(x => x.Name).Any(x => provider.ReservedNames.Contains(x))) + ReplaceErrorMappingNames(currentMethod, provider, replacement); ReplaceReservedParameterNamesTypes(currentMethod, provider, replacement); } else if (current is CodeProperty currentProperty && isNotInExceptions && @@ -225,6 +227,12 @@ private static void ReplaceReservedNamespaceSegments(CodeNamespace currentNamesp x) .Aggregate((x, y) => $"{x}.{y}"); } + private static void ReplaceErrorMappingNames(CodeMethod currentMethod, IReservedNamesProvider provider, Func replacement) + { + currentMethod.ErrorMappings.Values.Where(x => provider.ReservedNames.Contains(x.Name)) + .ToList() + .ForEach(x => x.Name = replacement.Invoke(x.Name)); + } private static void ReplaceReservedParameterNamesTypes(CodeMethod currentMethod, IReservedNamesProvider provider, Func replacement) { currentMethod.Parameters.Where(x => x.Type is CodeType parameterType && @@ -440,11 +448,17 @@ protected static void AddPropertiesAndMethodTypesImports(CodeElement current, bo .OfType() .Select(x => x.ReturnType) .Distinct(); + var errorTypes = currentClassChildren + .OfType() + .Where(x => x.IsOfKind(CodeMethodKind.RequestExecutor)) + .SelectMany(x => x.ErrorMappings.Values) + .Distinct(); var usingsToAdd = propertiesTypes .Union(methodsParametersTypes) .Union(methodsReturnTypes) .Union(indexerTypes) .Union(inheritTypes) + .Union(errorTypes) .Where(x => x != null) .SelectMany(x => x?.AllTypes?.Select(y => new Tuple(y, y?.TypeDefinition?.GetImmediateParentOfType()))) .Where(x => x.Item2 != null && (includeCurrentNamespace || x.Item2 != currentClassNamespace)) @@ -541,4 +555,23 @@ protected static void CorrectDateTypes(CodeClass parentClass, Dictionary AddParentClassToErrorClasses(x, parentClassName, parentClassNamespace)); + } } diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs index 630450e96f..e9203fd92d 100644 --- a/src/Kiota.Builder/Refiners/GoRefiner.cs +++ b/src/Kiota.Builder/Refiners/GoRefiner.cs @@ -87,6 +87,11 @@ public override void Refine(CodeNamespace generatedCode) new string[] {"github.com/microsoft/kiota/abstractions/go/serialization.ParseNodeFactory", "github.com/microsoft/kiota/abstractions/go.RegisterDefaultDeserializer"}); ReplaceExecutorAndGeneratorParametersByParameterSets( generatedCode); + AddParentClassToErrorClasses( + generatedCode, + "ApiError", + "github.com/microsoft/kiota/abstractions/go" + ); } private static void ReplaceExecutorAndGeneratorParametersByParameterSets(CodeElement currentElement) { if (currentElement is CodeMethod currentMethod && @@ -257,6 +262,8 @@ private static void AddErrorImportForEnums(CodeElement currentElement) { "github.com/microsoft/kiota/abstractions/go/serialization", "SerializationWriter"), new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer), "github.com/microsoft/kiota/abstractions/go/serialization", "ParseNode", "Parsable"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor) && method.ErrorMappings.Any(), + "github.com/microsoft/kiota/abstractions/go/serialization", "Parsable"), new (x => x is CodeEnum num, "ToUpper", "strings"), };//TODO add backing store types once we have them defined private static void CorrectMethodType(CodeMethod currentMethod) { diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index a4c3cf0e65..17fb78e16f 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -37,6 +37,11 @@ public override void Refine(CodeNamespace generatedCode) new [] { "com.microsoft.kiota.ApiClientBuilder", "com.microsoft.kiota.serialization.SerializationWriterFactoryRegistry" }, new [] { "com.microsoft.kiota.serialization.ParseNodeFactoryRegistry" }); + AddParentClassToErrorClasses( + generatedCode, + "ApiException", + "com.microsoft.kiota" + ); } private static void SetSetterParametersToNullable(CodeElement currentElement, params Tuple[] accessorPairs) { if(currentElement is CodeMethod method && diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 4666bff1b9..6f5cca3b31 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -32,6 +32,11 @@ public override void Refine(CodeNamespace generatedCode) $"{AbstractionsPackageName}.SerializationWriterFactoryRegistry"}, new[] { $"{AbstractionsPackageName}.registerDefaultDeserializer", $"{AbstractionsPackageName}.ParseNodeFactoryRegistry" }); + AddParentClassToErrorClasses( + generatedCode, + "ApiError", + "@microsoft/kiota-abstractions" + ); } private static readonly CodeUsingDeclarationNameComparer usingComparer = new(); private static void AliasUsingsWithSameSymbol(CodeElement currentElement) { diff --git a/src/Kiota.Builder/Refiners/TypeScriptReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/TypeScriptReservedNamesProvider.cs index ffa751a636..a0e7348bd3 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptReservedNamesProvider.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptReservedNamesProvider.cs @@ -15,6 +15,7 @@ public class TypeScriptReservedNamesProvider : IReservedNamesProvider { "do", "else", "enum", + "error", "export", "extends", "false", @@ -24,7 +25,7 @@ public class TypeScriptReservedNamesProvider : IReservedNamesProvider { "If", "import", "in", - "istanceOf", + "instanceOf", "new", "null", "return", diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index f0dad7133f..668c0862dd 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -2,299 +2,306 @@ using System.Collections.Generic; using System.Linq; using Kiota.Builder.Extensions; -using Kiota.Builder.Writers.Extensions; -namespace Kiota.Builder.Writers.CSharp { - public class CodeMethodWriter : BaseElementWriter +namespace Kiota.Builder.Writers.CSharp; +public class CodeMethodWriter : BaseElementWriter +{ + public CodeMethodWriter(CSharpConventionService conventionService): base(conventionService) { } + public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { - public CodeMethodWriter(CSharpConventionService conventionService): base(conventionService) { } - public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) - { - if(codeElement == null) throw new ArgumentNullException(nameof(codeElement)); - if(codeElement.ReturnType == null) throw new InvalidOperationException($"{nameof(codeElement.ReturnType)} should not be null"); - if(writer == null) throw new ArgumentNullException(nameof(writer)); - if(!(codeElement.Parent is CodeClass)) throw new InvalidOperationException("the parent of a method should be a class"); + if(codeElement == null) throw new ArgumentNullException(nameof(codeElement)); + if(codeElement.ReturnType == null) throw new InvalidOperationException($"{nameof(codeElement.ReturnType)} should not be null"); + if(writer == null) throw new ArgumentNullException(nameof(writer)); + if(!(codeElement.Parent is CodeClass)) throw new InvalidOperationException("the parent of a method should be a class"); - var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); - var parentClass = codeElement.Parent as CodeClass; - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; - var isVoid = conventions.VoidTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); - WriteMethodDocumentation(codeElement, writer); - WriteMethodPrototype(codeElement, writer, returnType, inherits, isVoid); - writer.IncreaseIndent(); - 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); - var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam); - foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) { - var parameterName = parameter.Name.ToFirstCharacterLowerCase(); - if(nameof(String).Equals(parameter.Type.Name, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"if(string.IsNullOrEmpty({parameterName})) throw new ArgumentNullException(nameof({parameterName}));"); - else - writer.WriteLine($"_ = {parameterName} ?? throw new ArgumentNullException(nameof({parameterName}));"); - } - switch(codeElement.MethodKind) { - case CodeMethodKind.Serializer: - WriteSerializerBody(inherits, codeElement, parentClass, writer); + var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); + var parentClass = codeElement.Parent as CodeClass; + var inherits = parentClass.StartBlock is CodeClass.Declaration declaration && declaration.Inherits != null && !parentClass.IsErrorDefinition; + var isVoid = conventions.VoidTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); + WriteMethodDocumentation(codeElement, writer); + WriteMethodPrototype(codeElement, writer, returnType, inherits, isVoid); + writer.IncreaseIndent(); + 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); + var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam); + foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) { + var parameterName = parameter.Name.ToFirstCharacterLowerCase(); + if(nameof(String).Equals(parameter.Type.Name, StringComparison.OrdinalIgnoreCase)) + writer.WriteLine($"if(string.IsNullOrEmpty({parameterName})) throw new ArgumentNullException(nameof({parameterName}));"); + else + writer.WriteLine($"_ = {parameterName} ?? throw new ArgumentNullException(nameof({parameterName}));"); + } + switch(codeElement.MethodKind) { + case CodeMethodKind.Serializer: + WriteSerializerBody(inherits, codeElement, parentClass, writer); + break; + case CodeMethodKind.RequestGenerator: + WriteRequestGeneratorBody(codeElement, requestParams, parentClass, writer); break; - case CodeMethodKind.RequestGenerator: - WriteRequestGeneratorBody(codeElement, requestParams, parentClass, writer); - break; - case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, requestParams, isVoid, returnType, writer); - break; - case CodeMethodKind.Deserializer: - WriteDeserializerBody(codeElement, parentClass, writer); - break; - case CodeMethodKind.ClientConstructor: - WriteConstructorBody(parentClass, codeElement, writer); - WriteApiConstructorBody(parentClass, codeElement, writer); - break; - case CodeMethodKind.Constructor: - case CodeMethodKind.RawUrlConstructor: - WriteConstructorBody(parentClass, codeElement, writer); - break; - case CodeMethodKind.RequestBuilderWithParameters: - WriteRequestBuilderBody(parentClass, codeElement, writer); - break; - case CodeMethodKind.Getter: - case CodeMethodKind.Setter: - throw new InvalidOperationException("getters and setters are automatically added on fields in dotnet"); - case CodeMethodKind.RequestBuilderBackwardCompatibility: - throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); - default: - writer.WriteLine("return null;"); + case CodeMethodKind.RequestExecutor: + WriteRequestExecutorBody(codeElement, requestParams, isVoid, returnType, writer); break; - } - writer.DecreaseIndent(); - writer.WriteLine("}"); - } - private void WriteRequestBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer) - { - var importSymbol = conventions.GetTypeString(codeElement.ReturnType, parentClass); - conventions.AddRequestBuilderBody(parentClass, importSymbol, writer, prefix: "return ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); - } - private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { - var requestAdapterProperty = parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter); - var backingStoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.BackingStore)); - var requestAdapterPropertyName = requestAdapterProperty.Name.ToFirstCharacterUpperCase(); - WriteSerializationRegistration(method.SerializerModules, writer, "RegisterDefaultSerializer"); - WriteSerializationRegistration(method.DeserializerModules, writer, "RegisterDefaultDeserializer"); - writer.WriteLine($"{requestAdapterPropertyName}.BaseUrl = \"{method.BaseUrl}\";"); - if(backingStoreParameter != null) - writer.WriteLine($"{requestAdapterPropertyName}.EnableBackingStore({backingStoreParameter.Name});"); + case CodeMethodKind.Deserializer: + WriteDeserializerBody(inherits, codeElement, parentClass, writer); + break; + case CodeMethodKind.ClientConstructor: + WriteConstructorBody(parentClass, codeElement, writer); + WriteApiConstructorBody(parentClass, codeElement, writer); + break; + case CodeMethodKind.Constructor: + case CodeMethodKind.RawUrlConstructor: + WriteConstructorBody(parentClass, codeElement, writer); + break; + case CodeMethodKind.RequestBuilderWithParameters: + WriteRequestBuilderBody(parentClass, codeElement, writer); + break; + case CodeMethodKind.Getter: + case CodeMethodKind.Setter: + throw new InvalidOperationException("getters and setters are automatically added on fields in dotnet"); + case CodeMethodKind.RequestBuilderBackwardCompatibility: + throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); + default: + writer.WriteLine("return null;"); + break; } - private static void WriteSerializationRegistration(List serializationClassNames, LanguageWriter writer, string methodName) { - if(serializationClassNames != null) - foreach(var serializationClassName in serializationClassNames) - writer.WriteLine($"ApiClientBuilder.{methodName}<{serializationClassName}>();"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + } + private void WriteRequestBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer) + { + var importSymbol = conventions.GetTypeString(codeElement.ReturnType, parentClass); + conventions.AddRequestBuilderBody(parentClass, importSymbol, writer, prefix: "return ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); + } + private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { + var requestAdapterProperty = parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter); + var backingStoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.BackingStore)); + var requestAdapterPropertyName = requestAdapterProperty.Name.ToFirstCharacterUpperCase(); + WriteSerializationRegistration(method.SerializerModules, writer, "RegisterDefaultSerializer"); + WriteSerializationRegistration(method.DeserializerModules, writer, "RegisterDefaultDeserializer"); + writer.WriteLine($"{requestAdapterPropertyName}.BaseUrl = \"{method.BaseUrl}\";"); + if(backingStoreParameter != null) + writer.WriteLine($"{requestAdapterPropertyName}.EnableBackingStore({backingStoreParameter.Name});"); + } + private static void WriteSerializationRegistration(List serializationClassNames, LanguageWriter writer, string methodName) { + if(serializationClassNames != null) + foreach(var serializationClassName in serializationClassNames) + writer.WriteLine($"ApiClientBuilder.{methodName}<{serializationClassName}>();"); + } + private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer) { + foreach(var propWithDefault in parentClass + .Properties + .Where(x => !string.IsNullOrEmpty(x.DefaultValue)) + .OrderByDescending(x => x.PropertyKind) + .ThenBy(x => x.Name)) { + writer.WriteLine($"{propWithDefault.Name.ToFirstCharacterUpperCase()} = {propWithDefault.DefaultValue};"); } - private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer) { - foreach(var propWithDefault in parentClass - .Properties - .Where(x => !string.IsNullOrEmpty(x.DefaultValue)) - .OrderByDescending(x => x.PropertyKind) - .ThenBy(x => x.Name)) { - writer.WriteLine($"{propWithDefault.Name.ToFirstCharacterUpperCase()} = {propWithDefault.DefaultValue};"); + if(parentClass.IsOfKind(CodeClassKind.RequestBuilder)) { + if(currentMethod.IsOfKind(CodeMethodKind.Constructor)) { + var pathParametersParam = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.PathParameters)); + conventions.AddParametersAssignment(writer, + pathParametersParam.Type, + pathParametersParam.Name.ToFirstCharacterLowerCase(), + currentMethod.Parameters + .Where(x => x.IsOfKind(CodeParameterKind.Path)) + .Select(x => (x.Type, x.UrlTemplateParameterName, x.Name.ToFirstCharacterLowerCase())) + .ToArray()); + AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName); } - if(parentClass.IsOfKind(CodeClassKind.RequestBuilder)) { - if(currentMethod.IsOfKind(CodeMethodKind.Constructor)) { - var pathParametersParam = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.PathParameters)); - conventions.AddParametersAssignment(writer, - pathParametersParam.Type, - pathParametersParam.Name.ToFirstCharacterLowerCase(), - currentMethod.Parameters - .Where(x => x.IsOfKind(CodeParameterKind.Path)) - .Select(x => (x.Type, x.UrlTemplateParameterName, x.Name.ToFirstCharacterLowerCase())) - .ToArray()); - AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName); - } - else if(currentMethod.IsOfKind(CodeMethodKind.RawUrlConstructor)) { - var pathParametersProp = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); - var rawUrlParam = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.RawUrl)); - conventions.AddParametersAssignment(writer, - pathParametersProp.Type, - string.Empty, - (rawUrlParam.Type, Constants.RawUrlParameterName, rawUrlParam.Name.ToFirstCharacterLowerCase())); - AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName); - } - AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.RequestAdapter, CodePropertyKind.RequestAdapter, writer); + else if(currentMethod.IsOfKind(CodeMethodKind.RawUrlConstructor)) { + var pathParametersProp = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); + var rawUrlParam = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.RawUrl)); + conventions.AddParametersAssignment(writer, + pathParametersProp.Type, + string.Empty, + (rawUrlParam.Type, Constants.RawUrlParameterName, rawUrlParam.Name.ToFirstCharacterLowerCase())); + AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName); } + AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.RequestAdapter, CodePropertyKind.RequestAdapter, writer); } - private static void AssignPropertyFromParameter(CodeClass parentClass, CodeMethod currentMethod, CodeParameterKind parameterKind, CodePropertyKind propertyKind, LanguageWriter writer, string variableName = default) { - var property = parentClass.GetPropertyOfKind(propertyKind); - if(property != null) { - var parameter = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(parameterKind)); - if(!string.IsNullOrEmpty(variableName)) - writer.WriteLine($"{property.Name.ToFirstCharacterUpperCase()} = {variableName};"); - else if(parameter != null) - writer.WriteLine($"{property.Name.ToFirstCharacterUpperCase()} = {parameter.Name};"); - } + } + private static void AssignPropertyFromParameter(CodeClass parentClass, CodeMethod currentMethod, CodeParameterKind parameterKind, CodePropertyKind propertyKind, LanguageWriter writer, string variableName = default) { + var property = parentClass.GetPropertyOfKind(propertyKind); + if(property != null) { + var parameter = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(parameterKind)); + if(!string.IsNullOrEmpty(variableName)) + writer.WriteLine($"{property.Name.ToFirstCharacterUpperCase()} = {variableName};"); + else if(parameter != null) + writer.WriteLine($"{property.Name.ToFirstCharacterUpperCase()} = {parameter.Name};"); } - private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer) { - var hideParentMember = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; - var parentSerializationInfo = hideParentMember ? $"(base.{codeElement.Name.ToFirstCharacterUpperCase()}())" : string.Empty; - writer.WriteLine($"return new Dictionary>{parentSerializationInfo} {{"); - writer.IncreaseIndent(); - foreach(var otherProp in parentClass - .Properties - .Where(x => x.IsOfKind(CodePropertyKind.Custom)) - .OrderBy(x => x.Name)) { - writer.WriteLine($"{{\"{otherProp.SerializationName ?? otherProp.Name.ToFirstCharacterLowerCase()}\", (o,n) => {{ (o as {parentClass.Name.ToFirstCharacterUpperCase()}).{otherProp.Name.ToFirstCharacterUpperCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeElement)}(); }} }},"); - } - writer.DecreaseIndent(); - writer.WriteLine("};"); + } + private void WriteDeserializerBody(bool shouldHide, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer) { + var parentSerializationInfo = shouldHide ? $"(base.{codeElement.Name.ToFirstCharacterUpperCase()}())" : string.Empty; + writer.WriteLine($"return new Dictionary>{parentSerializationInfo} {{"); + writer.IncreaseIndent(); + foreach(var otherProp in parentClass + .Properties + .Where(x => x.IsOfKind(CodePropertyKind.Custom)) + .OrderBy(x => x.Name)) { + writer.WriteLine($"{{\"{otherProp.SerializationName ?? otherProp.Name.ToFirstCharacterLowerCase()}\", (o,n) => {{ (o as {parentClass.Name.ToFirstCharacterUpperCase()}).{otherProp.Name.ToFirstCharacterUpperCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeElement)}(); }} }},"); } - private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod method) { - var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; - var propertyType = conventions.GetTypeString(propType, method, false); - if(propType is CodeType currentType) { - if(isCollection) - if(currentType.TypeDefinition == null) - return $"GetCollectionOfPrimitiveValues<{propertyType}>().ToList"; - else if (currentType.TypeDefinition is CodeEnum enumType) - return $"GetCollectionOfEnumValues<{enumType.Name.ToFirstCharacterUpperCase()}>().ToList"; - else - return $"GetCollectionOfObjectValues<{propertyType}>().ToList"; + writer.DecreaseIndent(); + writer.WriteLine("};"); + } + private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod method) { + var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; + var propertyType = conventions.GetTypeString(propType, method, false); + if(propType is CodeType currentType) { + if(isCollection) + if(currentType.TypeDefinition == null) + return $"GetCollectionOfPrimitiveValues<{propertyType}>().ToList"; else if (currentType.TypeDefinition is CodeEnum enumType) - return $"GetEnumValue<{enumType.Name.ToFirstCharacterUpperCase()}>"; - } - return propertyType switch - { - "byte[]" => "GetByteArrayValue", - _ when conventions.IsPrimitiveType(propertyType) => $"Get{propertyType.TrimEnd(CSharpConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value", - _ => $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>", - }; - } - private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, 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); - var generatorMethodName = (codeElement.Parent as CodeClass) - .Methods - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) - ?.Name; - var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } - .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 RequestAdapter.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler, cancellationToken);"); + return $"GetCollectionOfEnumValues<{enumType.Name.ToFirstCharacterUpperCase()}>().ToList"; + else + return $"GetCollectionOfObjectValues<{propertyType}>().ToList"; + else if (currentType.TypeDefinition is CodeEnum enumType) + return $"GetEnumValue<{enumType.Name.ToFirstCharacterUpperCase()}>"; } - private const string RequestInfoVarName = "requestInfo"; - private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer) { - if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - - var operationName = codeElement.HttpMethod.ToString(); - var urlTemplateParamsProperty = currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); - var urlTemplateProperty = currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate); - var requestAdapterProperty = currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter); - writer.WriteLine($"var {RequestInfoVarName} = new RequestInformation {{"); + return propertyType switch + { + "byte[]" => "GetByteArrayValue", + _ when conventions.IsPrimitiveType(propertyType) => $"Get{propertyType.TrimEnd(CSharpConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value", + _ => $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>", + }; + } + private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, 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); + var generatorMethodName = (codeElement.Parent as CodeClass) + .Methods + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) + ?.Name; + var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options } + .Select(x => x?.Name).Where(x => x != null).Aggregate((x,y) => $"{x}, {y}"); + writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); + var errorMappingVarName = "default"; + if(codeElement.ErrorMappings.Any()) { + errorMappingVarName = "errorMapping"; + writer.WriteLine($"var {errorMappingVarName} = new Dictionary> {{"); writer.IncreaseIndent(); - writer.WriteLines($"HttpMethod = Method.{operationName?.ToUpperInvariant()},", - $"UrlTemplate = {GetPropertyCall(urlTemplateProperty, "string.Empty")},", - $"PathParameters = {GetPropertyCall(urlTemplateParamsProperty, "string.Empty")},"); - writer.DecreaseIndent(); - writer.WriteLine("};"); - if(requestParams.requestBody != null) { - if(requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({requestParams.requestBody.Name});"); - else - writer.WriteLine($"{RequestInfoVarName}.SetContentFromParsable({requestAdapterProperty.Name.ToFirstCharacterUpperCase()}, \"{codeElement.ContentType}\", {requestParams.requestBody.Name});"); + foreach(var errorMapping in codeElement.ErrorMappings) { + writer.WriteLine($"{{\"{errorMapping.Key.ToUpperInvariant()}\", () => new {errorMapping.Value.Name.ToFirstCharacterUpperCase()}()}},"); } - if(requestParams.queryString != null) { - writer.WriteLine($"if ({requestParams.queryString.Name} != null) {{"); - writer.IncreaseIndent(); - writer.WriteLines($"var qParams = new {operationName?.ToFirstCharacterUpperCase()}QueryParameters();", - $"{requestParams.queryString.Name}.Invoke(qParams);", - $"qParams.AddQueryParameters({RequestInfoVarName}.QueryParameters);"); - writer.DecreaseIndent(); - writer.WriteLine("}"); - } - if(requestParams.headers != null) - writer.WriteLine($"{requestParams.headers.Name}?.Invoke({RequestInfoVarName}.Headers);"); - if(requestParams.options != null) - writer.WriteLine($"{RequestInfoVarName}.AddRequestOptions({requestParams.options.Name}?.ToArray());"); - writer.WriteLine($"return {RequestInfoVarName};"); + writer.CloseBlock("};"); } - private static string GetPropertyCall(CodeProperty property, string defaultValue) => property == null ? defaultValue : $"{property.Name.ToFirstCharacterUpperCase()}"; - private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass parentClass, LanguageWriter writer) { - var additionalDataProperty = parentClass.GetPropertyOfKind(CodePropertyKind.AdditionalData); - if(shouldHide) - writer.WriteLine("base.Serialize(writer);"); - foreach(var otherProp in parentClass - .Properties - .Where(x => x.IsOfKind(CodePropertyKind.Custom)) - .OrderBy(x => x.Name)) { - writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type, method)}(\"{otherProp.SerializationName ?? otherProp.Name.ToFirstCharacterLowerCase()}\", {otherProp.Name.ToFirstCharacterUpperCase()});"); - } - if(additionalDataProperty != null) - writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});"); + writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await RequestAdapter.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler, {errorMappingVarName}, cancellationToken);"); + } + private const string RequestInfoVarName = "requestInfo"; + private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer) { + if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); + + var operationName = codeElement.HttpMethod.ToString(); + var urlTemplateParamsProperty = currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); + var urlTemplateProperty = currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate); + var requestAdapterProperty = currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter); + writer.WriteLine($"var {RequestInfoVarName} = new RequestInformation {{"); + writer.IncreaseIndent(); + writer.WriteLines($"HttpMethod = Method.{operationName?.ToUpperInvariant()},", + $"UrlTemplate = {GetPropertyCall(urlTemplateProperty, "string.Empty")},", + $"PathParameters = {GetPropertyCall(urlTemplateParamsProperty, "string.Empty")},"); + writer.DecreaseIndent(); + writer.WriteLine("};"); + if(requestParams.requestBody != null) { + if(requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) + writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({requestParams.requestBody.Name});"); + else + writer.WriteLine($"{RequestInfoVarName}.SetContentFromParsable({requestAdapterProperty.Name.ToFirstCharacterUpperCase()}, \"{codeElement.ContentType}\", {requestParams.requestBody.Name});"); } - private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { - if(isVoid) return "SendNoContentAsync"; - else if(isStream || conventions.IsPrimitiveType(returnType)) - if(isCollection) - return $"SendPrimitiveCollectionAsync<{returnType.StripArraySuffix()}>"; - else - return $"SendPrimitiveAsync<{returnType}>"; - else if (isCollection) return $"SendCollectionAsync<{returnType.StripArraySuffix()}>"; - else return $"SendAsync<{returnType}>"; + if(requestParams.queryString != null) { + writer.WriteLine($"if ({requestParams.queryString.Name} != null) {{"); + writer.IncreaseIndent(); + writer.WriteLines($"var qParams = new {operationName?.ToFirstCharacterUpperCase()}QueryParameters();", + $"{requestParams.queryString.Name}.Invoke(qParams);", + $"qParams.AddQueryParameters({RequestInfoVarName}.QueryParameters);"); + writer.DecreaseIndent(); + writer.WriteLine("}"); } - private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer) { - var isDescriptionPresent = !string.IsNullOrEmpty(code.Description); - var parametersWithDescription = code.Parameters.Where(x => !string.IsNullOrEmpty(code.Description)); - if (isDescriptionPresent || parametersWithDescription.Any()) { - writer.WriteLine($"{conventions.DocCommentPrefix}"); - if(isDescriptionPresent) - writer.WriteLine($"{conventions.DocCommentPrefix}{code.Description}"); - foreach(var paramWithDescription in parametersWithDescription.OrderBy(x => x.Name)) - writer.WriteLine($"{conventions.DocCommentPrefix}{paramWithDescription.Description}"); - writer.WriteLine($"{conventions.DocCommentPrefix}"); - } + if(requestParams.headers != null) + writer.WriteLine($"{requestParams.headers.Name}?.Invoke({RequestInfoVarName}.Headers);"); + if(requestParams.options != null) + writer.WriteLine($"{RequestInfoVarName}.AddRequestOptions({requestParams.options.Name}?.ToArray());"); + writer.WriteLine($"return {RequestInfoVarName};"); + } + private static string GetPropertyCall(CodeProperty property, string defaultValue) => property == null ? defaultValue : $"{property.Name.ToFirstCharacterUpperCase()}"; + private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass parentClass, LanguageWriter writer) { + var additionalDataProperty = parentClass.GetPropertyOfKind(CodePropertyKind.AdditionalData); + if(shouldHide) + writer.WriteLine("base.Serialize(writer);"); + foreach(var otherProp in parentClass + .Properties + .Where(x => x.IsOfKind(CodePropertyKind.Custom)) + .OrderBy(x => x.Name)) { + writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type, method)}(\"{otherProp.SerializationName ?? otherProp.Name.ToFirstCharacterLowerCase()}\", {otherProp.Name.ToFirstCharacterUpperCase()});"); } - private static readonly CodeParameterOrderComparer parameterOrderComparer = new(); - private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string returnType, bool inherits, bool isVoid) { - var staticModifier = code.IsStatic ? "static " : string.Empty; - var hideModifier = inherits && code.IsSerializationMethod ? "new " : string.Empty; - var genericTypePrefix = isVoid ? string.Empty : "<"; - var genericTypeSuffix = code.IsAsync && !isVoid ? ">": string.Empty; - var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.RawUrlConstructor); - var asyncPrefix = code.IsAsync ? "async Task" + genericTypePrefix : string.Empty; - var voidCorrectedTaskReturnType = code.IsAsync && isVoid ? string.Empty : returnType; - if(code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor)) - voidCorrectedTaskReturnType = $"IEnumerable<{voidCorrectedTaskReturnType.StripArraySuffix()}>"; - // TODO: Task type should be moved into the refiner - var completeReturnType = isConstructor ? - string.Empty : - $"{asyncPrefix}{voidCorrectedTaskReturnType}{genericTypeSuffix} "; - var baseSuffix = string.Empty; - if(isConstructor && inherits) - baseSuffix = " : base()"; - var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer).Select(p=> conventions.GetParameterSignature(p, code)).ToList()); - var methodName = isConstructor ? code.Parent.Name.ToFirstCharacterUpperCase() : code.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{conventions.GetAccessModifier(code.Access)} {staticModifier}{hideModifier}{completeReturnType}{methodName}({parameters}){baseSuffix} {{"); + if(additionalDataProperty != null) + writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});"); + } + private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) { + if(isVoid) return "SendNoContentAsync"; + else if(isStream || conventions.IsPrimitiveType(returnType)) + if(isCollection) + return $"SendPrimitiveCollectionAsync<{returnType.StripArraySuffix()}>"; + else + return $"SendPrimitiveAsync<{returnType}>"; + else if (isCollection) return $"SendCollectionAsync<{returnType.StripArraySuffix()}>"; + else return $"SendAsync<{returnType}>"; + } + private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer) { + var isDescriptionPresent = !string.IsNullOrEmpty(code.Description); + var parametersWithDescription = code.Parameters.Where(x => !string.IsNullOrEmpty(code.Description)); + if (isDescriptionPresent || parametersWithDescription.Any()) { + writer.WriteLine($"{conventions.DocCommentPrefix}"); + if(isDescriptionPresent) + writer.WriteLine($"{conventions.DocCommentPrefix}{code.Description}"); + foreach(var paramWithDescription in parametersWithDescription.OrderBy(x => x.Name)) + writer.WriteLine($"{conventions.DocCommentPrefix}{paramWithDescription.Description}"); + writer.WriteLine($"{conventions.DocCommentPrefix}"); } - private string GetSerializationMethodName(CodeTypeBase propType, CodeMethod method) { - var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; - var propertyType = conventions.GetTypeString(propType, method, false); - if(propType is CodeType currentType) { - if(isCollection) - if(currentType.TypeDefinition == null) - return $"WriteCollectionOfPrimitiveValues<{propertyType}>"; - else if(currentType.TypeDefinition is CodeEnum enumType) - return $"WriteCollectionOfEnumValues<{enumType.Name.ToFirstCharacterUpperCase()}>"; - else - return $"WriteCollectionOfObjectValues<{propertyType}>"; - else if (currentType.TypeDefinition is CodeEnum enumType) - return $"WriteEnumValue<{enumType.Name.ToFirstCharacterUpperCase()}>"; - - } - return propertyType switch - { - "byte[]" => "WriteByteArrayValue", - _ when conventions.IsPrimitiveType(propertyType) => $"Write{propertyType.TrimEnd(CSharpConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value", - _ => $"WriteObjectValue<{propertyType.ToFirstCharacterUpperCase()}>", - }; + } + private static readonly CodeParameterOrderComparer parameterOrderComparer = new(); + private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string returnType, bool inherits, bool isVoid) { + var staticModifier = code.IsStatic ? "static " : string.Empty; + var hideModifier = inherits && code.IsSerializationMethod ? "new " : string.Empty; + var genericTypePrefix = isVoid ? string.Empty : "<"; + var genericTypeSuffix = code.IsAsync && !isVoid ? ">": string.Empty; + var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.RawUrlConstructor); + var asyncPrefix = code.IsAsync ? "async Task" + genericTypePrefix : string.Empty; + var voidCorrectedTaskReturnType = code.IsAsync && isVoid ? string.Empty : returnType; + if(code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor)) + voidCorrectedTaskReturnType = $"IEnumerable<{voidCorrectedTaskReturnType.StripArraySuffix()}>"; + // TODO: Task type should be moved into the refiner + var completeReturnType = isConstructor ? + string.Empty : + $"{asyncPrefix}{voidCorrectedTaskReturnType}{genericTypeSuffix} "; + var baseSuffix = string.Empty; + if(isConstructor && inherits) + baseSuffix = " : base()"; + var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer).Select(p=> conventions.GetParameterSignature(p, code)).ToList()); + var methodName = isConstructor ? code.Parent.Name.ToFirstCharacterUpperCase() : code.Name.ToFirstCharacterUpperCase(); + writer.WriteLine($"{conventions.GetAccessModifier(code.Access)} {staticModifier}{hideModifier}{completeReturnType}{methodName}({parameters}){baseSuffix} {{"); + } + private string GetSerializationMethodName(CodeTypeBase propType, CodeMethod method) { + var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; + var propertyType = conventions.GetTypeString(propType, method, false); + if(propType is CodeType currentType) { + if(isCollection) + if(currentType.TypeDefinition == null) + return $"WriteCollectionOfPrimitiveValues<{propertyType}>"; + else if(currentType.TypeDefinition is CodeEnum enumType) + return $"WriteCollectionOfEnumValues<{enumType.Name.ToFirstCharacterUpperCase()}>"; + else + return $"WriteCollectionOfObjectValues<{propertyType}>"; + else if (currentType.TypeDefinition is CodeEnum enumType) + return $"WriteEnumValue<{enumType.Name.ToFirstCharacterUpperCase()}>"; + } + return propertyType switch + { + "byte[]" => "WriteByteArrayValue", + _ when conventions.IsPrimitiveType(propertyType) => $"Write{propertyType.TrimEnd(CSharpConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value", + _ => $"WriteObjectValue<{propertyType.ToFirstCharacterUpperCase()}>", + }; } } diff --git a/src/Kiota.Builder/Writers/Go/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Go/CodeClassDeclarationWriter.cs index 064b0df0b0..c28b4e4029 100644 --- a/src/Kiota.Builder/Writers/Go/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeClassDeclarationWriter.cs @@ -33,11 +33,12 @@ public override void WriteCodeElement(CodeClass.Declaration codeElement, Languag writer.WriteLines(")", string.Empty); } var className = codeElement.Name.ToFirstCharacterUpperCase(); - conventions.WriteShortDescription($"{className} {(codeElement.Parent as CodeClass).Description.ToFirstCharacterLowerCase()}", writer); + var currentClass = codeElement.Parent as CodeClass; + conventions.WriteShortDescription($"{className} {currentClass.Description.ToFirstCharacterLowerCase()}", writer); writer.WriteLine($"type {className} struct {{"); writer.IncreaseIndent(); if(codeElement.Inherits?.AllTypes?.Any() ?? false) { - var parentTypeName = conventions.GetTypeString(codeElement.Inherits.AllTypes.First(), codeElement.Parent.Parent, true, false); + var parentTypeName = conventions.GetTypeString(codeElement.Inherits.AllTypes.First(), currentClass, true, false); writer.WriteLine($"{parentTypeName}"); } } diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs index 8c45066b85..fe9410eb3d 100644 --- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs @@ -16,7 +16,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri if(!(codeElement.Parent is CodeClass)) throw new InvalidOperationException("the parent of a method should be a class"); var parentClass = codeElement.Parent as CodeClass; - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; + var inherits = parentClass.StartBlock is CodeClass.Declaration declaration && declaration.Inherits != null && !parentClass.IsErrorDefinition; var returnType = conventions.GetTypeString(codeElement.ReturnType, parentClass); WriteMethodPrototype(codeElement, writer, returnType, parentClass); writer.IncreaseIndent(); @@ -30,10 +30,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var requestParams = new RequestProperties(requestOptionsParam, requestBodyParam, queryStringParam, headersParam, optionsParam); switch(codeElement.MethodKind) { case CodeMethodKind.Serializer: - WriteSerializerBody(parentClass, writer); + WriteSerializerBody(parentClass, writer, inherits); break; case CodeMethodKind.Deserializer: - WriteDeserializerBody(codeElement, parentClass, writer); + WriteDeserializerBody(codeElement, parentClass, writer, inherits); break; case CodeMethodKind.IndexerBackwardCompatibility: WriteIndexerBody(codeElement, parentClass, writer, returnType); @@ -101,14 +101,13 @@ private void WriteRequestBuilderBody(CodeClass parentClass, CodeMethod codeEleme var importSymbol = conventions.GetTypeString(codeElement.ReturnType, parentClass); conventions.AddRequestBuilderBody(parentClass, importSymbol, writer, pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path))); } - private void WriteSerializerBody(CodeClass parentClass, LanguageWriter writer) { + private void WriteSerializerBody(CodeClass parentClass, LanguageWriter writer, bool inherits) { var additionalDataProperty = parentClass.GetPropertyOfKind(CodePropertyKind.AdditionalData); - var shouldDeclareErrorVar = true; + var shouldDeclareErrorVar = !inherits; if(parentClass.StartBlock is CodeClass.Declaration declaration && - declaration.Inherits != null) { + inherits) { writer.WriteLine($"err := m.{declaration.Inherits.Name.ToFirstCharacterUpperCase()}.Serialize(writer)"); WriteReturnError(writer); - shouldDeclareErrorVar = false; } foreach(var otherProp in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)) { var accessorName = otherProp.IsNameEscaped && !string.IsNullOrEmpty(otherProp.SerializationName) ? otherProp.SerializationName : otherProp.Name.ToFirstCharacterLowerCase(); @@ -211,13 +210,11 @@ private void WriteSerializationRegistration(List serializationModules, L } private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer, bool inherits) { writer.WriteLine($"m := &{parentClass.Name.ToFirstCharacterUpperCase()}{{"); - if(inherits && - parentClass.StartBlock is CodeClass.Declaration declaration) { + if(parentClass.StartBlock is CodeClass.Declaration declaration && + (inherits || parentClass.IsErrorDefinition)) { writer.IncreaseIndent(); var parentClassName = declaration.Inherits.Name.ToFirstCharacterUpperCase(); - var parentClassFullSymbol = conventions.GetTypeString(declaration.Inherits, parentClass, false, false); - var moduleName = parentClassFullSymbol.Contains(dot) ? $"{parentClassFullSymbol.Split(dot).First()}." : string.Empty; - writer.WriteLine($"{parentClassName}: *{moduleName}New{parentClassName}(),"); + writer.WriteLine($"{parentClassName}: *{conventions.GetImportedStaticMethodName(declaration.Inherits, parentClass)}(),"); writer.DecreaseIndent(); } writer.CloseBlock(decreaseIndent: false); @@ -276,10 +273,10 @@ private void WriteIndexerBody(CodeMethod codeElement, CodeClass parentClass, Lan (idParameter.Type, codeElement.OriginalIndexer.ParameterName, "id")); conventions.AddRequestBuilderBody(parentClass, returnType, writer, urlTemplateVarName: conventions.TempDictionaryVarName); } - private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer) { + private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer, bool inherits) { var fieldToSerialize = parentClass.GetPropertiesOfKind(CodePropertyKind.Custom); if(parentClass.StartBlock is CodeClass.Declaration declaration && - declaration.Inherits != null) + inherits) writer.WriteLine($"res := m.{declaration.Inherits.Name.ToFirstCharacterUpperCase()}.{codeElement.Name.ToFirstCharacterUpperCase()}()"); else writer.WriteLine($"res := make({codeElement.ReturnType.Name})"); @@ -340,20 +337,25 @@ _ when string.IsNullOrEmpty(returnType) => "SendNoContentAsync", WriteGeneratorMethodCall(codeElement, requestParams, writer, $"{RequestInfoVarName}, err := "); WriteReturnError(writer, returnType); var parsableImportSymbol = GetConversionHelperMethodImport(codeElement.Parent as CodeClass, "Parsable"); - var typeString = conventions.GetTypeString(codeElement.ReturnType, codeElement.Parent, false, false)?.Split(dot); - var importSymbol = typeString == null || typeString.Length < 2 ? string.Empty : typeString.First() + dot; var constructorFunction = returnType switch { _ when isVoid => string.Empty, _ when isPrimitive || isBinary => $"\"{returnType.TrimCollectionAndPointerSymbols()}\", ", - _ => $"func () {parsableImportSymbol} {{ return {importSymbol}New{typeString.Last()}() }}, ", + _ => $"func () {parsableImportSymbol} {{ return {conventions.GetImportedStaticMethodName(codeElement.ReturnType, codeElement.Parent)}() }}, ", }; + var errorMappingVarName = "nil"; + if(codeElement.ErrorMappings.Any()) { + errorMappingVarName = "errorMapping"; + writer.WriteLine($"{errorMappingVarName} := {conventions.AbstractionsHash}.ErrorMappings {{"); + writer.IncreaseIndent(); + foreach(var errorMapping in codeElement.ErrorMappings) { + writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": func() {parsableImportSymbol} {{ return {conventions.GetImportedStaticMethodName(errorMapping.Value, codeElement.Parent)}() }},"); + } + writer.CloseBlock(); + } var assignmentPrefix = isVoid ? "err =" : "res, err :="; - if(responseHandlerParam != null) - writer.WriteLine($"{assignmentPrefix} m.requestAdapter.{sendMethodName}(*{RequestInfoVarName}, {constructorFunction}{responseHandlerParam.Name})"); - else - writer.WriteLine($"{assignmentPrefix} m.requestAdapter.{sendMethodName}(*{RequestInfoVarName}, {constructorFunction}nil)"); + writer.WriteLine($"{assignmentPrefix} m.requestAdapter.{sendMethodName}(*{RequestInfoVarName}, {constructorFunction}{responseHandlerParam?.Name ?? "nil"}, {errorMappingVarName})"); WriteReturnError(writer, returnType); var valueVarName = string.Empty; if(codeElement.ReturnType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None) { @@ -448,16 +450,15 @@ private string GetDeserializationMethodName(CodeTypeBase propType, CodeClass par var propertyTypeName = conventions.GetTypeString(propType, parentClass, false, false); var propertyTypeNameWithoutImportSymbol = conventions.TranslateType(propType, false); if(propType is CodeType currentType) { - var importSymbol = propertyTypeName.Contains(dot) ? $"{propertyTypeName.Split(dot).First().TrimCollectionAndPointerSymbols()}." : string.Empty; if(isCollection) if(currentType.TypeDefinition == null) return $"n.GetCollectionOfPrimitiveValues(\"{propertyTypeName.ToFirstCharacterLowerCase()}\")"; else if (currentType.TypeDefinition is CodeEnum) - return $"n.GetCollectionOfEnumValues({importSymbol}Parse{propertyTypeNameWithoutImportSymbol.ToFirstCharacterUpperCase()})"; + return $"n.GetCollectionOfEnumValues({conventions.GetImportedStaticMethodName(propType, parentClass, "Parse")})"; else return $"n.GetCollectionOfObjectValues({GetTypeFactory(propType, parentClass, propertyTypeNameWithoutImportSymbol)})"; else if (currentType.TypeDefinition is CodeEnum currentEnum) { - return $"n.GetEnum{(currentEnum.Flags ? "Set" : string.Empty)}Value({importSymbol}Parse{propertyTypeNameWithoutImportSymbol.ToFirstCharacterUpperCase()})"; + return $"n.GetEnum{(currentEnum.Flags ? "Set" : string.Empty)}Value({conventions.GetImportedStaticMethodName(propType, parentClass, "Parse")})"; } } return propertyTypeNameWithoutImportSymbol switch { @@ -468,13 +469,10 @@ _ when conventions.StreamTypeName.Equals(propertyTypeNameWithoutImportSymbol, St _ => $"n.GetObjectValue({GetTypeFactory(propType, parentClass, propertyTypeNameWithoutImportSymbol)})", }; } - private static readonly char dot = '.'; private string GetTypeFactory(CodeTypeBase propTypeBase, CodeClass parentClass, string propertyTypeName) { if(propTypeBase is CodeType propType) { - var importSymbol = conventions.GetTypeString(propType, parentClass, false, false); - var importNS = importSymbol.Contains(dot) ? importSymbol.Split(dot).First() + dot : string.Empty; var parsableSymbol = GetConversionHelperMethodImport(parentClass, "Parsable"); - return $"func () {parsableSymbol} {{ return {importNS}New{propertyTypeName.ToFirstCharacterUpperCase()}() }}"; + return $"func () {parsableSymbol} {{ return {conventions.GetImportedStaticMethodName(propType, parentClass)}() }}"; } else return GetTypeFactory(propTypeBase.AllTypes.First(), parentClass, propertyTypeName); } private void WriteSerializationMethodCall(CodeTypeBase propType, CodeClass parentClass, string serializationKey, string valueGet, bool shouldDeclareErrorVar, LanguageWriter writer) { diff --git a/src/Kiota.Builder/Writers/Go/GoConventionService.cs b/src/Kiota.Builder/Writers/Go/GoConventionService.cs index 4ce2a71e93..08296cd79a 100644 --- a/src/Kiota.Builder/Writers/Go/GoConventionService.cs +++ b/src/Kiota.Builder/Writers/Go/GoConventionService.cs @@ -24,6 +24,12 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen { return $"{parameter.Name.ToFirstCharacterLowerCase()} {GetTypeString(parameter.Type, targetElement)}"; } + private static readonly char dot = '.'; + public string GetImportedStaticMethodName(CodeTypeBase code, CodeElement targetElement, string methodPrefix = "New") { + var typeString = GetTypeString(code, targetElement, false, false)?.Split(dot); + var importSymbol = typeString == null || typeString.Length < 2 ? string.Empty : typeString.First() + dot; + return $"{importSymbol}{methodPrefix}{typeString.Last().ToFirstCharacterUpperCase()}"; + } public override string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true) => GetTypeString(code, targetElement, includeCollectionInformation, true); public string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation, bool addPointerSymbol) diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index c0caedcd72..f1b93edc32 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -26,7 +26,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteMethodPrototype(codeElement, writer, returnType); writer.IncreaseIndent(); var parentClass = codeElement.Parent as CodeClass; - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; + var inherits = parentClass.StartBlock is CodeClass.Declaration declaration && declaration.Inherits != null && !parentClass.IsErrorDefinition; var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); @@ -35,10 +35,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri AddNullChecks(codeElement, writer); switch(codeElement.MethodKind) { case CodeMethodKind.Serializer: - WriteSerializerBody(parentClass, codeElement, writer); + WriteSerializerBody(parentClass, codeElement, writer, inherits); break; case CodeMethodKind.Deserializer: - WriteDeserializerBody(codeElement, codeElement, parentClass, writer); + WriteDeserializerBody(codeElement, codeElement, parentClass, writer, inherits); break; case CodeMethodKind.IndexerBackwardCompatibility: WriteIndexerBody(codeElement, parentClass, writer, returnType); @@ -196,8 +196,7 @@ private void WriteIndexerBody(CodeMethod codeElement, CodeClass parentClass, Lan (codeElement.OriginalIndexer.IndexType, codeElement.OriginalIndexer.ParameterName, "id")); conventions.AddRequestBuilderBody(parentClass, returnType, writer, conventions.TempDictionaryVarName); } - private void WriteDeserializerBody(CodeMethod codeElement, CodeMethod method, CodeClass parentClass, LanguageWriter writer) { - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; + private void WriteDeserializerBody(CodeMethod codeElement, CodeMethod method, CodeClass parentClass, LanguageWriter writer, bool inherits) { var fieldToSerialize = parentClass.GetPropertiesOfKind(CodePropertyKind.Custom); writer.WriteLine($"return new HashMap<>({(inherits ? "super." + codeElement.Name.ToFirstCharacterLowerCase()+ "()" : fieldToSerialize.Count())}) {{{{"); if(fieldToSerialize.Any()) { @@ -222,10 +221,20 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ WriteGeneratorMethodCall(codeElement, requestParams, writer, $"final RequestInformation {RequestInfoVarName} = "); var sendMethodName = GetSendRequestMethodName(codeElement.ReturnType.IsCollection, returnType); var responseHandlerParam = codeElement.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.ResponseHandler)); + var errorMappingVarName = "null"; + if(codeElement.ErrorMappings.Any()) { + errorMappingVarName = "errorMapping"; + writer.WriteLine($"final HashMap> {errorMappingVarName} = new HashMap>({codeElement.ErrorMappings.Count}) {{{{"); + writer.IncreaseIndent(); + foreach(var errorMapping in codeElement.ErrorMappings) { + writer.WriteLine($"put(\"{errorMapping.Key.ToUpperInvariant()}\", {errorMapping.Value.Name.ToFirstCharacterUpperCase()}.class);"); + } + writer.CloseBlock("}};"); + } if(codeElement.Parameters.Any(x => x.IsOfKind(CodeParameterKind.ResponseHandler))) - writer.WriteLine($"return this.requestAdapter.{sendMethodName}({RequestInfoVarName}, {returnType}.class, {responseHandlerParam.Name});"); + writer.WriteLine($"return this.requestAdapter.{sendMethodName}({RequestInfoVarName}, {returnType}.class, {responseHandlerParam.Name}, {errorMappingVarName});"); else - writer.WriteLine($"return this.requestAdapter.{sendMethodName}({RequestInfoVarName}, {returnType}.class, null);"); + writer.WriteLine($"return this.requestAdapter.{sendMethodName}({RequestInfoVarName}, {returnType}.class, null, {errorMappingVarName});"); writer.DecreaseIndent(); writer.WriteLine("} catch (URISyntaxException ex) {"); writer.IncreaseIndent(); @@ -306,9 +315,9 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req writer.WriteLine($"return {RequestInfoVarName};"); } private static string GetPropertyCall(CodeProperty property, string defaultValue) => property == null ? defaultValue : $"{property.Name.ToFirstCharacterLowerCase()}"; - private void WriteSerializerBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { + private void WriteSerializerBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer, bool inherits) { var additionalDataProperty = parentClass.GetPropertyOfKind(CodePropertyKind.AdditionalData); - if((parentClass.StartBlock as CodeClass.Declaration).Inherits != null) + if(inherits) writer.WriteLine("super.serialize(writer);"); foreach(var otherProp in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)) { var accessorName = otherProp.IsNameEscaped && !string.IsNullOrEmpty(otherProp.SerializationName) ? otherProp.SerializationName : otherProp.Name.ToFirstCharacterLowerCase(); @@ -335,7 +344,10 @@ CodeMethodKind.Setter when (code.AccessedProperty?.IsNameEscaped ?? false) && !s _ => code.Name.ToFirstCharacterLowerCase() }; var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer).Select(p=> conventions.GetParameterSignature(p, code)).ToList()); - var throwableDeclarations = code.IsOfKind(CodeMethodKind.RequestGenerator) ? "throws URISyntaxException ": string.Empty; + var throwableDeclarations = code.MethodKind switch { + CodeMethodKind.RequestGenerator => "throws URISyntaxException ", + _ => string.Empty + }; var collectionCorrectedReturnType = code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor) ? $"Iterable<{returnType.StripArraySuffix()}>" : returnType; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index c82b588967..ae8b16d73b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -23,7 +23,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteMethodPrototype(codeElement, writer, returnType, isVoid); writer.IncreaseIndent(); var parentClass = codeElement.Parent as CodeClass; - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; + var inherits = parentClass.StartBlock is CodeClass.Declaration declaration && declaration.Inherits != null && !parentClass.IsErrorDefinition; var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter); var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers); @@ -39,7 +39,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri WriteIndexerBody(codeElement, parentClass, returnType, writer); break; case CodeMethodKind.Deserializer: - WriteDeserializerBody(codeElement, parentClass, writer); + WriteDeserializerBody(codeElement, parentClass, writer, inherits); break; case CodeMethodKind.Serializer: WriteSerializerBody(inherits, parentClass, writer); @@ -105,8 +105,10 @@ private static void WriteSerializationRegistration(List serializationMod writer.WriteLine($"{methodName}({module});"); } private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer, bool inherits) { - if(inherits) + if(inherits || parentClass.IsErrorDefinition) writer.WriteLine("super();"); + if(parentClass.IsErrorDefinition && parentClass.StartBlock is CodeClass.Declaration declaration) + writer.WriteLine($"Object.setPrototypeOf(this, {declaration.Inherits.Name.ToFirstCharacterUpperCase()}.prototype);"); var propertiesWithDefaultValues = new List { CodePropertyKind.AdditionalData, CodePropertyKind.BackingStore, @@ -176,8 +178,7 @@ private static void WriteDefaultMethodBody(CodeMethod codeElement, LanguageWrite var promiseSuffix = codeElement.IsAsync ? ")" : string.Empty; writer.WriteLine($"return {promisePrefix}{(codeElement.ReturnType.Name.Equals("string") ? "''" : "{} as any")}{promiseSuffix};"); } - private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer) { - var inherits = (parentClass.StartBlock as CodeClass.Declaration).Inherits != null; + private void WriteDeserializerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer, bool inherits) { writer.WriteLine($"return new Map void>([{(inherits ? $"...super.{codeElement.Name.ToFirstCharacterLowerCase()}()," : string.Empty)}"); writer.IncreaseIndent(); var parentClassName = parentClass.Name.ToFirstCharacterUpperCase(); @@ -208,7 +209,17 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(codeElement, returnType); var genericTypeForSendMethod = GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnTypeWithoutCollectionSymbol); var newFactoryParameter = GetTypeFactory(isVoid, isStream, returnTypeWithoutCollectionSymbol); - writer.WriteLine($"return this.requestAdapter?.{genericTypeForSendMethod}(requestInfo,{newFactoryParameter} responseHandler) ?? Promise.reject(new Error('http core is null'));"); + var errorMappingVarName = "undefined"; + if(codeElement.ErrorMappings.Any()) { + errorMappingVarName = "errorMapping"; + writer.WriteLine($"const {errorMappingVarName}: Record Parsable> = {{"); + writer.IncreaseIndent(); + foreach(var errorMapping in codeElement.ErrorMappings) { + writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": {errorMapping.Value.Name.ToFirstCharacterUpperCase()},"); + } + writer.CloseBlock("};"); + } + writer.WriteLine($"return this.requestAdapter?.{genericTypeForSendMethod}(requestInfo,{newFactoryParameter} responseHandler, {errorMappingVarName}) ?? Promise.reject(new Error('http core is null'));"); } private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, string fullTypeName) { if(!codeElement.ReturnType.IsCollection) return fullTypeName; diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 16c3c23d09..c9ad5d1052 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -508,4 +508,219 @@ public void MapsDuration(){ var progressProp = codeModel.FindChildByName("progress", true); Assert.Equal("TimeSpan", progressProp.Type.Name); } + [Fact] + public void AddsErrorMapping(){ + var node = OpenApiUrlTreeNode.Create(); + node.Attach("tasks", new OpenApiPathItem() { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse() + { + Content = + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "progress", new OpenApiSchema{ + Type = "string", + } + } + } + } + } + } + }, + ["4XX"] = new OpenApiResponse() + { + Content = + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "errorId", new OpenApiSchema{ + Type = "string", + } + } + } + } + } + } + }, + ["5XX"] = new OpenApiResponse() + { + Content = + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "serviceErrorId", new OpenApiSchema{ + Type = "string", + } + } + } + } + } + } + }, + ["401"] = new OpenApiResponse() + { + Content = + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "authenticationRealm", new OpenApiSchema{ + Type = "string", + } + } + } + } + } + } + } + } + } + } + }, "default"); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }); + var codeModel = builder.CreateSourceModel(node); + var executorMethod = codeModel.FindChildByName("get", true); + Assert.NotNull(executorMethod); + Assert.NotEmpty(executorMethod.ErrorMappings); + Assert.Contains("4XX", executorMethod.ErrorMappings.Keys); + Assert.Contains("401", executorMethod.ErrorMappings.Keys); + Assert.Contains("5XX", executorMethod.ErrorMappings.Keys); + var errorType401 = codeModel.FindChildByName("tasks401Error", true); + Assert.NotNull(errorType401); + Assert.True(errorType401.IsErrorDefinition); + Assert.NotNull(errorType401.FindChildByName("authenticationRealm", true)); + var errorType4XX = codeModel.FindChildByName("tasks4XXError", true); + Assert.NotNull(errorType4XX); + Assert.True(errorType4XX.IsErrorDefinition); + Assert.NotNull(errorType4XX.FindChildByName("errorId", true)); + var errorType5XX = codeModel.FindChildByName("tasks5XXError", true); + Assert.NotNull(errorType5XX); + Assert.True(errorType5XX.IsErrorDefinition); + Assert.NotNull(errorType5XX.FindChildByName("serviceErrorId", true)); + + } + [Fact] + public void DoesntAddSuffixesToErrorTypesWhenComponents(){ + var errorSchema = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "errorId", new OpenApiSchema { + Type = "string" + } + } + }, + Reference = new OpenApiReference { + Id = "microsoft.graph.error", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var errorResponse = new OpenApiResponse() + { + Content = + { + ["application/json"] = new OpenApiMediaType() + { + Schema = errorSchema + } + }, + Reference = new OpenApiReference { + Id = "microsoft.graph.error", + Type = ReferenceType.Response + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument() { + Paths = new OpenApiPaths() { + ["tasks"] = new OpenApiPathItem() { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse() + { + Content = + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "progress", new OpenApiSchema{ + Type = "string", + } + } + } + } + } + } + }, + ["4XX"] = errorResponse, + ["5XX"] = errorResponse, + ["401"] = errorResponse + } + } + } + } + }, + Components = new OpenApiComponents() { + Schemas = new Dictionary { + { + "microsoft.graph.error", errorSchema + } + }, + Responses = new Dictionary { + { + "microsoft.graph.error", errorResponse + } + } + }, + }; + var node = OpenApiUrlTreeNode.Create(document, "default"); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }); + var codeModel = builder.CreateSourceModel(node); + var executorMethod = codeModel.FindChildByName("get", true); + Assert.NotNull(executorMethod); + Assert.NotEmpty(executorMethod.ErrorMappings); + Assert.Contains("4XX", executorMethod.ErrorMappings.Keys); + Assert.Contains("401", executorMethod.ErrorMappings.Keys); + Assert.Contains("5XX", executorMethod.ErrorMappings.Keys); + var errorType = codeModel.FindChildByName("Error", true); + Assert.NotNull(errorType); + Assert.True(errorType.IsErrorDefinition); + Assert.NotNull(errorType.FindChildByName("errorId", true)); + + Assert.Null(codeModel.FindChildByName("tasks401Error", true)); + Assert.Null(codeModel.FindChildByName("tasks4XXError", true)); + Assert.Null(codeModel.FindChildByName("tasks5XXError", true)); + } + } diff --git a/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs index 4b6566d7aa..22331aba28 100644 --- a/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs @@ -1,190 +1,249 @@ -using System.Linq; +using System; +using System.Linq; using Xunit; -namespace Kiota.Builder.Refiners.Tests { - public class CSharpLanguageRefinerTests { - private readonly CodeNamespace root = CodeNamespace.InitRootNamespace(); - #region CommonLanguageRefinerTests - [Fact] - public void DoesNotEscapesReservedKeywordsForClassOrPropertyKind() { - // Arrange - var model = root.AddClass(new CodeClass { - Name = "break", // this a keyword - ClassKind = CodeClassKind.Model, - }).First(); - var property = model.AddProperty(new CodeProperty - { - Name = "alias",// this a keyword - Type = new CodeType - { - Name = "string" - } - }).First(); - // Act - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); - // Assert - Assert.Equal("break", model.Name); - Assert.DoesNotContain("@", model.Name); // classname will be capitalized - Assert.Equal("alias", property.Name); - Assert.DoesNotContain("@", property.Name); // classname will be capitalized - } - [Fact] - public void ConvertsUnionTypesToWrapper() { - var model = root.AddClass(new CodeClass { - Name = "model", - ClassKind = CodeClassKind.Model - }).First(); - var union = new CodeUnionType { - Name = "union", - }; - union.AddType(new () { - Name = "type1", - }, new() { - Name = "type2" - }); - var property = model.AddProperty(new CodeProperty { - Name = "deserialize", - PropertyKind = CodePropertyKind.Custom, - Type = union.Clone() as CodeTypeBase, - }).First(); - var method = model.AddMethod(new CodeMethod { - Name = "method", - ReturnType = union.Clone() as CodeTypeBase - }).First(); - var parameter = new CodeParameter { - Name = "param1", - Type = union.Clone() as CodeTypeBase - }; - var indexer = new CodeIndexer { - Name = "idx", - ReturnType = union.Clone() as CodeTypeBase, - }; - model.SetIndexer(indexer); - method.AddParameter(parameter); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); //using CSharp so the indexer doesn't get removed - Assert.True(property.Type is CodeType); - Assert.True(parameter.Type is CodeType); - Assert.True(method.ReturnType is CodeType); - Assert.True(indexer.ReturnType is CodeType); - } - [Fact] - public void MovesClassesWithNamespaceNamesUnderNamespace() { - var graphNS = root.AddNamespace("graph"); - var modelNS = graphNS.AddNamespace("graph.model"); - var model = graphNS.AddClass(new CodeClass { - Name = "model", - ClassKind = CodeClassKind.Model - }).First(); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); - Assert.Single(root.GetChildElements(true)); - Assert.Single(graphNS.GetChildElements(true)); - Assert.Single(modelNS.GetChildElements(true)); - Assert.Equal(modelNS, model.Parent); - } - [Fact] - public void KeepsCancellationParametersInRequestExecutors() - { - var model = root.AddClass(new CodeClass - { - Name = "model", - ClassKind = CodeClassKind.RequestBuilder - }).First(); - var method = model.AddMethod(new CodeMethod - { - Name = "getMethod", - MethodKind = CodeMethodKind.RequestExecutor, - ReturnType = new CodeType { - Name = "string" - } - }).First(); - var cancellationParam = new CodeParameter - { - Name = "cancelletionToken", - Optional = true, - ParameterKind = CodeParameterKind.Cancellation, - Description = "Cancellation token to use when cancelling requests", - Type = new CodeType { Name = "CancelletionToken", IsExternal = true }, - }; - method.AddParameter(cancellationParam); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); //using CSharp so the cancelletionToken doesn't get removed - Assert.True(method.Parameters.Any()); - Assert.Contains(cancellationParam, method.Parameters); - } - #endregion - #region CSharp - [Fact] - public void DisambiguatePropertiesWithClassNames() { - var model = root.AddClass(new CodeClass { - Name = "Model", - ClassKind = CodeClassKind.Model - }).First(); - var propToAdd = model.AddProperty(new CodeProperty{ - Name = "model", - Type = new CodeType { - Name = "string" - } - }).First(); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); - Assert.Equal("model_prop", propToAdd.Name); - Assert.Equal("model", propToAdd.SerializationName); - } - [Fact] - public void DisambiguatePropertiesWithClassNames_DoesntReplaceSerializationName() { - var serializationName = "serializationName"; - var model = root.AddClass(new CodeClass { - Name = "Model", - ClassKind = CodeClassKind.Model - }).First(); - var propToAdd = model.AddProperty(new CodeProperty{ - Name = "model", - Type = new CodeType { - Name = "string" +namespace Kiota.Builder.Refiners.Tests; +public class CSharpLanguageRefinerTests { + private readonly CodeNamespace root = CodeNamespace.InitRootNamespace(); + #region CommonLanguageRefinerTests + [Fact] + public void AddsExceptionInheritanceOnErrorClasses() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + + var declaration = model.StartBlock as CodeClass.Declaration; + + Assert.Contains("ApiException", declaration.Usings.Select(x => x.Name)); + Assert.Equal("ApiException", declaration.Inherits.Name); + } + [Fact] + public void FailsExceptionInheritanceOnErrorClassesWhichAlreadyInherit() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var declaration = model.StartBlock as CodeClass.Declaration; + declaration.Inherits = new CodeType { + Name = "SomeOtherModel" + }; + Assert.Throws(() => ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root)); + } + [Fact] + public void AddsUsingsForErrorTypesForRequestExecutor() { + var requestBuilder = root.AddClass(new CodeClass { + Name = "somerequestbuilder", + ClassKind = CodeClassKind.RequestBuilder, + }).First(); + var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed + var errorClass = subNS.AddClass(new CodeClass { + Name = "Error4XX", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var requestExecutor = requestBuilder.AddMethod(new CodeMethod { + Name = "get", + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType { + Name = "string" + }, + ErrorMappings = new () { + { "4XX", new CodeType { + Name = "Error4XX", + TypeDefinition = errorClass, + } }, - SerializationName = serializationName, - }).First(); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); - Assert.Equal(serializationName, propToAdd.SerializationName); - } - [Fact] - public void ReplacesDateOnlyByNativeType() + }, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + + var declaration = requestBuilder.StartBlock as CodeClass.Declaration; + + Assert.Contains("Error4XX", declaration.Usings.Select(x => x.Declaration?.Name)); + } + [Fact] + public void DoesNotEscapesReservedKeywordsForClassOrPropertyKind() { + // Arrange + var model = root.AddClass(new CodeClass { + Name = "break", // this a keyword + ClassKind = CodeClassKind.Model, + }).First(); + var property = model.AddProperty(new CodeProperty { - var model = root.AddClass(new CodeClass + Name = "alias",// this a keyword + Type = new CodeType { - Name = "model", - ClassKind = CodeClassKind.Model - }).First(); - var method = model.AddMethod(new CodeMethod - { - Name = "method", - ReturnType = new CodeType - { - Name = "DateOnly" - }, - }).First(); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); - Assert.NotEmpty(model.StartBlock.Usings); - Assert.Equal("Date", method.ReturnType.Name); - } - [Fact] - public void ReplacesTimeOnlyByNativeType() + Name = "string" + } + }).First(); + // Act + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + // Assert + Assert.Equal("break", model.Name); + Assert.DoesNotContain("@", model.Name); // classname will be capitalized + Assert.Equal("alias", property.Name); + Assert.DoesNotContain("@", property.Name); // classname will be capitalized + } + [Fact] + public void ConvertsUnionTypesToWrapper() { + var model = root.AddClass(new CodeClass { + Name = "model", + ClassKind = CodeClassKind.Model + }).First(); + var union = new CodeUnionType { + Name = "union", + }; + union.AddType(new () { + Name = "type1", + }, new() { + Name = "type2" + }); + var property = model.AddProperty(new CodeProperty { + Name = "deserialize", + PropertyKind = CodePropertyKind.Custom, + Type = union.Clone() as CodeTypeBase, + }).First(); + var method = model.AddMethod(new CodeMethod { + Name = "method", + ReturnType = union.Clone() as CodeTypeBase + }).First(); + var parameter = new CodeParameter { + Name = "param1", + Type = union.Clone() as CodeTypeBase + }; + var indexer = new CodeIndexer { + Name = "idx", + ReturnType = union.Clone() as CodeTypeBase, + }; + model.SetIndexer(indexer); + method.AddParameter(parameter); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); //using CSharp so the indexer doesn't get removed + Assert.True(property.Type is CodeType); + Assert.True(parameter.Type is CodeType); + Assert.True(method.ReturnType is CodeType); + Assert.True(indexer.ReturnType is CodeType); + } + [Fact] + public void MovesClassesWithNamespaceNamesUnderNamespace() { + var graphNS = root.AddNamespace("graph"); + var modelNS = graphNS.AddNamespace("graph.model"); + var model = graphNS.AddClass(new CodeClass { + Name = "model", + ClassKind = CodeClassKind.Model + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + Assert.Single(root.GetChildElements(true)); + Assert.Single(graphNS.GetChildElements(true)); + Assert.Single(modelNS.GetChildElements(true)); + Assert.Equal(modelNS, model.Parent); + } + [Fact] + public void KeepsCancellationParametersInRequestExecutors() + { + var model = root.AddClass(new CodeClass + { + Name = "model", + ClassKind = CodeClassKind.RequestBuilder + }).First(); + var method = model.AddMethod(new CodeMethod + { + Name = "getMethod", + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType { + Name = "string" + } + }).First(); + var cancellationParam = new CodeParameter { - var model = root.AddClass(new CodeClass + Name = "cancelletionToken", + Optional = true, + ParameterKind = CodeParameterKind.Cancellation, + Description = "Cancellation token to use when cancelling requests", + Type = new CodeType { Name = "CancelletionToken", IsExternal = true }, + }; + method.AddParameter(cancellationParam); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); //using CSharp so the cancelletionToken doesn't get removed + Assert.True(method.Parameters.Any()); + Assert.Contains(cancellationParam, method.Parameters); + } + #endregion + #region CSharp + [Fact] + public void DisambiguatePropertiesWithClassNames() { + var model = root.AddClass(new CodeClass { + Name = "Model", + ClassKind = CodeClassKind.Model + }).First(); + var propToAdd = model.AddProperty(new CodeProperty{ + Name = "model", + Type = new CodeType { + Name = "string" + } + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + Assert.Equal("model_prop", propToAdd.Name); + Assert.Equal("model", propToAdd.SerializationName); + } + [Fact] + public void DisambiguatePropertiesWithClassNames_DoesntReplaceSerializationName() { + var serializationName = "serializationName"; + var model = root.AddClass(new CodeClass { + Name = "Model", + ClassKind = CodeClassKind.Model + }).First(); + var propToAdd = model.AddProperty(new CodeProperty{ + Name = "model", + Type = new CodeType { + Name = "string" + }, + SerializationName = serializationName, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + Assert.Equal(serializationName, propToAdd.SerializationName); + } + [Fact] + public void ReplacesDateOnlyByNativeType() + { + var model = root.AddClass(new CodeClass + { + Name = "model", + ClassKind = CodeClassKind.Model + }).First(); + var method = model.AddMethod(new CodeMethod + { + Name = "method", + ReturnType = new CodeType { - Name = "model", - ClassKind = CodeClassKind.Model - }).First(); - var method = model.AddMethod(new CodeMethod + Name = "DateOnly" + }, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + Assert.NotEmpty(model.StartBlock.Usings); + Assert.Equal("Date", method.ReturnType.Name); + } + [Fact] + public void ReplacesTimeOnlyByNativeType() + { + var model = root.AddClass(new CodeClass + { + Name = "model", + ClassKind = CodeClassKind.Model + }).First(); + var method = model.AddMethod(new CodeMethod + { + Name = "method", + ReturnType = new CodeType { - Name = "method", - ReturnType = new CodeType - { - Name = "TimeOnly" - }, - }).First(); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); - Assert.NotEmpty(model.StartBlock.Usings); - Assert.Equal("Time", method.ReturnType.Name); - } - #endregion + Name = "TimeOnly" + }, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root); + Assert.NotEmpty(model.StartBlock.Usings); + Assert.Equal("Time", method.ReturnType.Name); } + #endregion } diff --git a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs index 39c7f8b73c..dcd7e00109 100644 --- a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs @@ -1,10 +1,76 @@ -using System.Linq; +using System; +using System.Linq; using Xunit; namespace Kiota.Builder.Refiners.Tests; public class GoLanguageRefinerTests { private readonly CodeNamespace root = CodeNamespace.InitRootNamespace(); #region CommonLangRefinerTests + [Fact] + public void AddsExceptionInheritanceOnErrorClasses() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root); + + var declaration = model.StartBlock as CodeClass.Declaration; + + Assert.Contains("ApiError", declaration.Usings.Select(x => x.Name)); + Assert.Equal("ApiError", declaration.Inherits.Name); + } + [Fact] + public void FailsExceptionInheritanceOnErrorClassesWhichAlreadyInherit() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var declaration = model.StartBlock as CodeClass.Declaration; + declaration.Inherits = new CodeType { + Name = "SomeOtherModel" + }; + Assert.Throws(() => ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root)); + } + [Fact] + public void AddsUsingsForErrorTypesForRequestExecutor() { + var main = root.AddNamespace("main"); + var models = main.AddNamespace($"{main.Name}.models"); + models.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + }); // so move to models namespace finds the models namespace + var requestBuilder = main.AddClass(new CodeClass { + Name = "somerequestbuilder", + ClassKind = CodeClassKind.RequestBuilder, + }).First(); + var subNS = models.AddNamespace($"{models.Name}.subns"); // otherwise the import gets trimmed + var errorClass = subNS.AddClass(new CodeClass { + Name = "Error4XX", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var requestExecutor = requestBuilder.AddMethod(new CodeMethod { + Name = "get", + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType { + Name = "string" + }, + ErrorMappings = new () { + { "4XX", new CodeType { + Name = "Error4XX", + TypeDefinition = errorClass, + } + }, + }, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root); + + var declaration = requestBuilder.StartBlock as CodeClass.Declaration; + + Assert.Contains("Error4XX", declaration.Usings.Select(x => x.Declaration?.Name)); + } [Fact] public void DoesNotKeepCancellationParametersInRequestExecutors() { diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs index b1d5aed24a..b688fdbfca 100644 --- a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Xunit; namespace Kiota.Builder.Refiners.Tests; @@ -6,6 +7,65 @@ public class JavaLanguageRefinerTests { private readonly CodeNamespace root = CodeNamespace.InitRootNamespace(); #region CommonLanguageRefinerTests [Fact] + public void AddsExceptionInheritanceOnErrorClasses() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Java }, root); + + var declaration = model.StartBlock as CodeClass.Declaration; + + Assert.Contains("ApiException", declaration.Usings.Select(x => x.Name)); + Assert.Equal("ApiException", declaration.Inherits.Name); + } + [Fact] + public void FailsExceptionInheritanceOnErrorClassesWhichAlreadyInherit() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var declaration = model.StartBlock as CodeClass.Declaration; + declaration.Inherits = new CodeType { + Name = "SomeOtherModel" + }; + Assert.Throws(() => ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Java }, root)); + } + [Fact] + public void AddsUsingsForErrorTypesForRequestExecutor() { + var requestBuilder = root.AddClass(new CodeClass { + Name = "somerequestbuilder", + ClassKind = CodeClassKind.RequestBuilder, + }).First(); + var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed + var errorClass = subNS.AddClass(new CodeClass { + Name = "Error4XX", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var requestExecutor = requestBuilder.AddMethod(new CodeMethod { + Name = "get", + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType { + Name = "string" + }, + ErrorMappings = new () { + { "4XX", new CodeType { + Name = "Error4XX", + TypeDefinition = errorClass, + } + }, + }, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Java }, root); + + var declaration = requestBuilder.StartBlock as CodeClass.Declaration; + + Assert.Contains("Error4XX", declaration.Usings.Select(x => x.Declaration?.Name)); + } + [Fact] public void EscapesReservedKeywordsInInternalDeclaration() { var model = root.AddClass(new CodeClass { Name = "break", diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 371982f997..d212b768b7 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Xunit; @@ -14,6 +15,67 @@ public TypeScriptLanguageRefinerTests() { }; graphNS.AddClass(parentClass); } +#region commonrefiner + [Fact] + public void AddsExceptionInheritanceOnErrorClasses() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + + var declaration = model.StartBlock as CodeClass.Declaration; + + Assert.Contains("ApiError", declaration.Usings.Select(x => x.Name)); + Assert.Equal("ApiError", declaration.Inherits.Name); + } + [Fact] + public void FailsExceptionInheritanceOnErrorClassesWhichAlreadyInherit() { + var model = root.AddClass(new CodeClass { + Name = "somemodel", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var declaration = model.StartBlock as CodeClass.Declaration; + declaration.Inherits = new CodeType { + Name = "SomeOtherModel" + }; + Assert.Throws(() => ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root)); + } + [Fact] + public void AddsUsingsForErrorTypesForRequestExecutor() { + var requestBuilder = root.AddClass(new CodeClass { + Name = "somerequestbuilder", + ClassKind = CodeClassKind.RequestBuilder, + }).First(); + var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed + var errorClass = subNS.AddClass(new CodeClass { + Name = "Error4XX", + ClassKind = CodeClassKind.Model, + IsErrorDefinition = true, + }).First(); + var requestExecutor = requestBuilder.AddMethod(new CodeMethod { + Name = "get", + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType { + Name = "string" + }, + ErrorMappings = new () { + { "4XX", new CodeType { + Name = "Error4XX", + TypeDefinition = errorClass, + } + }, + }, + }).First(); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + + var declaration = requestBuilder.StartBlock as CodeClass.Declaration; + + Assert.Contains("Error4XX", declaration.Usings.Select(x => x.Declaration?.Name)); + } +#endregion #region typescript private const string HttpCoreDefaultName = "IRequestAdapter"; private const string FactoryDefaultName = "ISerializationWriterFactory"; diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs index a57e2c4f00..a9a9cb660d 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs @@ -5,455 +5,484 @@ using Kiota.Builder.Tests; using Xunit; -namespace Kiota.Builder.Writers.CSharp.Tests { - public class CodeMethodWriterTests : IDisposable { - private const string DefaultPath = "./"; - private const string DefaultName = "name"; - private readonly StringWriter tw; - private readonly LanguageWriter writer; - private readonly CodeMethod method; - private readonly CodeClass parentClass; - private const string MethodName = "methodName"; - private const string ReturnTypeName = "Somecustomtype"; - private const string MethodDescription = "some description"; - private const string ParamDescription = "some parameter description"; - private const string ParamName = "paramName"; - public CodeMethodWriterTests() +namespace Kiota.Builder.Writers.CSharp.Tests; +public class CodeMethodWriterTests : IDisposable { + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeMethod method; + private readonly CodeClass parentClass; + private readonly CodeNamespace root; + private const string MethodName = "methodName"; + private const string ReturnTypeName = "Somecustomtype"; + private const string MethodDescription = "some description"; + private const string ParamDescription = "some parameter description"; + private const string ParamName = "paramName"; + public CodeMethodWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.CSharp, DefaultPath, DefaultName); + tw = new StringWriter(); + writer.SetTextWriter(tw); + root = CodeNamespace.InitRootNamespace(); + parentClass = new CodeClass { + Name = "parentClass" + }; + root.AddClass(parentClass); + method = new CodeMethod { + Name = MethodName, + }; + method.ReturnType = new CodeType { + Name = ReturnTypeName + }; + parentClass.AddMethod(method); + } + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + private void AddRequestProperties() { + parentClass.AddProperty(new CodeProperty { + Name = "RequestAdapter", + PropertyKind = CodePropertyKind.RequestAdapter, + }); + parentClass.AddProperty(new CodeProperty { + Name = "pathParameters", + PropertyKind = CodePropertyKind.PathParameters, + }); + parentClass.AddProperty(new CodeProperty { + Name = "urlTemplate", + PropertyKind = CodePropertyKind.UrlTemplate, + }); + } + private void AddSerializationProperties() { + var addData = parentClass.AddProperty(new CodeProperty { + Name = "additionalData", + PropertyKind = CodePropertyKind.AdditionalData, + }).First(); + addData.Type = new CodeType { + Name = "string" + }; + var dummyProp = parentClass.AddProperty(new CodeProperty { + Name = "dummyProp", + }).First(); + dummyProp.Type = new CodeType { + Name = "string" + }; + var dummyCollectionProp = parentClass.AddProperty(new CodeProperty { + Name = "dummyColl", + }).First(); + dummyCollectionProp.Type = new CodeType { + Name = "string", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + }; + var dummyComplexCollection = parentClass.AddProperty(new CodeProperty { + Name = "dummyComplexColl" + }).First(); + dummyComplexCollection.Type = new CodeType { + Name = "Complex", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + TypeDefinition = new CodeClass { + Name = "SomeComplexType" + } + }; + var dummyEnumProp = parentClass.AddProperty(new CodeProperty{ + Name = "dummyEnumCollection", + }).First(); + dummyEnumProp.Type = new CodeType { + Name = "SomeEnum", + TypeDefinition = new CodeEnum { + Name = "EnumType" + } + }; + } + private void AddInheritanceClass() { + (parentClass.StartBlock as CodeClass.Declaration).Inherits = new CodeType { + Name = "someParentClass" + }; + } + private void AddRequestBodyParameters() { + var stringType = new CodeType { + Name = "string", + }; + method.AddParameter(new CodeParameter { + Name = "h", + ParameterKind = CodeParameterKind.Headers, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "q", + ParameterKind = CodeParameterKind.QueryParameter, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "b", + ParameterKind = CodeParameterKind.RequestBody, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "r", + ParameterKind = CodeParameterKind.ResponseHandler, + Type = stringType, + }); + method.AddParameter(new CodeParameter { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); + method.AddParameter(new CodeParameter { - writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.CSharp, DefaultPath, DefaultName); - tw = new StringWriter(); - writer.SetTextWriter(tw); - var root = CodeNamespace.InitRootNamespace(); - parentClass = new CodeClass { - Name = "parentClass" - }; - root.AddClass(parentClass); - method = new CodeMethod { - Name = MethodName, - }; - method.ReturnType = new CodeType { - Name = ReturnTypeName - }; - parentClass.AddMethod(method); - } - public void Dispose() - { - tw?.Dispose(); - GC.SuppressFinalize(this); - } - private void AddRequestProperties() { - parentClass.AddProperty(new CodeProperty { - Name = "RequestAdapter", - PropertyKind = CodePropertyKind.RequestAdapter, - }); - parentClass.AddProperty(new CodeProperty { - Name = "pathParameters", - PropertyKind = CodePropertyKind.PathParameters, - }); - parentClass.AddProperty(new CodeProperty { - Name = "urlTemplate", - PropertyKind = CodePropertyKind.UrlTemplate, - }); - } - private void AddSerializationProperties() { - var addData = parentClass.AddProperty(new CodeProperty { - Name = "additionalData", - PropertyKind = CodePropertyKind.AdditionalData, - }).First(); - addData.Type = new CodeType { - Name = "string" - }; - var dummyProp = parentClass.AddProperty(new CodeProperty { - Name = "dummyProp", - }).First(); - dummyProp.Type = new CodeType { - Name = "string" - }; - var dummyCollectionProp = parentClass.AddProperty(new CodeProperty { - Name = "dummyColl", - }).First(); - dummyCollectionProp.Type = new CodeType { - Name = "string", - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, - }; - var dummyComplexCollection = parentClass.AddProperty(new CodeProperty { - Name = "dummyComplexColl" - }).First(); - dummyComplexCollection.Type = new CodeType { - Name = "Complex", - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, - TypeDefinition = new CodeClass { - Name = "SomeComplexType" - } - }; - var dummyEnumProp = parentClass.AddProperty(new CodeProperty{ - Name = "dummyEnumCollection", - }).First(); - dummyEnumProp.Type = new CodeType { - Name = "SomeEnum", - TypeDefinition = new CodeEnum { - Name = "EnumType" - } - }; - } - private void AddInheritanceClass() { - (parentClass.StartBlock as CodeClass.Declaration).Inherits = new CodeType { - Name = "someParentClass" - }; - } - private void AddRequestBodyParameters() { - var stringType = new CodeType { - Name = "string", - }; - method.AddParameter(new CodeParameter { - Name = "h", - ParameterKind = CodeParameterKind.Headers, - Type = stringType, - }); - method.AddParameter(new CodeParameter{ - Name = "q", - ParameterKind = CodeParameterKind.QueryParameter, - Type = stringType, - }); - method.AddParameter(new CodeParameter{ - Name = "b", - ParameterKind = CodeParameterKind.RequestBody, - Type = stringType, - }); - method.AddParameter(new CodeParameter{ - Name = "r", - ParameterKind = CodeParameterKind.ResponseHandler, - Type = stringType, - }); - method.AddParameter(new CodeParameter { - Name = "o", - ParameterKind = CodeParameterKind.Options, - Type = stringType, - }); - method.AddParameter(new CodeParameter - { - Name = "c", - ParameterKind = CodeParameterKind.Cancellation, - Type = stringType, - }); - } - [Fact] - public void WritesRequestBuilder() { - method.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void WritesRequestBodiesThrowOnNullHttpMethod() { - method.MethodKind = CodeMethodKind.RequestExecutor; - Assert.Throws(() => writer.Write(method)); - method.MethodKind = CodeMethodKind.RequestGenerator; - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void WritesRequestExecutorBody() { - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("var requestInfo", result); - Assert.Contains("SendAsync", result); - Assert.Contains(AsyncKeyword, result); - Assert.Contains("await", result); - Assert.Contains("cancellationToken", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestExecutorBodyForCollections() { - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("SendCollectionAsync", result); - Assert.Contains("cancellationToken", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestGeneratorBody() { - method.MethodKind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("var requestInfo = new RequestInformation", result); - Assert.Contains("HttpMethod = Method.GET", result); - Assert.Contains("UrlTemplate = ", result); - Assert.Contains("PathParameters = ", result); - Assert.Contains("h?.Invoke", result); - Assert.Contains("AddQueryParameters", result); - Assert.Contains("SetContentFromParsable", result); - Assert.Contains("AddRequestOptions", result); - Assert.Contains("return requestInfo;", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesInheritedDeSerializerBody() { - method.MethodKind = CodeMethodKind.Deserializer; - AddSerializationProperties(); - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("base.", result); - Assert.Contains("new", result); - } - [Fact] - public void WritesDeSerializerBody() { - method.MethodKind = CodeMethodKind.Deserializer; - AddSerializationProperties(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("GetStringValue", result); - Assert.Contains("GetCollectionOfPrimitiveValues", result); - Assert.Contains("GetCollectionOfObjectValues", result); - Assert.Contains("GetEnumValue", result); - } - [Fact] - public void WritesInheritedSerializerBody() { - method.MethodKind = CodeMethodKind.Serializer; - method.IsAsync = false; - AddSerializationProperties(); - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("base.Serialize(writer);", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesSerializerBody() { - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { - Name = "string" - }; - method.MethodKind = CodeMethodKind.Serializer; - method.IsAsync = false; - AddSerializationProperties(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("WriteStringValue", result); - Assert.Contains("WriteCollectionOfPrimitiveValues", result); - Assert.Contains("WriteCollectionOfObjectValues", result); - Assert.Contains("WriteEnumValue", result); - Assert.Contains("WriteAdditionalData(additionalData);", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesMethodAsyncDescription() { - - method.Description = MethodDescription; - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { - Name = "string" - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("/// ", result); - Assert.Contains(MethodDescription, result); - Assert.Contains("", result); - Assert.Contains(ParamName, result); - Assert.Contains(ParamDescription, result); - Assert.Contains("", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesMethodSyncDescription() { - - method.Description = MethodDescription; - method.IsAsync = false; - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { + Name = "c", + ParameterKind = CodeParameterKind.Cancellation, + Type = stringType, + }); + } + [Fact] + public void WritesRequestBuilder() { + method.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void WritesRequestBodiesThrowOnNullHttpMethod() { + method.MethodKind = CodeMethodKind.RequestExecutor; + Assert.Throws(() => writer.Write(method)); + method.MethodKind = CodeMethodKind.RequestGenerator; + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void WritesRequestExecutorBody() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + var error4XX = root.AddClass(new CodeClass{ + Name = "Error4XX", + }).First(); + var error5XX = root.AddClass(new CodeClass{ + Name = "Error5XX", + }).First(); + var error401 = root.AddClass(new CodeClass{ + Name = "Error401", + }).First(); + method.ErrorMappings = new () { + {"4XX", new CodeType {Name = "Error4XX", TypeDefinition = error4XX}}, + {"5XX", new CodeType {Name = "Error5XX", TypeDefinition = error5XX}}, + {"403", new CodeType {Name = "Error403", TypeDefinition = error401}}, + }; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("var requestInfo", result); + Assert.Contains("var errorMapping = new Dictionary>", result); + Assert.Contains("{\"4XX\", () => new Error4XX()},", result); + Assert.Contains("{\"5XX\", () => new Error5XX()},", result); + Assert.Contains("{\"403\", () => new Error403()},", result); + Assert.Contains("SendAsync", result); + Assert.Contains(AsyncKeyword, result); + Assert.Contains("await", result); + Assert.Contains("cancellationToken", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void DoesntCreateDictionaryOnEmptyErrorMapping() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("var errorMapping = new Dictionary>", result); + Assert.Contains("default", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestExecutorBodyForCollections() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("SendCollectionAsync", result); + Assert.Contains("cancellationToken", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBody() { + method.MethodKind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("var requestInfo = new RequestInformation", result); + Assert.Contains("HttpMethod = Method.GET", result); + Assert.Contains("UrlTemplate = ", result); + Assert.Contains("PathParameters = ", result); + Assert.Contains("h?.Invoke", result); + Assert.Contains("AddQueryParameters", result); + Assert.Contains("SetContentFromParsable", result); + Assert.Contains("AddRequestOptions", result); + Assert.Contains("return requestInfo;", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesInheritedDeSerializerBody() { + method.MethodKind = CodeMethodKind.Deserializer; + AddSerializationProperties(); + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("base.", result); + Assert.Contains("new", result); + } + [Fact] + public void WritesDeSerializerBody() { + method.MethodKind = CodeMethodKind.Deserializer; + AddSerializationProperties(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("GetStringValue", result); + Assert.Contains("GetCollectionOfPrimitiveValues", result); + Assert.Contains("GetCollectionOfObjectValues", result); + Assert.Contains("GetEnumValue", result); + } + [Fact] + public void WritesInheritedSerializerBody() { + method.MethodKind = CodeMethodKind.Serializer; + method.IsAsync = false; + AddSerializationProperties(); + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("base.Serialize(writer);", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesSerializerBody() { + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.MethodKind = CodeMethodKind.Serializer; + method.IsAsync = false; + AddSerializationProperties(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("WriteStringValue", result); + Assert.Contains("WriteCollectionOfPrimitiveValues", result); + Assert.Contains("WriteCollectionOfObjectValues", result); + Assert.Contains("WriteEnumValue", result); + Assert.Contains("WriteAdditionalData(additionalData);", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesMethodAsyncDescription() { + + method.Description = MethodDescription; + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.AddParameter(parameter); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("/// ", result); + Assert.Contains(MethodDescription, result); + Assert.Contains("", result); + Assert.Contains(ParamName, result); + Assert.Contains(ParamDescription, result); + Assert.Contains("", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesMethodSyncDescription() { + + method.Description = MethodDescription; + method.IsAsync = false; + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.AddParameter(parameter); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("@returns a Promise of", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void Defensive() { + var codeMethodWriter = new CodeMethodWriter(new CSharpConventionService()); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); + var originalParent = method.Parent; + method.Parent = CodeNamespace.InitRootNamespace(); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); + method.Parent = originalParent; + method.ReturnType = null; + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); + } + [Fact] + public void ThrowsIfParentIsNotClass() { + method.Parent = CodeNamespace.InitRootNamespace(); + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void ThrowsIfReturnTypeIsMissing() { + method.ReturnType = null; + Assert.Throws(() => writer.Write(method)); + } + private const string TaskPrefix = "Task<"; + private const string AsyncKeyword = "async"; + [Fact] + public void WritesReturnType() { + writer.Write(method); + var result = tw.ToString(); + Assert.Contains($"{AsyncKeyword} {TaskPrefix}{ReturnTypeName}> {MethodName.ToFirstCharacterUpperCase()}", result); // async default + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void DoesNotAddUndefinedOnNonNullableReturnType(){ + method.ReturnType.IsNullable = false; + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("?", result); + } + [Fact] + public void DoesNotAddAsyncInformationOnSyncMethods() { + method.IsAsync = false; + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain(TaskPrefix, result); + Assert.DoesNotContain(AsyncKeyword, result); + } + [Fact] + public void WritesPublicMethodByDefault() { + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("public ", result);// public default + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesPrivateMethod() { + method.Access = AccessModifier.Private; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("private ", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesProtectedMethod() { + method.Access = AccessModifier.Protected; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("protected ", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesPathParameterRequestBuilder() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; + method.AddParameter(new CodeParameter { + Name = "pathParam", + ParameterKind = CodeParameterKind.Path, + Type = new CodeType { Name = "string" - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("@returns a Promise of", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new CSharpConventionService()); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); - var originalParent = method.Parent; - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - method.Parent = originalParent; - method.ReturnType = null; - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - } - [Fact] - public void ThrowsIfParentIsNotClass() { - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void ThrowsIfReturnTypeIsMissing() { - method.ReturnType = null; - Assert.Throws(() => writer.Write(method)); - } - private const string TaskPrefix = "Task<"; - private const string AsyncKeyword = "async"; - [Fact] - public void WritesReturnType() { - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"{AsyncKeyword} {TaskPrefix}{ReturnTypeName}> {MethodName.ToFirstCharacterUpperCase()}", result); // async default - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void DoesNotAddUndefinedOnNonNullableReturnType(){ - method.ReturnType.IsNullable = false; - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("?", result); - } - [Fact] - public void DoesNotAddAsyncInformationOnSyncMethods() { - method.IsAsync = false; - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain(TaskPrefix, result); - Assert.DoesNotContain(AsyncKeyword, result); - } - [Fact] - public void WritesPublicMethodByDefault() { - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("public ", result);// public default - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesPrivateMethod() { - method.Access = AccessModifier.Private; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("private ", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesProtectedMethod() { - method.Access = AccessModifier.Protected; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("protected ", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesPathParameterRequestBuilder() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; - method.AddParameter(new CodeParameter { - Name = "pathParam", - ParameterKind = CodeParameterKind.Path, - Type = new CodeType { - Name = "string" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("RequestAdapter", result); - Assert.Contains("PathParameters", result); - Assert.Contains("pathParam", result); - Assert.Contains("return new", result); - } - [Fact] - public void WritesConstructor() { - method.MethodKind = CodeMethodKind.Constructor; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.AddProperty(new CodeProperty { - Name = propName, - DefaultValue = defaultValue, - PropertyKind = CodePropertyKind.UrlTemplate, - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = {defaultValue}", result); - } - [Fact] - public void WritesApiConstructor() { - method.MethodKind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty { - Name = "core", - PropertyKind = CodePropertyKind.RequestAdapter, - }).First(); - coreProp.Type = new CodeType { - Name = "RequestAdapter", - IsExternal = true, - }; - method.AddParameter(new CodeParameter { - Name = "core", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - method.DeserializerModules = new() {"com.microsoft.kiota.serialization.Deserializer"}; - method.SerializerModules = new() {"com.microsoft.kiota.serialization.Serializer"}; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains("RegisterDefaultSerializer", result); - Assert.Contains("RegisterDefaultDeserializer", result); - } - [Fact] - public void WritesApiConstructorWithBackingStore() { - method.MethodKind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty { - Name = "core", - PropertyKind = CodePropertyKind.RequestAdapter, - }).First(); - coreProp.Type = new CodeType { - Name = "RequestAdapter", - IsExternal = true, - }; - method.AddParameter(new CodeParameter { - Name = "core", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - var backingStoreParam = new CodeParameter { - Name = "backingStore", - ParameterKind = CodeParameterKind.BackingStore, - }; - backingStoreParam.Type = new CodeType { - Name = "IBackingStore", - IsExternal = true, - }; - method.AddParameter(backingStoreParam); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.CSharp, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(method); - 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)); - } - } -} + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("RequestAdapter", result); + Assert.Contains("PathParameters", result); + Assert.Contains("pathParam", result); + Assert.Contains("return new", result); + } + [Fact] + public void WritesConstructor() { + method.MethodKind = CodeMethodKind.Constructor; + var defaultValue = "someVal"; + var propName = "propWithDefaultValue"; + parentClass.AddProperty(new CodeProperty { + Name = propName, + DefaultValue = defaultValue, + PropertyKind = CodePropertyKind.UrlTemplate, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = {defaultValue}", result); + } + [Fact] + public void WritesApiConstructor() { + method.MethodKind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty { + Name = "core", + PropertyKind = CodePropertyKind.RequestAdapter, + }).First(); + coreProp.Type = new CodeType { + Name = "RequestAdapter", + IsExternal = true, + }; + method.AddParameter(new CodeParameter { + Name = "core", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + method.DeserializerModules = new() {"com.microsoft.kiota.serialization.Deserializer"}; + method.SerializerModules = new() {"com.microsoft.kiota.serialization.Serializer"}; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains("RegisterDefaultSerializer", result); + Assert.Contains("RegisterDefaultDeserializer", result); + } + [Fact] + public void WritesApiConstructorWithBackingStore() { + method.MethodKind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty { + Name = "core", + PropertyKind = CodePropertyKind.RequestAdapter, + }).First(); + coreProp.Type = new CodeType { + Name = "RequestAdapter", + IsExternal = true, + }; + method.AddParameter(new CodeParameter { + Name = "core", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + var backingStoreParam = new CodeParameter { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType { + Name = "IBackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.CSharp, DefaultPath, DefaultName); + tempWriter.SetTextWriter(tw); + tempWriter.Write(method); + 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/Go/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs index d4a9e63e2b..84a3876445 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs @@ -7,621 +7,652 @@ using Moq; using Xunit; -namespace Kiota.Builder.Writers.Go.Tests { - public class CodeMethodWriterTests : IDisposable { - private const string DefaultPath = "./"; - private const string DefaultName = "name"; - private readonly StringWriter tw; - private readonly LanguageWriter writer; - private readonly CodeMethod method; - private readonly CodeClass parentClass; - private readonly CodeNamespace root; - private const string MethodName = "methodName"; - private const string ReturnTypeName = "Somecustomtype"; - private const string MethodDescription = "some description"; - private const string ParamDescription = "some parameter description"; - private const string ParamName = "paramName"; - public CodeMethodWriterTests() - { - writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); - tw = new StringWriter(); - writer.SetTextWriter(tw); - root = CodeNamespace.InitRootNamespace(); - parentClass = new CodeClass { - Name = "parentClass" - }; - root.AddClass(parentClass); - method = new CodeMethod { - Name = MethodName, - }; - method.ReturnType = new CodeType { - Name = ReturnTypeName - }; - parentClass.AddMethod(method); - } - public void Dispose() - { - tw?.Dispose(); - GC.SuppressFinalize(this); - } - private void AddRequestProperties() { - parentClass.AddProperty(new CodeProperty { - Name = "requestAdapter", - PropertyKind = CodePropertyKind.RequestAdapter, - }); - parentClass.AddProperty(new CodeProperty { - Name = "pathParameters", - PropertyKind = CodePropertyKind.PathParameters, - Type = new CodeType { - Name = "string" - }, - }); - parentClass.AddProperty(new CodeProperty { - Name = "UrlTemplate", - PropertyKind = CodePropertyKind.UrlTemplate, - }); - } - private void AddSerializationProperties() { - var addData = parentClass.AddProperty(new CodeProperty { - Name = "additionalData", - PropertyKind = CodePropertyKind.AdditionalData, - }).First(); - addData.Type = new CodeType { - Name = "string" - }; - var dummyProp = parentClass.AddProperty(new CodeProperty { - Name = "dummyProp", - }).First(); - dummyProp.Type = new CodeType { +namespace Kiota.Builder.Writers.Go.Tests; +public class CodeMethodWriterTests : IDisposable { + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeMethod method; + private readonly CodeClass parentClass; + private readonly CodeNamespace root; + private const string MethodName = "methodName"; + private const string ReturnTypeName = "Somecustomtype"; + private const string MethodDescription = "some description"; + private const string ParamDescription = "some parameter description"; + private const string ParamName = "paramName"; + public CodeMethodWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); + tw = new StringWriter(); + writer.SetTextWriter(tw); + root = CodeNamespace.InitRootNamespace(); + parentClass = new CodeClass { + Name = "parentClass" + }; + root.AddClass(parentClass); + method = new CodeMethod { + Name = MethodName, + }; + method.ReturnType = new CodeType { + Name = ReturnTypeName + }; + parentClass.AddMethod(method); + } + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + private void AddRequestProperties() { + parentClass.AddProperty(new CodeProperty { + Name = "requestAdapter", + PropertyKind = CodePropertyKind.RequestAdapter, + }); + parentClass.AddProperty(new CodeProperty { + Name = "pathParameters", + PropertyKind = CodePropertyKind.PathParameters, + Type = new CodeType { Name = "string" - }; - var dummyCollectionProp = parentClass.AddProperty(new CodeProperty { - Name = "dummyColl", - }).First(); - dummyCollectionProp.Type = new CodeType { + }, + }); + parentClass.AddProperty(new CodeProperty { + Name = "UrlTemplate", + PropertyKind = CodePropertyKind.UrlTemplate, + }); + } + private void AddSerializationProperties() { + var addData = parentClass.AddProperty(new CodeProperty { + Name = "additionalData", + PropertyKind = CodePropertyKind.AdditionalData, + }).First(); + addData.Type = new CodeType { + Name = "string" + }; + var dummyProp = parentClass.AddProperty(new CodeProperty { + Name = "dummyProp", + }).First(); + dummyProp.Type = new CodeType { + Name = "string" + }; + var dummyCollectionProp = parentClass.AddProperty(new CodeProperty { + Name = "dummyColl", + }).First(); + dummyCollectionProp.Type = new CodeType { + Name = "string", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + }; + var dummyComplexCollection = parentClass.AddProperty(new CodeProperty { + Name = "dummyComplexColl" + }).First(); + dummyComplexCollection.Type = new CodeType { + Name = "Complex", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + TypeDefinition = new CodeClass { + Name = "SomeComplexType" + } + }; + var dummyEnumProp = parentClass.AddProperty(new CodeProperty{ + Name = "dummyEnumCollection", + }).First(); + dummyEnumProp.Type = new CodeType { + Name = "SomeEnum", + TypeDefinition = new CodeEnum { + Name = "EnumType" + } + }; + } + private void AddInheritanceClass() { + (parentClass.StartBlock as CodeClass.Declaration).Inherits = new CodeType { + Name = "someParentClass" + }; + } + private void AddRequestBodyParameters(CodeMethod target = default) { + var stringType = new CodeType { + Name = "string", + }; + target ??= method; + target.AddParameter(new CodeParameter { + Name = "h", + ParameterKind = CodeParameterKind.Headers, + Type = stringType, + }); + target.AddParameter(new CodeParameter{ + Name = "q", + ParameterKind = CodeParameterKind.QueryParameter, + Type = stringType, + }); + target.AddParameter(new CodeParameter{ + Name = "b", + ParameterKind = CodeParameterKind.RequestBody, + Type = stringType, + }); + target.AddParameter(new CodeParameter{ + Name = "r", + ParameterKind = CodeParameterKind.ResponseHandler, + Type = stringType, + }); + target.AddParameter(new CodeParameter { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); + } + [Fact] + public void WritesNullableVoidTypeForExecutor(){ + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType = new CodeType { + Name = "void", + }; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("(error)", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestBuilder() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.pathParameters", result); + Assert.Contains("m.requestAdapter", result); + Assert.Contains("return", result); + Assert.Contains("func (m", result); + Assert.Contains("New", result); + } + [Fact] + public void WritesRequestBodiesThrowOnNullHttpMethod() { + method.MethodKind = CodeMethodKind.RequestExecutor; + Assert.Throws(() => writer.Write(method)); + method.MethodKind = CodeMethodKind.RequestGenerator; + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void WritesRequestExecutorBody() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + var error4XX = root.AddClass(new CodeClass{ + Name = "Error4XX", + }).First(); + var error5XX = root.AddClass(new CodeClass{ + Name = "Error5XX", + }).First(); + var error401 = root.AddClass(new CodeClass{ + Name = "Error401", + }).First(); + method.ErrorMappings = new () { + {"4XX", new CodeType {Name = "Error4XX", TypeDefinition = error4XX}}, + {"5XX", new CodeType {Name = "Error5XX", TypeDefinition = error5XX}}, + {"403", new CodeType {Name = "Error403", TypeDefinition = error401}}, + }; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("requestInfo, err :=", result); + Assert.Contains($"errorMapping := {AbstractionsPackageHash}.ErrorMappings", result); + Assert.Contains($"\"4XX\": func() Parsable {{ return ", result); + Assert.Contains($"\"5XX\": func() Parsable {{ return ", result); + Assert.Contains($"\"403\": func() Parsable {{ return ", result); + Assert.Contains("NewError5XX", result); + Assert.Contains("NewError4XX", result); + Assert.Contains("NewError403", result); + Assert.Contains("m.requestAdapter.SendAsync", result); + Assert.Contains("return res.(", result); + Assert.Contains("err != nil", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void DoesntCreateDictionaryOnEmptyErrorMapping() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain($"errorMapping := {AbstractionsPackageHash}.ErrorMappings", result); + Assert.Contains("nil)", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + private const string AbstractionsPackageHash = "ida96af0f171bb75f894a4013a6b3146a4397c58f11adb81a2b7cbea9314783a9"; + [Fact] + public void WritesRequestGeneratorBody() { + var configurationMock = new Mock(); + var refiner = new GoRefiner(configurationMock.Object); + method.MethodKind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + var executor = parentClass.AddMethod(new CodeMethod { + Name = "executor", + HttpMethod = HttpMethod.Get, + MethodKind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType { + Name = "string", + IsExternal = true, + } + }).First(); + AddRequestBodyParameters(executor); + AddRequestBodyParameters(); + AddRequestProperties(); + refiner.Refine(parentClass.Parent as CodeNamespace); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains($"requestInfo := {AbstractionsPackageHash}.NewRequestInformation()", result); + Assert.Contains("requestInfo.UrlTemplate = ", result); + Assert.Contains("requestInfo.PathParameters", result); + Assert.Contains($"Method = {AbstractionsPackageHash}.GET", result); + Assert.Contains("err != nil", result); + Assert.Contains("H != nil", result); + Assert.Contains("requestInfo.Headers =", result); + Assert.Contains("Q != nil", result); + Assert.Contains("requestInfo.AddQueryParameters(*(options.Q))", result); + Assert.Contains("O) != 0", result); + Assert.Contains("requestInfo.AddRequestOptions(", result); + Assert.Contains("requestInfo.SetContentFromParsable(m.requestAdapter", result); + Assert.Contains("return requestInfo, nil", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesInheritedDeSerializerBody() { + method.MethodKind = CodeMethodKind.Deserializer; + method.IsAsync = false; + AddSerializationProperties(); + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.SomeParentClass.MethodName()", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesDeSerializerBody() { + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.MethodKind = CodeMethodKind.Deserializer; + method.IsAsync = false; + AddSerializationProperties(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("GetStringValue", result); + Assert.Contains("GetCollectionOfPrimitiveValues", result); + Assert.Contains("GetCollectionOfObjectValues", result); + Assert.Contains("GetEnumValue", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesInheritedSerializerBody() { + method.MethodKind = CodeMethodKind.Serializer; + method.IsAsync = false; + AddSerializationProperties(); + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.SomeParentClass.Serialize", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesSerializerBody() { + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.MethodKind = CodeMethodKind.Serializer; + method.IsAsync = false; + AddSerializationProperties(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("WriteStringValue", result); + Assert.Contains("WriteCollectionOfStringValues", result); + Assert.Contains("WriteCollectionOfObjectValues", result); + Assert.Contains("WriteAdditionalData(m.GetAdditionalData())", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact(Skip = "descriptions are not supported")] + public void WritesMethodSyncDescription() { + method.Description = MethodDescription; + method.IsAsync = false; + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.AddParameter(parameter); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("@return a CompletableFuture of", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void Defensive() { + var codeMethodWriter = new CodeMethodWriter(new GoConventionService()); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); + var originalParent = method.Parent; + method.Parent = CodeNamespace.InitRootNamespace(); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); + method.Parent = originalParent; + method.ReturnType = null; + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); + } + [Fact] + public void ThrowsIfParentIsNotClass() { + method.Parent = CodeNamespace.InitRootNamespace(); + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void ThrowsIfReturnTypeIsMissing() { + method.ReturnType = null; + Assert.Throws(() => writer.Write(method)); + } + private const string TaskPrefix = "func() ("; + [Fact] + public void WritesReturnType() { + writer.Write(method); + var result = tw.ToString(); + Assert.Contains($"{MethodName.ToFirstCharacterUpperCase()}()(*{ReturnTypeName}, error)", result);// async default + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void DoesNotAddAsyncInformationOnSyncMethods() { + method.IsAsync = false; + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain(TaskPrefix, result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesPublicMethodByDefault() { + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(MethodName.ToFirstCharacterUpperCase(), result);// public default + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesPrivateMethod() { + method.Access = AccessModifier.Private; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(MethodName.ToFirstCharacterLowerCase(), result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesProtectedMethod() { + method.Access = AccessModifier.Protected; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(MethodName.ToFirstCharacterLowerCase(), result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesIndexer() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.IndexerBackwardCompatibility; + method.OriginalIndexer = new () { + Name = "indx", + ParameterName = "id", + IndexType = new CodeType { Name = "string", - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, - }; - var dummyComplexCollection = parentClass.AddProperty(new CodeProperty { - Name = "dummyComplexColl" - }).First(); - dummyComplexCollection.Type = new CodeType { - Name = "Complex", - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, - TypeDefinition = new CodeClass { - Name = "SomeComplexType" - } - }; - var dummyEnumProp = parentClass.AddProperty(new CodeProperty{ - Name = "dummyEnumCollection", - }).First(); - dummyEnumProp.Type = new CodeType { - Name = "SomeEnum", - TypeDefinition = new CodeEnum { - Name = "EnumType" - } - }; - } - private void AddInheritanceClass() { - (parentClass.StartBlock as CodeClass.Declaration).Inherits = new CodeType { - Name = "someParentClass" - }; - } - private void AddRequestBodyParameters(CodeMethod target = default) { - var stringType = new CodeType { + IsNullable = true, + } + }; + method.AddParameter(new CodeParameter { + Name = "id", + ParameterKind = CodeParameterKind.Custom, + Type = new CodeType { Name = "string", - }; - target ??= method; - target.AddParameter(new CodeParameter { - Name = "h", - ParameterKind = CodeParameterKind.Headers, - Type = stringType, - }); - target.AddParameter(new CodeParameter{ - Name = "q", - ParameterKind = CodeParameterKind.QueryParameter, - Type = stringType, - }); - target.AddParameter(new CodeParameter{ - Name = "b", - ParameterKind = CodeParameterKind.RequestBody, - Type = stringType, - }); - target.AddParameter(new CodeParameter{ - Name = "r", - ParameterKind = CodeParameterKind.ResponseHandler, - Type = stringType, - }); - target.AddParameter(new CodeParameter { - Name = "o", - ParameterKind = CodeParameterKind.Options, - Type = stringType, - }); - } - [Fact] - public void WritesNullableVoidTypeForExecutor(){ - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - method.ReturnType = new CodeType { - Name = "void", - }; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("(error)", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestBuilder() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.pathParameters", result); - Assert.Contains("m.requestAdapter", result); - Assert.Contains("return", result); - Assert.Contains("func (m", result); - Assert.Contains("New", result); - } - [Fact] - public void WritesRequestBodiesThrowOnNullHttpMethod() { - method.MethodKind = CodeMethodKind.RequestExecutor; - Assert.Throws(() => writer.Write(method)); - method.MethodKind = CodeMethodKind.RequestGenerator; - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void WritesRequestExecutorBody() { - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("requestInfo, err :=", result); - Assert.Contains("m.requestAdapter.SendAsync", result); - Assert.Contains("return res.(", result); - Assert.Contains("err != nil", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - private const string AbstractionsPackageHash = "ida96af0f171bb75f894a4013a6b3146a4397c58f11adb81a2b7cbea9314783a9"; - [Fact] - public void WritesRequestGeneratorBody() { - var configurationMock = new Mock(); - var refiner = new GoRefiner(configurationMock.Object); - method.MethodKind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - var executor = parentClass.AddMethod(new CodeMethod { - Name = "executor", - HttpMethod = HttpMethod.Get, - MethodKind = CodeMethodKind.RequestExecutor, - ReturnType = new CodeType { - Name = "string", - IsExternal = true, - } - }).First(); - AddRequestBodyParameters(executor); - AddRequestBodyParameters(); - AddRequestProperties(); - refiner.Refine(parentClass.Parent as CodeNamespace); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"requestInfo := {AbstractionsPackageHash}.NewRequestInformation()", result); - Assert.Contains("requestInfo.UrlTemplate = ", result); - Assert.Contains("requestInfo.PathParameters", result); - Assert.Contains($"Method = {AbstractionsPackageHash}.GET", result); - Assert.Contains("err != nil", result); - Assert.Contains("H != nil", result); - Assert.Contains("requestInfo.Headers =", result); - Assert.Contains("Q != nil", result); - Assert.Contains("requestInfo.AddQueryParameters(*(options.Q))", result); - Assert.Contains("O) != 0", result); - Assert.Contains("requestInfo.AddRequestOptions(", result); - Assert.Contains("requestInfo.SetContentFromParsable(m.requestAdapter", result); - Assert.Contains("return requestInfo, nil", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesInheritedDeSerializerBody() { - method.MethodKind = CodeMethodKind.Deserializer; - method.IsAsync = false; - AddSerializationProperties(); - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.SomeParentClass.MethodName()", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesDeSerializerBody() { - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { + IsNullable = true, + }, + Optional = true + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.requestAdapter", result); + Assert.Contains("m.pathParameters", result); + Assert.Contains("= *id", result); + Assert.Contains("return", result); + Assert.Contains("New", result); + } + [Fact] + public void WritesPathParameterRequestBuilder() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; + method.AddParameter(new CodeParameter { + Name = "pathParam", + ParameterKind = CodeParameterKind.Path, + Type = new CodeType { Name = "string" - }; - method.MethodKind = CodeMethodKind.Deserializer; - method.IsAsync = false; - AddSerializationProperties(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("GetStringValue", result); - Assert.Contains("GetCollectionOfPrimitiveValues", result); - Assert.Contains("GetCollectionOfObjectValues", result); - Assert.Contains("GetEnumValue", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesInheritedSerializerBody() { - method.MethodKind = CodeMethodKind.Serializer; - method.IsAsync = false; - AddSerializationProperties(); - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.SomeParentClass.Serialize", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesSerializerBody() { - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.requestAdapter", result); + Assert.Contains("m.pathParameters", result); + Assert.Contains("pathParam", result); + Assert.Contains("return New", result); + } + [Fact] + public void WritesDescription() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; + method.AddParameter(new CodeParameter { + Name = "pathParam", + ParameterKind = CodeParameterKind.Path, + Type = new CodeType { Name = "string" - }; - method.MethodKind = CodeMethodKind.Serializer; - method.IsAsync = false; - AddSerializationProperties(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("WriteStringValue", result); - Assert.Contains("WriteCollectionOfStringValues", result); - Assert.Contains("WriteCollectionOfObjectValues", result); - Assert.Contains("WriteAdditionalData(m.GetAdditionalData())", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact(Skip = "descriptions are not supported")] - public void WritesMethodSyncDescription() { - method.Description = MethodDescription; - method.IsAsync = false; - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { + } + }); + method.Description = "Some description"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains($"// {method.Name.ToFirstCharacterUpperCase()} some description", result); + } + [Fact] + public void WritesGetterToBackingStore() { + parentClass.AddBackingStoreProperty(); + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Getter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.GetBackingStore().Get(\"someProperty\")", result); + } + [Fact] + public void WritesGetterToBackingStoreWithNonnullProperty() { + method.AddAccessedProperty(); + parentClass.AddBackingStoreProperty(); + method.AccessedProperty.Type = new CodeType { + Name = "string", + IsNullable = false, + }; + var defaultValue = "someDefaultValue"; + method.AccessedProperty.DefaultValue = defaultValue; + method.MethodKind = CodeMethodKind.Getter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("if value == nil", result); + Assert.Contains(defaultValue, result); + } + [Fact] + public void WritesSetterToBackingStore() { + parentClass.AddBackingStoreProperty(); + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Setter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.GetBackingStore().Set(\"someProperty\", value)", result); + } + [Fact] + public void WritesGetterToField() { + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Getter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.someProperty", result); + } + [Fact] + public void WritesSetterToField() { + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Setter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("m.someProperty = value", result); + } + [Fact] + public void WritesConstructor() { + method.MethodKind = CodeMethodKind.Constructor; + var defaultValue = "someVal"; + var propName = "propWithDefaultValue"; + parentClass.ClassKind = CodeClassKind.RequestBuilder; + parentClass.AddProperty(new CodeProperty { + Name = propName, + DefaultValue = defaultValue, + PropertyKind = CodePropertyKind.UrlTemplate, + }); + AddRequestProperties(); + method.AddParameter(new CodeParameter { + Name = "pathParameters", + ParameterKind = CodeParameterKind.PathParameters, + Type = new CodeType { + Name = "map[string]string" + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains($"m.{propName} = {defaultValue}", result); + Assert.Contains("make(map[string]string)", result); + } + [Fact] + public void WritesRawUrlConstructor() { + method.MethodKind = CodeMethodKind.RawUrlConstructor; + var defaultValue = "someVal"; + var propName = "propWithDefaultValue"; + parentClass.ClassKind = CodeClassKind.RequestBuilder; + parentClass.AddProperty(new CodeProperty { + Name = propName, + DefaultValue = defaultValue, + PropertyKind = CodePropertyKind.UrlTemplate, + }); + AddRequestProperties(); + method.AddParameter(new CodeParameter { + Name = "rawUrl", + ParameterKind = CodeParameterKind.RawUrl, + Type = new CodeType { Name = "string" - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("@return a CompletableFuture of", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new GoConventionService()); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); - var originalParent = method.Parent; - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - method.Parent = originalParent; - method.ReturnType = null; - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - } - [Fact] - public void ThrowsIfParentIsNotClass() { - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void ThrowsIfReturnTypeIsMissing() { - method.ReturnType = null; - Assert.Throws(() => writer.Write(method)); - } - private const string TaskPrefix = "func() ("; - [Fact] - public void WritesReturnType() { - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"{MethodName.ToFirstCharacterUpperCase()}()(*{ReturnTypeName}, error)", result);// async default - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void DoesNotAddAsyncInformationOnSyncMethods() { - method.IsAsync = false; - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain(TaskPrefix, result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesPublicMethodByDefault() { - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(MethodName.ToFirstCharacterUpperCase(), result);// public default - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesPrivateMethod() { - method.Access = AccessModifier.Private; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(MethodName.ToFirstCharacterLowerCase(), result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesProtectedMethod() { - method.Access = AccessModifier.Protected; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(MethodName.ToFirstCharacterLowerCase(), result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesIndexer() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.IndexerBackwardCompatibility; - method.OriginalIndexer = new () { - Name = "indx", - ParameterName = "id", - IndexType = new CodeType { - Name = "string", - IsNullable = true, - } - }; - method.AddParameter(new CodeParameter { - Name = "id", - ParameterKind = CodeParameterKind.Custom, - Type = new CodeType { - Name = "string", - IsNullable = true, - }, - Optional = true - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.requestAdapter", result); - Assert.Contains("m.pathParameters", result); - Assert.Contains("= *id", result); - Assert.Contains("return", result); - Assert.Contains("New", result); - } - [Fact] - public void WritesPathParameterRequestBuilder() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; - method.AddParameter(new CodeParameter { - Name = "pathParam", - ParameterKind = CodeParameterKind.Path, - Type = new CodeType { - Name = "string" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.requestAdapter", result); - Assert.Contains("m.pathParameters", result); - Assert.Contains("pathParam", result); - Assert.Contains("return New", result); - } - [Fact] - public void WritesDescription() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; - method.AddParameter(new CodeParameter { - Name = "pathParam", - ParameterKind = CodeParameterKind.Path, - Type = new CodeType { - Name = "string" - } - }); - method.Description = "Some description"; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"// {method.Name.ToFirstCharacterUpperCase()} some description", result); - } - [Fact] - public void WritesGetterToBackingStore() { - parentClass.AddBackingStoreProperty(); - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.GetBackingStore().Get(\"someProperty\")", result); - } - [Fact] - public void WritesGetterToBackingStoreWithNonnullProperty() { - method.AddAccessedProperty(); - parentClass.AddBackingStoreProperty(); - method.AccessedProperty.Type = new CodeType { - Name = "string", - IsNullable = false, - }; - var defaultValue = "someDefaultValue"; - method.AccessedProperty.DefaultValue = defaultValue; - method.MethodKind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("if value == nil", result); - Assert.Contains(defaultValue, result); - } - [Fact] - public void WritesSetterToBackingStore() { - parentClass.AddBackingStoreProperty(); - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Setter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.GetBackingStore().Set(\"someProperty\", value)", result); - } - [Fact] - public void WritesGetterToField() { - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.someProperty", result); - } - [Fact] - public void WritesSetterToField() { - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Setter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("m.someProperty = value", result); - } - [Fact] - public void WritesConstructor() { - method.MethodKind = CodeMethodKind.Constructor; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.ClassKind = CodeClassKind.RequestBuilder; - parentClass.AddProperty(new CodeProperty { - Name = propName, - DefaultValue = defaultValue, - PropertyKind = CodePropertyKind.UrlTemplate, - }); - AddRequestProperties(); - method.AddParameter(new CodeParameter { - Name = "pathParameters", - ParameterKind = CodeParameterKind.PathParameters, - Type = new CodeType { - Name = "map[string]string" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains($"m.{propName} = {defaultValue}", result); - Assert.Contains("make(map[string]string)", result); - } - [Fact] - public void WritesRawUrlConstructor() { - method.MethodKind = CodeMethodKind.RawUrlConstructor; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.ClassKind = CodeClassKind.RequestBuilder; - parentClass.AddProperty(new CodeProperty { - Name = propName, - DefaultValue = defaultValue, - PropertyKind = CodePropertyKind.UrlTemplate, - }); - AddRequestProperties(); - method.AddParameter(new CodeParameter { - Name = "rawUrl", - ParameterKind = CodeParameterKind.RawUrl, - Type = new CodeType { - Name = "string" - } - }); - method.AddParameter(new CodeParameter { - Name = "requestAdapter", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = new CodeType { - Name = "string" - } - }); - method.OriginalMethod = new (); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains($"urlParams := make(map[string]string)", result); - Assert.Contains($"urlParams[\"request-raw-url\"] = rawUrl", result); - } - [Fact] - public void WritesInheritedConstructor() { - method.MethodKind = CodeMethodKind.Constructor; - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains("SomeParentClass: *NewSomeParentClass", result); - } - [Fact] - public void WritesApiConstructor() { - method.MethodKind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty { - Name = "core", - PropertyKind = CodePropertyKind.RequestAdapter, - }).First(); - coreProp.Type = new CodeType { - Name = "HttpCore", - IsExternal = true, - }; - method.AddParameter(new CodeParameter { - Name = "core", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - method.DeserializerModules = new() {"github.com/microsoft/kiota/serialization/go/json.Deserializer"}; - method.SerializerModules = new() {"github.com/microsoft/kiota/serialization/go/json.Serializer"}; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains("RegisterDefaultSerializer", result); - Assert.Contains("RegisterDefaultDeserializer", result); - } - [Fact] - public void WritesApiConstructorWithBackingStore() { - method.MethodKind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty { - Name = "core", - PropertyKind = CodePropertyKind.RequestAdapter, - }).First(); - coreProp.Type = new CodeType { - Name = "HttpCore", - IsExternal = true, - }; - method.AddParameter(new CodeParameter { - Name = "core", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - var backingStoreParam = new CodeParameter { - Name = "backingStore", - ParameterKind = CodeParameterKind.BackingStore, - }; - backingStoreParam.Type = new CodeType { - Name = "IBackingStore", - IsExternal = true, - }; - method.AddParameter(backingStoreParam); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(method); - var result = tw.ToString(); - Assert.Contains("EnableBackingStore", result); - } - [Fact] - public void AccessorsTargetingEscapedPropertiesAreNotEscapedThemselves() { - var model = root.AddClass(new CodeClass { - Name = "SomeClass", - ClassKind = CodeClassKind.Model - }).First(); - model.AddProperty(new CodeProperty { - Name = "select", - Type = new CodeType { Name = "string" }, - Access = AccessModifier.Public, - PropertyKind = CodePropertyKind.Custom, - }); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root); - var getter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Getter)); - var setter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Setter)); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(getter); - var result = tw.ToString(); - Assert.Contains("GetSelect", result); - Assert.DoesNotContain("GetSelect_escaped", result); - - using var tw2 = new StringWriter(); - tempWriter.SetTextWriter(tw2); - tempWriter.Write(setter); - result = tw2.ToString(); - Assert.Contains("SetSelect", result); - Assert.DoesNotContain("SetSelect_escaped", result); - } + } + }); + method.AddParameter(new CodeParameter { + Name = "requestAdapter", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = new CodeType { + Name = "string" + } + }); + method.OriginalMethod = new (); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains($"urlParams := make(map[string]string)", result); + Assert.Contains($"urlParams[\"request-raw-url\"] = rawUrl", result); + } + [Fact] + public void WritesInheritedConstructor() { + method.MethodKind = CodeMethodKind.Constructor; + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains("SomeParentClass: *NewSomeParentClass", result); + } + [Fact] + public void WritesApiConstructor() { + method.MethodKind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty { + Name = "core", + PropertyKind = CodePropertyKind.RequestAdapter, + }).First(); + coreProp.Type = new CodeType { + Name = "HttpCore", + IsExternal = true, + }; + method.AddParameter(new CodeParameter { + Name = "core", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + method.DeserializerModules = new() {"github.com/microsoft/kiota/serialization/go/json.Deserializer"}; + method.SerializerModules = new() {"github.com/microsoft/kiota/serialization/go/json.Serializer"}; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains("RegisterDefaultSerializer", result); + Assert.Contains("RegisterDefaultDeserializer", result); + } + [Fact] + public void WritesApiConstructorWithBackingStore() { + method.MethodKind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty { + Name = "core", + PropertyKind = CodePropertyKind.RequestAdapter, + }).First(); + coreProp.Type = new CodeType { + Name = "HttpCore", + IsExternal = true, + }; + method.AddParameter(new CodeParameter { + Name = "core", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + var backingStoreParam = new CodeParameter { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType { + Name = "IBackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); + tempWriter.SetTextWriter(tw); + tempWriter.Write(method); + var result = tw.ToString(); + Assert.Contains("EnableBackingStore", result); + } + [Fact] + public void AccessorsTargetingEscapedPropertiesAreNotEscapedThemselves() { + var model = root.AddClass(new CodeClass { + Name = "SomeClass", + ClassKind = CodeClassKind.Model + }).First(); + model.AddProperty(new CodeProperty { + Name = "select", + Type = new CodeType { Name = "string" }, + Access = AccessModifier.Public, + PropertyKind = CodePropertyKind.Custom, + }); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, root); + var getter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Getter)); + var setter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Setter)); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); + tempWriter.SetTextWriter(tw); + tempWriter.Write(getter); + var result = tw.ToString(); + Assert.Contains("GetSelect", result); + Assert.DoesNotContain("GetSelect_escaped", result); + + using var tw2 = new StringWriter(); + tempWriter.SetTextWriter(tw2); + tempWriter.Write(setter); + result = tw2.ToString(); + Assert.Contains("SetSelect", result); + Assert.DoesNotContain("SetSelect_escaped", result); } } diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index 5bb690cef8..c8a79df70a 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -6,605 +6,633 @@ using Kiota.Builder.Tests; using Xunit; -namespace Kiota.Builder.Writers.Java.Tests { - public class CodeMethodWriterTests : IDisposable { - private const string DefaultPath = "./"; - private const string DefaultName = "name"; - private readonly StringWriter tw; - private readonly LanguageWriter writer; - private readonly CodeMethod method; - private readonly CodeClass parentClass; - private readonly CodeNamespace root; - private const string MethodName = "methodName"; - private const string ReturnTypeName = "Somecustomtype"; - private const string MethodDescription = "some description"; - private const string ParamDescription = "some parameter description"; - private const string ParamName = "paramName"; - public CodeMethodWriterTests() - { - writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, DefaultPath, DefaultName); - tw = new StringWriter(); - writer.SetTextWriter(tw); - root = CodeNamespace.InitRootNamespace(); - parentClass = new CodeClass { - Name = "parentClass" - }; - root.AddClass(parentClass); - method = new CodeMethod { - Name = MethodName, - }; - method.ReturnType = new CodeType { - Name = ReturnTypeName - }; - parentClass.AddMethod(method); - } - public void Dispose() - { - tw?.Dispose(); - GC.SuppressFinalize(this); - } - private void AddRequestProperties() { - parentClass.AddProperty(new CodeProperty { - Name = "requestAdapter", - PropertyKind = CodePropertyKind.RequestAdapter, - }); - parentClass.AddProperty(new CodeProperty { - Name = "pathParameters", - PropertyKind = CodePropertyKind.PathParameters, - Type = new CodeType { - Name = "string", - } - }); - parentClass.AddProperty(new CodeProperty { - Name = "urlTemplate", - PropertyKind = CodePropertyKind.UrlTemplate, - }); - } - private void AddSerializationProperties() { - var addData = parentClass.AddProperty(new CodeProperty { - Name = "additionalData", - PropertyKind = CodePropertyKind.AdditionalData, - }).First(); - addData.Type = new CodeType { - Name = "string" - }; - var dummyProp = parentClass.AddProperty(new CodeProperty { - Name = "dummyProp", - }).First(); - dummyProp.Type = new CodeType { - Name = "string" - }; - var dummyCollectionProp = parentClass.AddProperty(new CodeProperty { - Name = "dummyColl", - }).First(); - dummyCollectionProp.Type = new CodeType { - Name = "string", - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, - }; - var dummyComplexCollection = parentClass.AddProperty(new CodeProperty { - Name = "dummyComplexColl" - }).First(); - dummyComplexCollection.Type = new CodeType { - Name = "Complex", - CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, - TypeDefinition = new CodeClass { - Name = "SomeComplexType" - } - }; - var dummyEnumProp = parentClass.AddProperty(new CodeProperty{ - Name = "dummyEnumCollection", - }).First(); - dummyEnumProp.Type = new CodeType { - Name = "SomeEnum", - TypeDefinition = new CodeEnum { - Name = "EnumType" - } - }; - } - private void AddInheritanceClass() { - (parentClass.StartBlock as CodeClass.Declaration).Inherits = new CodeType { - Name = "someParentClass" - }; - } - private void AddRequestBodyParameters() { - var stringType = new CodeType { +namespace Kiota.Builder.Writers.Java.Tests; +public class CodeMethodWriterTests : IDisposable { + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeMethod method; + private readonly CodeClass parentClass; + private readonly CodeNamespace root; + private const string MethodName = "methodName"; + private const string ReturnTypeName = "Somecustomtype"; + private const string MethodDescription = "some description"; + private const string ParamDescription = "some parameter description"; + private const string ParamName = "paramName"; + public CodeMethodWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, DefaultPath, DefaultName); + tw = new StringWriter(); + writer.SetTextWriter(tw); + root = CodeNamespace.InitRootNamespace(); + parentClass = new CodeClass { + Name = "parentClass" + }; + root.AddClass(parentClass); + method = new CodeMethod { + Name = MethodName, + }; + method.ReturnType = new CodeType { + Name = ReturnTypeName + }; + parentClass.AddMethod(method); + } + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + private void AddRequestProperties() { + parentClass.AddProperty(new CodeProperty { + Name = "requestAdapter", + PropertyKind = CodePropertyKind.RequestAdapter, + }); + parentClass.AddProperty(new CodeProperty { + Name = "pathParameters", + PropertyKind = CodePropertyKind.PathParameters, + Type = new CodeType { Name = "string", - }; - method.AddParameter(new CodeParameter { - Name = "h", - ParameterKind = CodeParameterKind.Headers, - Type = stringType, - }); - method.AddParameter(new CodeParameter{ - Name = "q", - ParameterKind = CodeParameterKind.QueryParameter, - Type = stringType, - }); - method.AddParameter(new CodeParameter{ - Name = "b", - ParameterKind = CodeParameterKind.RequestBody, - Type = stringType, - }); - method.AddParameter(new CodeParameter{ - Name = "r", - ParameterKind = CodeParameterKind.ResponseHandler, - Type = stringType, - }); - method.AddParameter(new CodeParameter { - Name = "o", - ParameterKind = CodeParameterKind.Options, - Type = stringType, - }); - } - [Fact] - public void WritesNullableVoidTypeForExecutor(){ - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - method.ReturnType = new CodeType { - Name = "void", - }; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("CompletableFuture", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestBuilder() { - method.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void WritesRequestBodiesThrowOnNullHttpMethod() { - method.MethodKind = CodeMethodKind.RequestExecutor; - Assert.Throws(() => writer.Write(method)); - method.MethodKind = CodeMethodKind.RequestGenerator; - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void WritesRequestExecutorBody() { - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("final RequestInformation requestInfo", result); - Assert.Contains("sendAsync", result); - Assert.Contains("CompletableFuture.failedFuture(ex)", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestExecutorBodyForCollections() { - method.MethodKind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("sendCollectionAsync", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestGeneratorBody() { - method.MethodKind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("final RequestInformation requestInfo = new RequestInformation()", result); - Assert.Contains("urlTemplate =", result); - Assert.Contains("pathParameters =", result); - Assert.Contains("httpMethod = HttpMethod.GET", result); - Assert.Contains("h.accept(requestInfo.headers)", result); - Assert.Contains("AddQueryParameters", result); - Assert.Contains("setContentFromParsable", result); - Assert.Contains("addRequestOptions", 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 RequestInformation requestInfo = new RequestInformation()", result); - Assert.DoesNotContain("httpMethod = HttpMethod.GET", result); - Assert.DoesNotContain("h.accept(requestInfo.headers)", result); - Assert.DoesNotContain("AddQueryParameters", result); - Assert.DoesNotContain("setContentFromParsable", result); - Assert.DoesNotContain("addRequestOptions", 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; - AddSerializationProperties(); - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("super.methodName()", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesDeSerializerBody() { - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { - Name = "string" - }; - method.MethodKind = CodeMethodKind.Deserializer; - method.IsAsync = false; - AddSerializationProperties(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("getStringValue", result); - Assert.Contains("getCollectionOfPrimitiveValues", result); - Assert.Contains("getCollectionOfObjectValues", result); - Assert.Contains("getEnumValue", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesInheritedSerializerBody() { - method.MethodKind = CodeMethodKind.Serializer; - method.IsAsync = false; - AddSerializationProperties(); - AddInheritanceClass(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("super.serialize", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesSerializerBody() { - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { - Name = "string" - }; - method.MethodKind = CodeMethodKind.Serializer; - method.IsAsync = false; - AddSerializationProperties(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("writeStringValue", result); - Assert.Contains("writeCollectionOfPrimitiveValues", result); - Assert.Contains("writeCollectionOfObjectValues", result); - Assert.Contains("writeEnumValue", result); - Assert.Contains("writeAdditionalData(this.getAdditionalData());", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesMethodAsyncDescription() { - - method.Description = MethodDescription; - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { + } + }); + parentClass.AddProperty(new CodeProperty { + Name = "urlTemplate", + PropertyKind = CodePropertyKind.UrlTemplate, + }); + } + private void AddSerializationProperties() { + var addData = parentClass.AddProperty(new CodeProperty { + Name = "additionalData", + PropertyKind = CodePropertyKind.AdditionalData, + }).First(); + addData.Type = new CodeType { + Name = "string" + }; + var dummyProp = parentClass.AddProperty(new CodeProperty { + Name = "dummyProp", + }).First(); + dummyProp.Type = new CodeType { + Name = "string" + }; + var dummyCollectionProp = parentClass.AddProperty(new CodeProperty { + Name = "dummyColl", + }).First(); + dummyCollectionProp.Type = new CodeType { + Name = "string", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + }; + var dummyComplexCollection = parentClass.AddProperty(new CodeProperty { + Name = "dummyComplexColl" + }).First(); + dummyComplexCollection.Type = new CodeType { + Name = "Complex", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + TypeDefinition = new CodeClass { + Name = "SomeComplexType" + } + }; + var dummyEnumProp = parentClass.AddProperty(new CodeProperty{ + Name = "dummyEnumCollection", + }).First(); + dummyEnumProp.Type = new CodeType { + Name = "SomeEnum", + TypeDefinition = new CodeEnum { + Name = "EnumType" + } + }; + } + private void AddInheritanceClass() { + (parentClass.StartBlock as CodeClass.Declaration).Inherits = new CodeType { + Name = "someParentClass" + }; + } + private void AddRequestBodyParameters() { + var stringType = new CodeType { + Name = "string", + }; + method.AddParameter(new CodeParameter { + Name = "h", + ParameterKind = CodeParameterKind.Headers, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "q", + ParameterKind = CodeParameterKind.QueryParameter, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "b", + ParameterKind = CodeParameterKind.RequestBody, + Type = stringType, + }); + method.AddParameter(new CodeParameter{ + Name = "r", + ParameterKind = CodeParameterKind.ResponseHandler, + Type = stringType, + }); + method.AddParameter(new CodeParameter { + Name = "o", + ParameterKind = CodeParameterKind.Options, + Type = stringType, + }); + } + [Fact] + public void WritesNullableVoidTypeForExecutor(){ + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType = new CodeType { + Name = "void", + }; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("CompletableFuture", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestBuilder() { + method.MethodKind = CodeMethodKind.RequestBuilderBackwardCompatibility; + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void WritesRequestBodiesThrowOnNullHttpMethod() { + method.MethodKind = CodeMethodKind.RequestExecutor; + Assert.Throws(() => writer.Write(method)); + method.MethodKind = CodeMethodKind.RequestGenerator; + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void WritesRequestExecutorBody() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + var error4XX = root.AddClass(new CodeClass{ + Name = "Error4XX", + }).First(); + var error5XX = root.AddClass(new CodeClass{ + Name = "Error5XX", + }).First(); + var error401 = root.AddClass(new CodeClass{ + Name = "Error401", + }).First(); + method.ErrorMappings = new () { + {"4XX", new CodeType {Name = "Error4XX", TypeDefinition = error4XX}}, + {"5XX", new CodeType {Name = "Error5XX", TypeDefinition = error5XX}}, + {"403", new CodeType {Name = "Error403", TypeDefinition = error401}}, + }; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("final RequestInformation requestInfo", result); + Assert.Contains("final HashMap> errorMapping = new HashMap>", result); + Assert.Contains("put(\"4XX\", Error4XX.class);", result); + Assert.Contains("put(\"5XX\", Error5XX.class);", result); + Assert.Contains("put(\"403\", Error403.class);", result); + Assert.Contains("sendAsync", result); + Assert.Contains("CompletableFuture.failedFuture(ex)", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void DoesntCreateDictionaryOnEmptyErrorMapping() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("final HashMap> errorMapping = new HashMap>", result); + Assert.Contains("null", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestExecutorBodyForCollections() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("sendCollectionAsync", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBody() { + method.MethodKind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("final RequestInformation requestInfo = new RequestInformation()", result); + Assert.Contains("urlTemplate =", result); + Assert.Contains("pathParameters =", result); + Assert.Contains("httpMethod = HttpMethod.GET", result); + Assert.Contains("h.accept(requestInfo.headers)", result); + Assert.Contains("AddQueryParameters", result); + Assert.Contains("setContentFromParsable", result); + Assert.Contains("addRequestOptions", 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 RequestInformation requestInfo = new RequestInformation()", result); + Assert.DoesNotContain("httpMethod = HttpMethod.GET", result); + Assert.DoesNotContain("h.accept(requestInfo.headers)", result); + Assert.DoesNotContain("AddQueryParameters", result); + Assert.DoesNotContain("setContentFromParsable", result); + Assert.DoesNotContain("addRequestOptions", 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; + AddSerializationProperties(); + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("super.methodName()", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesDeSerializerBody() { + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.MethodKind = CodeMethodKind.Deserializer; + method.IsAsync = false; + AddSerializationProperties(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("getStringValue", result); + Assert.Contains("getCollectionOfPrimitiveValues", result); + Assert.Contains("getCollectionOfObjectValues", result); + Assert.Contains("getEnumValue", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesInheritedSerializerBody() { + method.MethodKind = CodeMethodKind.Serializer; + method.IsAsync = false; + AddSerializationProperties(); + AddInheritanceClass(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("super.serialize", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesSerializerBody() { + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.MethodKind = CodeMethodKind.Serializer; + method.IsAsync = false; + AddSerializationProperties(); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("writeStringValue", result); + Assert.Contains("writeCollectionOfPrimitiveValues", result); + Assert.Contains("writeCollectionOfObjectValues", result); + Assert.Contains("writeEnumValue", result); + Assert.Contains("writeAdditionalData(this.getAdditionalData());", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesMethodAsyncDescription() { + + method.Description = MethodDescription; + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.AddParameter(parameter); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("/**", result); + Assert.Contains(MethodDescription, result); + Assert.Contains("@param ", result); + Assert.Contains(ParamName, result); + Assert.Contains(ParamDescription, result); + Assert.Contains("@return a CompletableFuture of", result); + Assert.Contains("*/", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesMethodSyncDescription() { + + method.Description = MethodDescription; + method.IsAsync = false; + var parameter = new CodeParameter{ + Description = ParamDescription, + Name = ParamName + }; + parameter.Type = new CodeType { + Name = "string" + }; + method.AddParameter(parameter); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("@return a CompletableFuture of", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void Defensive() { + var codeMethodWriter = new CodeMethodWriter(new JavaConventionService()); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); + var originalParent = method.Parent; + method.Parent = CodeNamespace.InitRootNamespace(); + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); + method.Parent = originalParent; + method.ReturnType = null; + Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); + } + [Fact] + public void ThrowsIfParentIsNotClass() { + method.Parent = CodeNamespace.InitRootNamespace(); + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void ThrowsIfReturnTypeIsMissing() { + method.ReturnType = null; + Assert.Throws(() => writer.Write(method)); + } + private const string TaskPrefix = "CompletableFuture<"; + [Fact] + public void WritesReturnType() { + writer.Write(method); + var result = tw.ToString(); + Assert.Contains($"{TaskPrefix}{ReturnTypeName}> {MethodName}", result);// async default + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void DoesNotAddAsyncInformationOnSyncMethods() { + method.IsAsync = false; + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain(TaskPrefix, result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesPublicMethodByDefault() { + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("public ", result);// public default + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesPrivateMethod() { + method.Access = AccessModifier.Private; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("private ", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesProtectedMethod() { + method.Access = AccessModifier.Protected; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("protected ", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesIndexer() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.IndexerBackwardCompatibility; + method.OriginalIndexer = new CodeIndexer { + Name = "idx", + IndexType = new CodeType { + Name = "int" + }, + ParameterName = "collectionId" + }; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("collectionId", result); + Assert.Contains("requestAdapter", result); + Assert.Contains("pathParameters", result); + Assert.Contains("id", result); + Assert.Contains("return new", result); + } + [Fact] + public void WritesPathParameterRequestBuilder() { + AddRequestProperties(); + method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; + method.AddParameter(new CodeParameter { + Name = "pathParam", + ParameterKind = CodeParameterKind.Path, + Type = new CodeType { Name = "string" - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("/**", result); - Assert.Contains(MethodDescription, result); - Assert.Contains("@param ", result); - Assert.Contains(ParamName, result); - Assert.Contains(ParamDescription, result); - Assert.Contains("@return a CompletableFuture of", result); - Assert.Contains("*/", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesMethodSyncDescription() { - - method.Description = MethodDescription; - method.IsAsync = false; - var parameter = new CodeParameter{ - Description = ParamDescription, - Name = ParamName - }; - parameter.Type = new CodeType { + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("requestAdapter", result); + Assert.Contains("pathParameters", result); + Assert.Contains("pathParam", result); + Assert.Contains("return new", result); + } + [Fact] + public void WritesGetterToBackingStore() { + parentClass.AddBackingStoreProperty(); + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Getter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("this.getBackingStore().get(\"someProperty\")", result); + } + [Fact] + public void WritesGetterToBackingStoreWithNonnullProperty() { + method.AddAccessedProperty(); + parentClass.AddBackingStoreProperty(); + method.AccessedProperty.Type = new CodeType { + Name = "string", + IsNullable = false, + }; + var defaultValue = "someDefaultValue"; + method.AccessedProperty.DefaultValue = defaultValue; + method.MethodKind = CodeMethodKind.Getter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("if(value == null)", result); + Assert.Contains(defaultValue, result); + } + [Fact] + public void WritesSetterToBackingStore() { + parentClass.AddBackingStoreProperty(); + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Setter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("this.getBackingStore().set(\"someProperty\", value)", result); + } + [Fact] + public void WritesGetterToField() { + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Getter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("this.someProperty", result); + } + [Fact] + public void WritesSetterToField() { + method.AddAccessedProperty(); + method.MethodKind = CodeMethodKind.Setter; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("this.someProperty = value", result); + } + [Fact] + public void WritesConstructor() { + method.MethodKind = CodeMethodKind.Constructor; + var defaultValue = "someVal"; + var propName = "propWithDefaultValue"; + parentClass.ClassKind = CodeClassKind.RequestBuilder; + parentClass.AddProperty(new CodeProperty { + Name = propName, + DefaultValue = defaultValue, + PropertyKind = CodePropertyKind.UrlTemplate, + }); + AddRequestProperties(); + method.AddParameter(new CodeParameter { + Name = "pathParameters", + ParameterKind = CodeParameterKind.PathParameters, + Type = new CodeType { + Name = "Map" + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains($"this.{propName} = {defaultValue}", result); + Assert.Contains("new Map(pathParameters)", result); + } + [Fact] + public void WritesRawUrlConstructor() { + method.MethodKind = CodeMethodKind.RawUrlConstructor; + var defaultValue = "someVal"; + var propName = "propWithDefaultValue"; + parentClass.ClassKind = CodeClassKind.RequestBuilder; + parentClass.AddProperty(new CodeProperty { + Name = propName, + DefaultValue = defaultValue, + PropertyKind = CodePropertyKind.UrlTemplate, + }); + AddRequestProperties(); + method.AddParameter(new CodeParameter { + Name = "rawUrl", + ParameterKind = CodeParameterKind.RawUrl, + Type = new CodeType { Name = "string" - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("@return a CompletableFuture of", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new JavaConventionService()); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); - var originalParent = method.Parent; - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - method.Parent = originalParent; - method.ReturnType = null; - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - } - [Fact] - public void ThrowsIfParentIsNotClass() { - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => writer.Write(method)); - } - [Fact] - public void ThrowsIfReturnTypeIsMissing() { - method.ReturnType = null; - Assert.Throws(() => writer.Write(method)); - } - private const string TaskPrefix = "CompletableFuture<"; - [Fact] - public void WritesReturnType() { - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"{TaskPrefix}{ReturnTypeName}> {MethodName}", result);// async default - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void DoesNotAddAsyncInformationOnSyncMethods() { - method.IsAsync = false; - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain(TaskPrefix, result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesPublicMethodByDefault() { - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("public ", result);// public default - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesPrivateMethod() { - method.Access = AccessModifier.Private; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("private ", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesProtectedMethod() { - method.Access = AccessModifier.Protected; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("protected ", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesIndexer() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.IndexerBackwardCompatibility; - method.OriginalIndexer = new CodeIndexer { - Name = "idx", - IndexType = new CodeType { - Name = "int" - }, - ParameterName = "collectionId" - }; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("collectionId", result); - Assert.Contains("requestAdapter", result); - Assert.Contains("pathParameters", result); - Assert.Contains("id", result); - Assert.Contains("return new", result); - } - [Fact] - public void WritesPathParameterRequestBuilder() { - AddRequestProperties(); - method.MethodKind = CodeMethodKind.RequestBuilderWithParameters; - method.AddParameter(new CodeParameter { - Name = "pathParam", - ParameterKind = CodeParameterKind.Path, - Type = new CodeType { - Name = "string" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("requestAdapter", result); - Assert.Contains("pathParameters", result); - Assert.Contains("pathParam", result); - Assert.Contains("return new", result); - } - [Fact] - public void WritesGetterToBackingStore() { - parentClass.AddBackingStoreProperty(); - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.getBackingStore().get(\"someProperty\")", result); - } - [Fact] - public void WritesGetterToBackingStoreWithNonnullProperty() { - method.AddAccessedProperty(); - parentClass.AddBackingStoreProperty(); - method.AccessedProperty.Type = new CodeType { - Name = "string", - IsNullable = false, - }; - var defaultValue = "someDefaultValue"; - method.AccessedProperty.DefaultValue = defaultValue; - method.MethodKind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("if(value == null)", result); - Assert.Contains(defaultValue, result); - } - [Fact] - public void WritesSetterToBackingStore() { - parentClass.AddBackingStoreProperty(); - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Setter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.getBackingStore().set(\"someProperty\", value)", result); - } - [Fact] - public void WritesGetterToField() { - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.someProperty", result); - } - [Fact] - public void WritesSetterToField() { - method.AddAccessedProperty(); - method.MethodKind = CodeMethodKind.Setter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.someProperty = value", result); - } - [Fact] - public void WritesConstructor() { - method.MethodKind = CodeMethodKind.Constructor; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.ClassKind = CodeClassKind.RequestBuilder; - parentClass.AddProperty(new CodeProperty { - Name = propName, - DefaultValue = defaultValue, - PropertyKind = CodePropertyKind.UrlTemplate, - }); - AddRequestProperties(); - method.AddParameter(new CodeParameter { - Name = "pathParameters", - ParameterKind = CodeParameterKind.PathParameters, - Type = new CodeType { - Name = "Map" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains($"this.{propName} = {defaultValue}", result); - Assert.Contains("new Map(pathParameters)", result); - } - [Fact] - public void WritesRawUrlConstructor() { - method.MethodKind = CodeMethodKind.RawUrlConstructor; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.ClassKind = CodeClassKind.RequestBuilder; - parentClass.AddProperty(new CodeProperty { - Name = propName, - DefaultValue = defaultValue, - PropertyKind = CodePropertyKind.UrlTemplate, - }); - AddRequestProperties(); - method.AddParameter(new CodeParameter { - Name = "rawUrl", - ParameterKind = CodeParameterKind.RawUrl, - Type = new CodeType { - Name = "string" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains($"this.{propName} = {defaultValue}", result); - Assert.Contains($"urlTplParams.put(\"request-raw-url\", rawUrl);", result); - } - [Fact] - public void WritesApiConstructor() { - method.MethodKind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty { - Name = "core", - PropertyKind = CodePropertyKind.RequestAdapter, - }).First(); - coreProp.Type = new CodeType { - Name = "HttpCore", - IsExternal = true, - }; - method.AddParameter(new CodeParameter { - Name = "core", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - method.DeserializerModules = new() {"com.microsoft.kiota.serialization.Deserializer"}; - method.SerializerModules = new() {"com.microsoft.kiota.serialization.Serializer"}; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains("registerDefaultSerializer", result); - Assert.Contains("registerDefaultDeserializer", result); - } - [Fact] - public void WritesApiConstructorWithBackingStore() { - method.MethodKind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty { - Name = "core", - PropertyKind = CodePropertyKind.RequestAdapter, - }).First(); - coreProp.Type = new CodeType { - Name = "HttpCore", - IsExternal = true, - }; - method.AddParameter(new CodeParameter { - Name = "core", - ParameterKind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - var backingStoreParam = new CodeParameter { - Name = "backingStore", - ParameterKind = CodeParameterKind.BackingStore, - }; - backingStoreParam.Type = new CodeType { - Name = "BackingStore", - IsExternal = true, - }; - method.AddParameter(backingStoreParam); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(method); - var result = tw.ToString(); - Assert.Contains("enableBackingStore", result); - } - [Fact] - public void AccessorsTargetingEscapedPropertiesAreNotEscapedThemselves() { - var model = root.AddClass(new CodeClass { - Name = "SomeClass", - ClassKind = CodeClassKind.Model - }).First(); - model.AddProperty(new CodeProperty { - Name = "short", - Type = new CodeType { Name = "string" }, - Access = AccessModifier.Public, - PropertyKind = CodePropertyKind.Custom, - }); - ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Java }, root); - var getter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Getter)); - var setter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Setter)); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(getter); - var result = tw.ToString(); - Assert.Contains("getShort", result); - Assert.DoesNotContain("getShort_escaped", result); - - using var tw2 = new StringWriter(); - tempWriter.SetTextWriter(tw2); - tempWriter.Write(setter); - result = tw2.ToString(); - Assert.Contains("setShort", result); - Assert.DoesNotContain("setShort_escaped", result); - } + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains($"this.{propName} = {defaultValue}", result); + Assert.Contains($"urlTplParams.put(\"request-raw-url\", rawUrl);", result); + } + [Fact] + public void WritesApiConstructor() { + method.MethodKind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty { + Name = "core", + PropertyKind = CodePropertyKind.RequestAdapter, + }).First(); + coreProp.Type = new CodeType { + Name = "HttpCore", + IsExternal = true, + }; + method.AddParameter(new CodeParameter { + Name = "core", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + method.DeserializerModules = new() {"com.microsoft.kiota.serialization.Deserializer"}; + method.SerializerModules = new() {"com.microsoft.kiota.serialization.Serializer"}; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); + Assert.Contains("registerDefaultSerializer", result); + Assert.Contains("registerDefaultDeserializer", result); + } + [Fact] + public void WritesApiConstructorWithBackingStore() { + method.MethodKind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty { + Name = "core", + PropertyKind = CodePropertyKind.RequestAdapter, + }).First(); + coreProp.Type = new CodeType { + Name = "HttpCore", + IsExternal = true, + }; + method.AddParameter(new CodeParameter { + Name = "core", + ParameterKind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + var backingStoreParam = new CodeParameter { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType { + Name = "BackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, DefaultPath, DefaultName); + tempWriter.SetTextWriter(tw); + tempWriter.Write(method); + var result = tw.ToString(); + Assert.Contains("enableBackingStore", result); + } + [Fact] + public void AccessorsTargetingEscapedPropertiesAreNotEscapedThemselves() { + var model = root.AddClass(new CodeClass { + Name = "SomeClass", + ClassKind = CodeClassKind.Model + }).First(); + model.AddProperty(new CodeProperty { + Name = "short", + Type = new CodeType { Name = "string" }, + Access = AccessModifier.Public, + PropertyKind = CodePropertyKind.Custom, + }); + ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Java }, root); + var getter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Getter)); + var setter = model.Methods.First(x => x.IsOfKind(CodeMethodKind.Setter)); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, DefaultPath, DefaultName); + tempWriter.SetTextWriter(tw); + tempWriter.Write(getter); + var result = tw.ToString(); + Assert.Contains("getShort", result); + Assert.DoesNotContain("getShort_escaped", result); + + using var tw2 = new StringWriter(); + tempWriter.SetTextWriter(tw2); + tempWriter.Write(setter); + result = tw2.ToString(); + Assert.Contains("setShort", result); + Assert.DoesNotContain("setShort_escaped", result); } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index aa4a57766a..161034efbc 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -12,6 +12,7 @@ public class CodeMethodWriterTests : IDisposable { private readonly LanguageWriter writer; private readonly CodeMethod method; private readonly CodeClass parentClass; + private readonly CodeNamespace root; private const string MethodName = "methodName"; private const string ReturnTypeName = "Somecustomtype"; private const string MethodDescription = "some description"; @@ -22,7 +23,7 @@ public CodeMethodWriterTests() writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); tw = new StringWriter(); writer.SetTextWriter(tw); - var root = CodeNamespace.InitRootNamespace(); + root = CodeNamespace.InitRootNamespace(); parentClass = new CodeClass { Name = "parentClass" }; @@ -149,15 +150,44 @@ public void WritesRequestBodiesThrowOnNullHttpMethod() { public void WritesRequestExecutorBody() { method.MethodKind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get; + var error4XX = root.AddClass(new CodeClass{ + Name = "Error4XX", + }).First(); + var error5XX = root.AddClass(new CodeClass{ + Name = "Error5XX", + }).First(); + var error401 = root.AddClass(new CodeClass{ + Name = "Error401", + }).First(); + method.ErrorMappings = new () { + {"4XX", new CodeType {Name = "Error4XX", TypeDefinition = error4XX}}, + {"5XX", new CodeType {Name = "Error5XX", TypeDefinition = error5XX}}, + {"403", new CodeType {Name = "Error403", TypeDefinition = error401}}, + }; AddRequestBodyParameters(); writer.Write(method); var result = tw.ToString(); Assert.Contains("const requestInfo", result); + Assert.Contains("const errorMapping: Record Parsable> =", result); + Assert.Contains("\"4XX\": Error4XX,", result); + Assert.Contains("\"5XX\": Error5XX,", result); + Assert.Contains("\"403\": Error403,", result); Assert.Contains("sendAsync", result); Assert.Contains("Promise.reject", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void DoesntCreateDictionaryOnEmptyErrorMapping() { + method.MethodKind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + AddRequestBodyParameters(); + writer.Write(method); + var result = tw.ToString(); + Assert.DoesNotContain("const errorMapping: Record Parsable> =", result); + Assert.Contains("undefined", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesRequestExecutorBodyForCollections() { method.MethodKind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get;