diff --git a/.vscode/extensions.json b/.vscode/extensions.json index dce5e47726..61f6a9fe98 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,3 @@ { - "recommendations": [ - "formulahendry.dotnet-test-explorer", - "ms-dotnettools.csharp", - "editorconfig.editorconfig" - ] + "recommendations": ["ms-dotnettools.csharp", "editorconfig.editorconfig"] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 242d689824..29f5089976 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,15 +4,12 @@ { "label": "clean:target", "type": "shell", - "command": "rm -r C:/sources/github/msgraph-sdk-typescript/src/*", + "command": "rm -r C:/sources/github/msgraph-sdk-typescript/src/*" }, { "label": "cleantargetandbuild", "dependsOrder": "sequence", - "dependsOn": [ - "clean:target", - "build" - ] + "dependsOn": ["clean:target", "build"] }, { "label": "build", @@ -21,7 +18,7 @@ "group": "build", "args": [ "build", - "${workspaceFolder}/src/kiota/kiota.csproj", + "${workspaceFolder}/kiota.sln", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -70,7 +67,7 @@ "args": [ "-reports:${workspaceFolder}/tests/**/coverage.cobertura.xml", "-targetdir:${workspaceFolder}/reports/coverage" - ], + ] }, "windows": { "command": "reportgenerator", @@ -79,10 +76,7 @@ "-targetdir:${workspaceFolder}\\reports\\coverage" ] }, - "dependsOn": [ - "coverage:clean", - "test" - ], + "dependsOn": ["coverage:clean", "test"], "dependsOrder": "sequence" }, { @@ -94,7 +88,7 @@ "args": [ "-reports:${workspaceFolder}/tests/Kiota.Builder.Tests/**/coverage.cobertura.xml", "-targetdir:${workspaceFolder}/reports/coverage" - ], + ] }, "windows": { "command": "reportgenerator", @@ -103,10 +97,7 @@ "-targetdir:${workspaceFolder}\\reports\\coverage" ] }, - "dependsOn": [ - "coverage:clean", - "test" - ], + "dependsOn": ["coverage:clean", "test"], "dependsOrder": "sequence" }, { @@ -114,50 +105,36 @@ "type": "shell", "linux": { "command": "xdg-open", - "args": [ - "${workspaceFolder}/reports/coverage/index.html" - ] + "args": ["${workspaceFolder}/reports/coverage/index.html"] }, "osx": { "command": "open", - "args": [ - "${workspaceFolder}/reports/coverage/index.html" - ] + "args": ["${workspaceFolder}/reports/coverage/index.html"] }, "windows": { "command": "start", - "args": [ - "${workspaceFolder}/reports/coverage/index.html" - ] + "args": ["${workspaceFolder}/reports/coverage/index.html"] }, - "group": "test", + "group": "test" }, { "label": "coverage:launch:global", "group": "test", "dependsOrder": "sequence", - "dependsOn": [ - "coverage:global", - "coverage:launch" - ] + "dependsOn": ["coverage:global", "coverage:launch"] }, { "label": "coverage:launch:unit", "group": "test", "dependsOrder": "sequence", - "dependsOn": [ - "coverage:unit", - "coverage:launch" - ] + "dependsOn": ["coverage:unit", "coverage:launch"] }, { "label": "clean", "command": "dotnet", "type": "process", "group": "build", - "args": [ - "clean" - ], + "args": ["clean"], "problemMatcher": "$msCompile" }, { @@ -181,7 +158,7 @@ "args": [ "watch", "run", - "${workspaceFolder}/src/kiota/kiota.csproj", + "${workspaceFolder}/kiota.sln", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], diff --git a/CHANGELOG.md b/CHANGELOG.md index f5038230b4..486b24345e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for reserved path parameters. [#2320](https://github.com/microsoft/kiota/issues/2320) - Added support for csv enum values in enums using a mask in Go. - Added support for `x-ms-enum-flags` extension in Generator to enable generation of bitwise(flagged) enum values[#3237](https://github.com/microsoft/kiota/issues/3237). +- Added support for mapping primary error messages in CSharp, Go, Java, and TypeScript. [#3066](https://github.com/microsoft/kiota/issues/3066) ### Changed diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs index fae2e66e0b..58d84b8491 100644 --- a/src/Kiota.Builder/CodeDOM/CodeClass.cs +++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs @@ -198,6 +198,13 @@ public IEnumerable AddInnerInterface(params CodeInterface[] codeI return AddRange(codeInterfaces); } public CodeClass? BaseClass => StartBlock.Inherits?.TypeDefinition as CodeClass; + /// + /// The interface associated with this class, if any. + /// + public CodeInterface? AssociatedInterface + { + get; set; + } public bool DerivesFrom(CodeClass codeClass) { ArgumentNullException.ThrowIfNull(codeClass); diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index b1ffee77a8..f8db7511cc 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -38,6 +38,10 @@ public enum CodeMethodKind /// Fluent API method returning a request builder with a set raw URL. depends on the RawUrlConstructor. /// RawUrlBuilder, + /// + /// The override for the error message for the error/exception type. + /// + ErrorMessageOverride } public enum HttpMethod { diff --git a/src/Kiota.Builder/CodeDOM/CodeProperty.cs b/src/Kiota.Builder/CodeDOM/CodeProperty.cs index 67d4d5844f..37b39d4e15 100644 --- a/src/Kiota.Builder/CodeDOM/CodeProperty.cs +++ b/src/Kiota.Builder/CodeDOM/CodeProperty.cs @@ -34,6 +34,10 @@ public enum CodePropertyKind /// The request middleware options. Used when request parameters are wrapped in a class. /// Options, + /// + /// The override for the error message for the error/exception type. + /// + ErrorMessageOverride } public class CodeProperty : CodeTerminalWithKind, IDocumentedElement, IAlternativeName, ICloneable, IDeprecableElement @@ -110,6 +114,13 @@ public DeprecationInformation? Deprecation { get; set; } + /// + /// Indicates if the property is the primary error message for the error/exception type. + /// + public bool IsPrimaryErrorMessage + { + get; set; + } public object Clone() { @@ -130,6 +141,7 @@ public object Clone() NamePrefix = NamePrefix, OriginalPropertyFromBaseType = OriginalPropertyFromBaseType?.Clone() as CodeProperty, Deprecation = Deprecation, + IsPrimaryErrorMessage = IsPrimaryErrorMessage, }; return property; } diff --git a/src/Kiota.Builder/Extensions/OpenApiDeprecationExtensionExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiDeprecationExtensionExtensions.cs index 3631362438..b2744888b6 100644 --- a/src/Kiota.Builder/Extensions/OpenApiDeprecationExtensionExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiDeprecationExtensionExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Kiota.Builder.CodeDOM; -using Kiota.Builder.OpenApiExtensions; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index 6e7416b9e9..79c1795a70 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection.Metadata; using System.Text.RegularExpressions; -using Kiota.Builder.OpenApiExtensions; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 1309963e3b..a8fc56d995 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Security; using System.Text.RegularExpressions; using System.Threading; @@ -24,15 +25,16 @@ using Kiota.Builder.Refiners; using Kiota.Builder.Validation; using Kiota.Builder.Writers; - using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.ApiManifest; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; using HttpMethod = Kiota.Builder.CodeDOM.HttpMethod; +[assembly: InternalsVisibleTo("Kiota.Builder.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] namespace Kiota.Builder; @@ -450,35 +452,10 @@ ex is SecurityException || ruleSet.AddKiotaValidationRules(config); var settings = new OpenApiReaderSettings { - ExtensionParsers = new() - { - { - OpenApiPagingExtension.Name, - static (i, _) => OpenApiPagingExtension.Parse(i) - }, - { - OpenApiEnumValuesDescriptionExtension.Name, - static (i, _ ) => OpenApiEnumValuesDescriptionExtension.Parse(i) - }, - { - OpenApiKiotaExtension.Name, - static (i, _ ) => OpenApiKiotaExtension.Parse(i) - }, - { - OpenApiDeprecationExtension.Name, - static (i, _ ) => OpenApiDeprecationExtension.Parse(i) - }, - { - OpenApiReservedParameterExtension.Name, - static (i, _ ) => OpenApiReservedParameterExtension.Parse(i) - }, - { - OpenApiEnumFlagsExtension.Name, - static (i, _ ) => OpenApiEnumFlagsExtension.Parse(i) - } - }, RuleSet = ruleSet, }; + settings.AddMicrosoftExtensionParsers(); + settings.ExtensionParsers.TryAdd(OpenApiKiotaExtension.Name, static (i, _) => OpenApiKiotaExtension.Parse(i)); try { var rawUri = config.OpenAPIFilePath.TrimEnd(ForwardSlash); @@ -530,7 +507,7 @@ public static string GetDeeperMostCommonNamespaceNameForModels(OpenApiDocument d .ToArray(); if (distinctKeys.FirstOrDefault() is not string longestKey) return string.Empty; var candidate = string.Empty; - var longestKeySegments = longestKey?.Split(NsNameSeparator, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty(); + var longestKeySegments = longestKey.Split(NsNameSeparator, StringSplitOptions.RemoveEmptyEntries); foreach (var segment in longestKeySegments) { var testValue = (candidate + NsNameSeparator + segment).Trim(NsNameSeparator); @@ -1170,6 +1147,11 @@ private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, Co ReadOnly = propertySchema?.ReadOnly ?? false, Type = resultType, Deprecation = propertySchema?.GetDeprecationInformation(), + IsPrimaryErrorMessage = kind == CodePropertyKind.Custom && + propertySchema is not null && + propertySchema.Extensions.TryGetValue(OpenApiPrimaryErrorMessageExtension.Name, out var openApiExtension) && + openApiExtension is OpenApiPrimaryErrorMessageExtension primaryErrorMessageExtension && + primaryErrorMessageExtension.IsPrimaryErrorMessage }; if (prop.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.QueryParameter) && !propertyName.Equals(childIdentifier, StringComparison.Ordinal)) diff --git a/src/Kiota.Builder/OpenApiExtensions/OpenApiDeprecationExtension.cs b/src/Kiota.Builder/OpenApiExtensions/OpenApiDeprecationExtension.cs deleted file mode 100644 index e083a466a7..0000000000 --- a/src/Kiota.Builder/OpenApiExtensions/OpenApiDeprecationExtension.cs +++ /dev/null @@ -1,95 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using Kiota.Builder.Extensions; -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Writers; - -namespace Kiota.Builder.OpenApiExtensions; - -/// -/// Extension element for OpenAPI to add deprecation information. x-ms-deprecation -/// Copied from https://github.com/microsoft/OpenAPI.NET.OData/blob/9bc5d29ded93dd1b7166f7f2434fa8fdbee6df5a/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiDeprecationExtension.cs#L16 -/// Except for the Parse method -/// -public class OpenApiDeprecationExtension : IOpenApiExtension -{ - /// - /// Name of the extension as used in the description. - /// - public static string Name => "x-ms-deprecation"; - /// - /// The date at which the element has been/will be removed entirely from the service. - /// - public DateTimeOffset? RemovalDate - { - get; set; - } - /// - /// The date at which the element has been/will be deprecated. - /// - public DateTimeOffset? Date - { - get; set; - } - /// - /// The version this revision was introduced. - /// - public string Version - { - get; set; - } = string.Empty; - /// - /// The description of the revision. - /// - public string Description - { - get; set; - } = string.Empty; - /// - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - if (writer == null) - throw new ArgumentNullException(nameof(writer)); - - if (RemovalDate.HasValue || Date.HasValue || !string.IsNullOrEmpty(Version) || !string.IsNullOrEmpty(Description)) - { - writer.WriteStartObject(); - - if (RemovalDate.HasValue) - writer.WriteProperty(nameof(RemovalDate).ToFirstCharacterLowerCase(), RemovalDate.Value); - if (Date.HasValue) - writer.WriteProperty(nameof(Date).ToFirstCharacterLowerCase(), Date.Value); - if (!string.IsNullOrEmpty(Version)) - writer.WriteProperty(nameof(Version).ToFirstCharacterLowerCase(), Version); - if (!string.IsNullOrEmpty(Description)) - writer.WriteProperty(nameof(Description).ToFirstCharacterLowerCase(), Description); - - writer.WriteEndObject(); - } - } - /// - /// Parses the to . - /// - /// The source object. - /// The . - public static OpenApiDeprecationExtension Parse(IOpenApiAny source) - { - if (source is not OpenApiObject rawObject) throw new ArgumentOutOfRangeException(nameof(source)); - var extension = new OpenApiDeprecationExtension(); - if (rawObject.TryGetValue(nameof(RemovalDate).ToFirstCharacterLowerCase(), out var removalDate) && removalDate is OpenApiDateTime removalDateValue) - extension.RemovalDate = removalDateValue.Value; - if (rawObject.TryGetValue(nameof(Date).ToFirstCharacterLowerCase(), out var date) && date is OpenApiDateTime dateValue) - extension.Date = dateValue.Value; - if (rawObject.TryGetValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is OpenApiString versionValue) - extension.Version = versionValue.Value; - if (rawObject.TryGetValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is OpenApiString descriptionValue) - extension.Description = descriptionValue.Value; - return extension; - } -} diff --git a/src/Kiota.Builder/OpenApiExtensions/OpenApiEnumFlagsExtension.cs b/src/Kiota.Builder/OpenApiExtensions/OpenApiEnumFlagsExtension.cs deleted file mode 100644 index 00031fed17..0000000000 --- a/src/Kiota.Builder/OpenApiExtensions/OpenApiEnumFlagsExtension.cs +++ /dev/null @@ -1,52 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using Kiota.Builder.Extensions; -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Writers; - -namespace Kiota.Builder.OpenApiExtensions; - -/// -/// Extension element for OpenAPI to add deprecation information. x-ms-enum-flags -/// -public class OpenApiEnumFlagsExtension : IOpenApiExtension -{ - /// - /// Name of the extension as used in the description. - /// - public static string Name => "x-ms-enum-flags"; - /// - /// Whether the enum is a flagged enum. - /// - public bool IsFlags - { - get; set; - } - /// - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - if (writer == null) - throw new ArgumentNullException(nameof(writer)); - - writer.WriteStartObject(); - writer.WriteProperty(nameof(IsFlags).ToFirstCharacterLowerCase(), IsFlags); - writer.WriteEndObject(); - } - - public static OpenApiEnumFlagsExtension Parse(IOpenApiAny source) - { - if (source is not OpenApiObject rawObject) throw new ArgumentOutOfRangeException(nameof(source)); - var extension = new OpenApiEnumFlagsExtension(); - if (rawObject.TryGetValue("isFlags", out var flagsValue) && flagsValue is OpenApiBoolean isFlags) - { - extension.IsFlags = isFlags.Value; - } - return extension; - } -} diff --git a/src/Kiota.Builder/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs b/src/Kiota.Builder/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs deleted file mode 100644 index b3fb1bf404..0000000000 --- a/src/Kiota.Builder/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs +++ /dev/null @@ -1,110 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Linq; - -using Kiota.Builder.Extensions; - -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Writers; - -namespace Kiota.Builder.OpenApiExtensions; - -/// -/// Extension element for OpenAPI to add enum values descriptions. -/// Based of the AutoRest specification https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-enum -/// THIS FILE IS A COPY OF https://github.com/microsoft/OpenAPI.NET.OData/blob/dbcf68683d1e21e00af9bfe5338e74556278419f/src/Microsoft.OpenApi.OData.Reader/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs -/// except for the parse method -/// -public class OpenApiEnumValuesDescriptionExtension : IOpenApiExtension -{ - /// - /// Name of the extension as used in the description. - /// - public static string Name => "x-ms-enum"; - - /// - /// The of the enum. - /// - public string EnumName { get; set; } = string.Empty; - - /// - /// Descriptions for the enum symbols, where the value MUST match the enum symbols in the main description - /// -#pragma warning disable CA2227 -#pragma warning disable CA1002 - public List ValuesDescriptions { get; set; } = new(); -#pragma warning restore CA1002 -#pragma warning restore CA2227 - - /// - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - ArgumentNullException.ThrowIfNull(writer); - if ((specVersion == OpenApiSpecVersion.OpenApi2_0 || specVersion == OpenApiSpecVersion.OpenApi3_0) && - !string.IsNullOrEmpty(EnumName) && - ValuesDescriptions.Any()) - { // when we upgrade to 3.1, we don't need to write this extension as JSON schema will support writing enum values - writer.WriteStartObject(); - writer.WriteProperty(nameof(Name).ToFirstCharacterLowerCase(), EnumName); - writer.WriteProperty("modelAsString", false); - writer.WriteRequiredCollection("values", ValuesDescriptions, (w, x) => - { - w.WriteStartObject(); - w.WriteProperty(nameof(x.Value).ToFirstCharacterLowerCase(), x.Value); - w.WriteProperty(nameof(x.Description).ToFirstCharacterLowerCase(), x.Description); - w.WriteProperty(nameof(x.Name).ToFirstCharacterLowerCase(), x.Name); - w.WriteEndObject(); - }); - writer.WriteEndObject(); - } - } - public static OpenApiEnumValuesDescriptionExtension Parse(IOpenApiAny source) - { - if (source is not OpenApiObject rawObject) throw new ArgumentOutOfRangeException(nameof(source)); - var extension = new OpenApiEnumValuesDescriptionExtension(); - if (rawObject.TryGetValue("values", out var values) && values is OpenApiArray valuesArray) - { - extension.ValuesDescriptions.AddRange(valuesArray - .OfType() - .Select(x => new EnumDescription(x))); - } - return extension; - } -} - -public class EnumDescription : IOpenApiElement -{ - public EnumDescription() - { - - } - public EnumDescription(OpenApiObject source) - { - ArgumentNullException.ThrowIfNull(source); - if (source.TryGetValue("value", out var rawValue) && rawValue is OpenApiString value) - Value = value.Value; - if (source.TryGetValue("description", out var rawDescription) && rawDescription is OpenApiString description) - Description = description.Value; - if (source.TryGetValue("name", out var rawName) && rawName is OpenApiString name) - Name = name.Value; - } - /// - /// The description for the enum symbol - /// - public string Description { get; set; } = string.Empty; - /// - /// The symbol for the enum symbol to use for code-generation - /// - public string Name { get; set; } = string.Empty; - /// - /// The symbol as described in the main enum schema. - /// - public string Value { get; set; } = string.Empty; -} diff --git a/src/Kiota.Builder/OpenApiExtensions/OpenApiPagingExtension.cs b/src/Kiota.Builder/OpenApiExtensions/OpenApiPagingExtension.cs deleted file mode 100644 index e47011f9e7..0000000000 --- a/src/Kiota.Builder/OpenApiExtensions/OpenApiPagingExtension.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -using Kiota.Builder.Extensions; - -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Writers; - -[assembly: InternalsVisibleTo("Kiota.Builder.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] -namespace Kiota.Builder.OpenApiExtensions; - -/// -/// Extension element for OpenAPI to add pageable information. -/// Based of the AutoRest specification https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-pageable -/// -internal class OpenApiPagingExtension : IOpenApiExtension -{ - /// - /// Name of the extension as used in the description. - /// - public static string Name => "x-ms-pageable"; - - /// - /// The name of the property that provides the collection of pageable items. - /// - public string ItemName - { - get; set; - } = "value"; - - /// - /// The name of the property that provides the next link (common: nextLink) - /// - public string NextLinkName - { - get; set; - } = "nextLink"; - - /// - /// The name (operationId) of the operation for retrieving the next page. - /// - public string OperationName - { - get; set; - } = string.Empty; - - /// - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - ArgumentNullException.ThrowIfNull(writer); - writer.WriteStartObject(); - if (!string.IsNullOrEmpty(NextLinkName)) - { - writer.WriteProperty(nameof(NextLinkName).ToFirstCharacterLowerCase(), NextLinkName); - } - - if (!string.IsNullOrEmpty(OperationName)) - { - writer.WriteProperty(nameof(OperationName).ToFirstCharacterLowerCase(), OperationName); - } - - writer.WriteProperty(nameof(ItemName).ToFirstCharacterLowerCase(), ItemName); - - writer.WriteEndObject(); - } - - public static OpenApiPagingExtension Parse(IOpenApiAny source) - { - if (source is not OpenApiObject rawObject) throw new ArgumentOutOfRangeException(nameof(source)); - var extension = new OpenApiPagingExtension(); - if (rawObject.TryGetValue(nameof(NextLinkName).ToFirstCharacterLowerCase(), out var nextLinkName) && nextLinkName is OpenApiString nextLinkNameStr) - { - extension.NextLinkName = nextLinkNameStr.Value; - } - - if (rawObject.TryGetValue(nameof(OperationName).ToFirstCharacterLowerCase(), out var opName) && opName is OpenApiString opNameStr) - { - extension.OperationName = opNameStr.Value; - } - - if (rawObject.TryGetValue(nameof(ItemName).ToFirstCharacterLowerCase(), out var itemName) && itemName is OpenApiString itemNameStr) - { - extension.ItemName = itemNameStr.Value; - } - - return extension; - } -} diff --git a/src/Kiota.Builder/OpenApiExtensions/OpenApiReservedParameterExtension.cs b/src/Kiota.Builder/OpenApiExtensions/OpenApiReservedParameterExtension.cs deleted file mode 100644 index 4291902ce2..0000000000 --- a/src/Kiota.Builder/OpenApiExtensions/OpenApiReservedParameterExtension.cs +++ /dev/null @@ -1,52 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Writers; - -namespace Kiota.Builder.OpenApiExtensions; - -/// -/// Extension element for OpenAPI to add reserved parameters. x-ms-reserved-parameters -/// -public class OpenApiReservedParameterExtension : IOpenApiExtension -{ - /// - /// Name of the extension as used in the description. - /// - public static string Name => "x-ms-reserved-parameter"; - /// - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - ArgumentNullException.ThrowIfNull(writer); - if (IsReserved.HasValue) - writer.WriteValue(IsReserved.Value); - } - /// - /// Whether the associated parameter is reserved or not. - /// - public bool? IsReserved - { - get; set; - } - /// - /// Parses the to . - /// - /// The source object. - /// The . - /// - public static OpenApiReservedParameterExtension Parse(IOpenApiAny source) - { - ArgumentNullException.ThrowIfNull(source); - if (source is not OpenApiBoolean rawBoolean) throw new ArgumentOutOfRangeException(nameof(source)); - return new OpenApiReservedParameterExtension - { - IsReserved = rawBoolean.Value - }; - } -} diff --git a/src/Kiota.Builder/OpenApiServerComparer.cs b/src/Kiota.Builder/OpenApiServerComparer.cs index 16415d2755..3bd5cdfe48 100644 --- a/src/Kiota.Builder/OpenApiServerComparer.cs +++ b/src/Kiota.Builder/OpenApiServerComparer.cs @@ -6,7 +6,7 @@ namespace Kiota.Builder; -internal partial class OpenApiServerComparer : IEqualityComparer +internal sealed partial class OpenApiServerComparer : IEqualityComparer { private static readonly Regex _protocolCleanupRegex = GetCleanupRegex(); [GeneratedRegex("^https?://", RegexOptions.IgnoreCase | RegexOptions.Compiled, 200)] diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index 0ac175dad2..5689d654c1 100644 --- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs +++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs @@ -16,6 +16,11 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); + AddPrimaryErrorMessage(generatedCode, + "Message", + () => new CodeType { Name = "string", IsNullable = false, IsExternal = true }, + true + ); MoveRequestBuilderPropertiesToBaseType(generatedCode, new CodeUsing { diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index ebccb2bad5..f3ec22130b 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -1150,8 +1150,7 @@ private static CodeInterface CopyClassAsInterface(CodeClass modelClass, Func(); - var existing = targetNS.FindChildByName(interfaceName, false); - if (existing != null) + if (targetNS.FindChildByName(interfaceName, false) is { } existing) return existing; var parentClass = modelClass.Parent as CodeClass; var insertValue = new CodeInterface @@ -1160,6 +1159,7 @@ private static CodeInterface CopyClassAsInterface(CodeClass modelClass, Func RemoveRequestConfigurationClasses(x, configurationParameterTypeUsing, defaultValueForGenericTypeParam)); } + internal static void AddPrimaryErrorMessage(CodeElement currentElement, string name, Func type, bool asProperty = false) + { + if (currentElement is CodeClass { IsErrorDefinition: true } currentClass) + { + if (asProperty) + { + currentClass.AddProperty(new CodeProperty + { + Name = name, + Access = AccessModifier.Public, + Kind = CodePropertyKind.ErrorMessageOverride, + Type = type(), + Documentation = new() + { + Description = "The primary error message.", + }, + }); + } + else + { + currentClass.AddMethod(new CodeMethod + { + Name = name, + Access = AccessModifier.Public, + Kind = CodeMethodKind.ErrorMessageOverride, + ReturnType = type(), + IsAsync = false, + IsStatic = false, + Documentation = new() + { + Description = "The primary error message.", + }, + }); + } + } + CrawlTree(currentElement, x => AddPrimaryErrorMessage(x, name, type, asProperty)); + } } diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs index 86ce81d079..80ed71ac75 100644 --- a/src/Kiota.Builder/Refiners/GoRefiner.cs +++ b/src/Kiota.Builder/Refiners/GoRefiner.cs @@ -174,6 +174,10 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance CodePropertyKind.QueryParameter, }, static s => s.ToFirstCharacterUpperCase()); + AddPrimaryErrorMessage(generatedCode, + "Error", + () => new CodeType { Name = "string", IsNullable = false, IsExternal = true } + ); GenerateCodeFiles(generatedCode); }, cancellationToken); } diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index e29910b493..362d3e8599 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -135,6 +135,10 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance ); RemoveHandlerFromRequestBuilder(generatedCode); SplitLongDiscriminatorMethods(generatedCode); + AddPrimaryErrorMessage(generatedCode, + "getMessage", + () => new CodeType { Name = "string", IsNullable = false, IsExternal = true } + ); }, cancellationToken); } private const int MaxDiscriminatorLength = 500; diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 7c1a97083a..19bda1990a 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -700,6 +700,7 @@ private static CodeInterface CreateModelInterface(CodeClass modelClass, Func(); ProcessModelClassDeclaration(modelClass, modelInterface, interfaceNamingCallback); ProcessModelClassProperties(modelClass, modelInterface, props, interfaceNamingCallback); + modelClass.AssociatedInterface = modelInterface; return modelInterface; } diff --git a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs index 97d98bb1c2..ee37610427 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; - using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; @@ -14,6 +12,8 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); + if (codeElement.Parent is not CodeClass parentClass) throw new InvalidOperationException($"The provided code element {codeElement.Name} doesn't have a parent of type {nameof(CodeClass)}"); + if (codeElement.Parent?.Parent is CodeNamespace) { writer.WriteLine(AutoGenerationHeader); @@ -35,12 +35,8 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit .Select(static x => x.ToFirstCharacterUpperCase()) .ToArray(); var derivation = derivedTypes.Any() ? ": " + derivedTypes.Aggregate(static (x, y) => $"{x}, {y}") + " " : string.Empty; - if (codeElement.Parent is CodeClass parentClass) - { - conventions.WriteLongDescription(parentClass.Documentation, writer); - conventions.WriteDeprecationAttribute(parentClass, writer); - } - writer.WriteLine($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); - writer.IncreaseIndent(); + conventions.WriteLongDescription(parentClass.Documentation, writer); + conventions.WriteDeprecationAttribute(parentClass, writer); + writer.StartBlock($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); } } diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 796899722d..1d6af02b91 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -84,6 +84,8 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w 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."); + case CodeMethodKind.ErrorMessageOverride: + throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by a property."); case CodeMethodKind.CommandBuilder: var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters; requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody); @@ -398,7 +400,7 @@ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams re (_, true) => "var collectionResult = ", (_, _) => "return ", }; - writer.WriteLine($"{prefix}await RequestAdapter.{GetSendRequestMethodName(isVoid, codeElement, codeElement.ReturnType)}(requestInfo{returnTypeFactory}, {errorMappingVarName}, cancellationToken);"); + writer.WriteLine($"{prefix}await RequestAdapter.{GetSendRequestMethodName(isVoid, codeElement, codeElement.ReturnType)}(requestInfo{returnTypeFactory}, {errorMappingVarName}, cancellationToken).ConfigureAwait(false);"); if (codeElement.ReturnType.IsCollection) writer.WriteLine("return collectionResult?.ToList();"); } diff --git a/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs index d30e1f2403..df0d692458 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs @@ -57,6 +57,12 @@ private void WritePropertyInternal(CodeProperty codeElement, LanguageWriter writ writer.DecreaseIndent(); writer.WriteLine("}"); break; + case CodePropertyKind.ErrorMessageOverride when parentClass.IsErrorDefinition: + if (parentClass.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) + writer.WriteLine($"public override {propertyType} {codeElement.Name.ToFirstCharacterUpperCase()} {{ get => {primaryMessageCodePath} ?? string.Empty; }}"); + else + writer.WriteLine($"public override {propertyType} {codeElement.Name.ToFirstCharacterUpperCase()} {{ get => base.Message; }}"); + break; case CodePropertyKind.QueryParameter when codeElement.IsNameEscaped: writer.WriteLine($"[QueryParameter(\"{codeElement.SerializationName}\")]"); goto default; diff --git a/src/Kiota.Builder/Writers/CodeTypeComparer.cs b/src/Kiota.Builder/Writers/CodeTypeComparer.cs index 48d7b99273..12d94e5927 100644 --- a/src/Kiota.Builder/Writers/CodeTypeComparer.cs +++ b/src/Kiota.Builder/Writers/CodeTypeComparer.cs @@ -4,7 +4,7 @@ using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers; -internal class CodeTypeComparer : IComparer +internal sealed class CodeTypeComparer : IComparer { private readonly bool OrderByDesc; public CodeTypeComparer(bool orderByDesc = false) diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs index 140807e7e3..637ea16b3c 100644 --- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs @@ -78,12 +78,26 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri case CodeMethodKind.ComposedTypeMarker: WriteComposedTypeMarkerBody(writer); break; + case CodeMethodKind.ErrorMessageOverride: + WriteErrorMethodOverride(parentClass, writer); + break; default: writer.WriteLine("return nil"); break; } writer.CloseBlock(); } + private static void WriteErrorMethodOverride(CodeClass parentClass, LanguageWriter writer) + { + if (parentClass.AssociatedInterface is not null && parentClass.IsErrorDefinition && parentClass.AssociatedInterface.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase() + "()") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) + { + writer.WriteLine($"return *(m.{primaryMessageCodePath})"); + } + else + { + writer.WriteLine("return m.ApiError.Error()"); + } + } private void WriteRawUrlBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer) { var rawUrlParameter = codeElement.Parameters.OfKind(CodeParameterKind.RawUrl) ?? throw new InvalidOperationException("RawUrlBuilder method should have a RawUrl parameter"); @@ -422,7 +436,8 @@ private void WriteMethodPrototype(CodeMethod code, CodeElement parentBlock, Lang CodeMethodKind.RequestBuilderBackwardCompatibility, CodeMethodKind.RawUrlConstructor, CodeMethodKind.ComposedTypeMarker, - CodeMethodKind.RawUrlBuilder) || code.IsAsync ? + CodeMethodKind.RawUrlBuilder, + CodeMethodKind.ErrorMessageOverride) || code.IsAsync ? string.Empty : "error"; if (!string.IsNullOrEmpty(finalReturnType) && !string.IsNullOrEmpty(errorDeclaration)) diff --git a/src/Kiota.Builder/Writers/Go/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/Go/CodePropertyWriter.cs index 090a009e9f..a139c2447e 100644 --- a/src/Kiota.Builder/Writers/Go/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodePropertyWriter.cs @@ -19,6 +19,8 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w { case CodePropertyKind.RequestBuilder: throw new InvalidOperationException("RequestBuilders are as properties are not supported in Go and should be replaced by methods by the refiner."); + case CodePropertyKind.ErrorMessageOverride: + throw new InvalidOperationException("Error message overrides are implemented with methods in Go."); case CodePropertyKind.QueryParameter when codeElement.IsNameEscaped: suffix = $" `uriparametername:\"{codeElement.SerializationName}\"`"; goto default; diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index 5c00c1ff53..cc1f0b79ba 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -85,6 +85,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri case CodeMethodKind.Factory when codeElement.IsOverload: WriteFactoryOverloadMethod(codeElement, parentClass, writer); break; + case CodeMethodKind.ErrorMessageOverride: + WriteErrorMethodOverride(parentClass, writer); + break; case CodeMethodKind.ComposedTypeMarker: throw new InvalidOperationException("ComposedTypeMarker is not required as interface is explicitly implemented."); default: @@ -93,6 +96,17 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri } writer.CloseBlock(); } + private static void WriteErrorMethodOverride(CodeClass parentClass, LanguageWriter writer) + { + if (parentClass.IsErrorDefinition && parentClass.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterLowerCase(), static x => x.Name.ToFirstCharacterLowerCase() + "()") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) + { + writer.WriteLine($"return this.{primaryMessageCodePath};"); + } + else + { + writer.WriteLine("return super.getMessage();"); + } + } private void WriteRawUrlBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer) { var rawUrlParameter = codeElement.Parameters.OfKind(CodeParameterKind.RawUrl) ?? throw new InvalidOperationException("RawUrlBuilder method should have a RawUrl parameter"); @@ -684,6 +698,10 @@ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string var finalReturnType = isConstructor ? string.Empty : $" {returnTypeAsyncPrefix}{collectionCorrectedReturnType}{returnTypeAsyncSuffix}"; var staticModifier = code.IsStatic ? " static" : string.Empty; conventions.WriteDeprecatedAnnotation(code, writer); + if (code.Kind is CodeMethodKind.ErrorMessageOverride) + { + writer.WriteLine($"@Override"); + } writer.WriteLine($"{accessModifier}{staticModifier}{finalReturnType} {methodName}({parameters}) {{"); } private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, string returnType) diff --git a/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs index aa089e9ff2..e72a95a031 100644 --- a/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs @@ -19,6 +19,8 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w conventions.WriteDeprecatedAnnotation(codeElement, writer); switch (codeElement.Kind) { + case CodePropertyKind.ErrorMessageOverride: + throw new InvalidOperationException("Error message overrides are implemented with methods in Java."); case CodePropertyKind.RequestBuilder: writer.WriteLine("@jakarta.annotation.Nonnull"); writer.StartBlock($"{conventions.GetAccessModifier(codeElement.Access)} {returnType} {codeElement.Name.ToFirstCharacterLowerCase()}() {{"); diff --git a/src/Kiota.Builder/Writers/Php/PhpConventionService.cs b/src/Kiota.Builder/Writers/Php/PhpConventionService.cs index e1e5b87017..001a786ab9 100644 --- a/src/Kiota.Builder/Writers/Php/PhpConventionService.cs +++ b/src/Kiota.Builder/Writers/Php/PhpConventionService.cs @@ -171,8 +171,7 @@ public void AddRequestBuilderBody(string returnType, LanguageWriter writer, stri { ArgumentNullException.ThrowIfNull(writer); var joined = string.Empty; - var codeParameters = pathParameters?.ToList(); - if (pathParameters != null && (codeParameters?.Any() ?? false)) + if (pathParameters?.ToList() is { } codeParameters && codeParameters.Any()) { joined = $", {string.Join(", ", codeParameters.Select(static x => $"${x.Name.ToFirstCharacterLowerCase()}"))}"; } diff --git a/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs new file mode 100644 index 0000000000..6c71045564 --- /dev/null +++ b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers; + +internal static class ProprietableBlockExtensions +{ + private static readonly Func isCustomProperty = x => x.Kind is CodePropertyKind.Custom; + private static readonly Func isPrimaryErrorMessage = x => isCustomProperty(x) && x.IsPrimaryErrorMessage; + private static readonly Func isGetterMethod = x => x.Kind is CodeMethodKind.Getter; + internal static string GetPrimaryMessageCodePath(this ProprietableBlock block, + Func propertyNameNormalization, + Func methodNameNormalization, + string pathSegment = ".") where TBlockKind : Enum where TBlockDeclaration : ProprietableBlockDeclaration, new() + { + if (block is CodeInterface currentInterface) + { + if (currentInterface.Methods.FirstOrDefault(static x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + return methodNameNormalization(primaryErrorMessageMethod); + else if (currentInterface.Properties.FirstOrDefault(isPrimaryErrorMessage) is CodeProperty primaryErrorMessageProperty) + return propertyNameNormalization(primaryErrorMessageProperty); + else if (currentInterface.Methods + .Where(isGetterMethod) + .Select(x => new { Value = x.ReturnType is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface && codeInterface.GetPrimaryMessageCodePath(propertyNameNormalization, methodNameNormalization, pathSegment) is string segment && !string.IsNullOrEmpty(segment) ? $"{methodNameNormalization(x)}{pathSegment}{segment}" : string.Empty, IsMethod = true }) + .Union(currentInterface.Properties + .Where(isCustomProperty) + .Select(x => new { Value = x.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface && codeInterface.GetPrimaryMessageCodePath(propertyNameNormalization, methodNameNormalization, pathSegment) is string segment && !string.IsNullOrEmpty(segment) ? $"{propertyNameNormalization(x)}{pathSegment}{segment}" : string.Empty, IsMethod = false })) + .OrderBy(static x => x.IsMethod) + .ThenBy(static x => x.Value, StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(static x => !string.IsNullOrEmpty(x.Value)) is { } primaryMessageCodePath) + return primaryMessageCodePath.Value; + + } + else if (block is CodeClass currentClass) + { + if (currentClass.Methods.FirstOrDefault(static x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + return methodNameNormalization(primaryErrorMessageMethod); + else if (currentClass.Properties.FirstOrDefault(isPrimaryErrorMessage) is CodeProperty primaryErrorMessageProperty) + return propertyNameNormalization(primaryErrorMessageProperty); + else if (currentClass.Methods + .Where(isGetterMethod) + .Select(x => new { Value = x.ReturnType is CodeType codeType && codeType.TypeDefinition is CodeClass codeClass && codeClass.GetPrimaryMessageCodePath(propertyNameNormalization, methodNameNormalization, pathSegment) is string segment && !string.IsNullOrEmpty(segment) ? $"{methodNameNormalization(x)}{pathSegment}{segment}" : string.Empty, IsMethod = true }) + .Union(currentClass.Properties + .Where(isCustomProperty) + .Select(x => new { Value = x.Type is CodeType codeType && codeType.TypeDefinition is CodeClass codeClass && codeClass.GetPrimaryMessageCodePath(propertyNameNormalization, methodNameNormalization, pathSegment) is string segment && !string.IsNullOrEmpty(segment) ? $"{propertyNameNormalization(x)}{pathSegment}{segment}" : string.Empty, IsMethod = false })) + .OrderBy(static x => x.IsMethod) + .ThenBy(static x => x.Value, StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(static x => !string.IsNullOrEmpty(x.Value)) is { } primaryMessageCodePath) + return primaryMessageCodePath.Value; + } + return string.Empty; + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 9a94b97cf1..5a7c0a2eaa 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -188,10 +188,21 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer.WriteLine($"...deserializeInto{inherits.Name.ToFirstCharacterUpperCase()}({param.Name.ToFirstCharacterLowerCase()}),"); } + var primaryErrorMapping = string.Empty; + var primaryErrorMappingKey = string.Empty; + var parentClass = codeFunction.OriginalMethodParentClass; + + if (parentClass.IsErrorDefinition && parentClass.AssociatedInterface is not null && parentClass.AssociatedInterface.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterLowerCase(), static x => x.Name.ToFirstCharacterLowerCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) + { + primaryErrorMapping = $" {param.Name.ToFirstCharacterLowerCase()}.message = {param.Name.ToFirstCharacterLowerCase()}.{primaryMessageCodePath} ?? \"\";"; + primaryErrorMappingKey = primaryMessageCodePath.Split("?.", StringSplitOptions.RemoveEmptyEntries)[0]; + } + foreach (var otherProp in properties) { var keyName = !string.IsNullOrWhiteSpace(otherProp.SerializationName) ? otherProp.SerializationName.ToFirstCharacterLowerCase() : otherProp.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)}; }},"); + var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; + writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)};{suffix} }},"); } writer.CloseBlock(); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index aeadf2d546..e26e45f012 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -77,6 +77,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri throw new InvalidOperationException("RawUrlConstructor is not supported as typescript relies on union types."); case CodeMethodKind.RequestBuilderBackwardCompatibility: throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); + case CodeMethodKind.ErrorMessageOverride: + throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by the deserializer function in typescript."); default: WriteDefaultMethodBody(codeElement, writer); break; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs index 7d1ea62a13..20e4cbc36a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs @@ -22,8 +22,8 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w case CodeInterface: WriteCodePropertyForInterface(codeElement, writer, returnType, isFlagEnum); break; - default: - WriteCodePropertyForClass(codeElement, writer, returnType, isFlagEnum); + case CodeClass codeClass: + WriteCodePropertyForClass(codeElement, codeClass, writer, returnType, isFlagEnum); break; } } @@ -33,14 +33,15 @@ private static void WriteCodePropertyForInterface(CodeProperty codeElement, Lang writer.WriteLine($"{codeElement.Name.ToFirstCharacterLowerCase()}?: {returnType}{(isFlagEnum ? "[]" : string.Empty)}{(codeElement.Type.IsNullable ? " | undefined" : string.Empty)};"); } - private void WriteCodePropertyForClass(CodeProperty codeElement, LanguageWriter writer, string returnType, bool isFlagEnum) + private void WriteCodePropertyForClass(CodeProperty codeElement, CodeClass parentClass, LanguageWriter writer, string returnType, bool isFlagEnum) { switch (codeElement.Kind) { + case CodePropertyKind.ErrorMessageOverride: + throw new InvalidOperationException($"Primary message mapping is done in deserializer function in TypeScript."); case CodePropertyKind.RequestBuilder: writer.StartBlock($"{conventions.GetAccessModifier(codeElement.Access)} get {codeElement.Name.ToFirstCharacterLowerCase()}(): {returnType} {{"); - if (codeElement.Parent is CodeClass parentClass) - conventions.AddRequestBuilderBody(parentClass, returnType, writer); + conventions.AddRequestBuilderBody(parentClass, returnType, writer); writer.CloseBlock(); break; default: diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs index 17c3aeb379..97f18b4a46 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using Kiota.Builder.Extensions; -using Kiota.Builder.OpenApiExtensions; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; using Xunit; diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 67479cd5aa..4fdb78f5c1 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -10,11 +10,11 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; -using Kiota.Builder.OpenApiExtensions; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -1803,6 +1803,14 @@ public void AddsErrorMapping() { "errorId", new OpenApiSchema{ Type = "string", + Extensions = new Dictionary + { + { OpenApiPrimaryErrorMessageExtension.Name, + new OpenApiPrimaryErrorMessageExtension { + IsPrimaryErrorMessage = true + } + } + } } } } @@ -1823,6 +1831,14 @@ public void AddsErrorMapping() { "serviceErrorId", new OpenApiSchema{ Type = "string", + Extensions = new Dictionary + { + { OpenApiPrimaryErrorMessageExtension.Name, + new OpenApiPrimaryErrorMessageExtension { + IsPrimaryErrorMessage = true + } + } + } } } } @@ -1843,6 +1859,19 @@ public void AddsErrorMapping() { "authenticationRealm", new OpenApiSchema{ Type = "string", + Extensions = new Dictionary + { + { OpenApiPrimaryErrorMessageExtension.Name, + new OpenApiPrimaryErrorMessageExtension { + IsPrimaryErrorMessage = true + } + } + } + } + }, + { + "authenticationCode", new OpenApiSchema{ + Type = "string", } } } @@ -1867,15 +1896,24 @@ public void AddsErrorMapping() var errorType401 = codeModel.FindChildByName("tasks401Error"); Assert.NotNull(errorType401); Assert.True(errorType401.IsErrorDefinition); - Assert.NotNull(errorType401.FindChildByName("authenticationRealm")); + var error401Property = errorType401.FindChildByName("authenticationCode", false); + Assert.NotNull(error401Property); + Assert.False(error401Property.IsPrimaryErrorMessage); + var errorType401MainProperty = errorType401.FindChildByName("authenticationRealm", false); + Assert.NotNull(errorType401MainProperty); + Assert.True(errorType401MainProperty.IsPrimaryErrorMessage); var errorType4XX = codeModel.FindChildByName("tasks4XXError"); Assert.NotNull(errorType4XX); Assert.True(errorType4XX.IsErrorDefinition); - Assert.NotNull(errorType4XX.FindChildByName("errorId")); + var errorType4XXProperty = errorType4XX.FindChildByName("errorId", false); + Assert.NotNull(errorType4XXProperty); + Assert.True(errorType4XXProperty.IsPrimaryErrorMessage); var errorType5XX = codeModel.FindChildByName("tasks5XXError"); Assert.NotNull(errorType5XX); Assert.True(errorType5XX.IsErrorDefinition); - Assert.NotNull(errorType5XX.FindChildByName("serviceErrorId")); + var errorType5XXProperty = errorType5XX.FindChildByName("serviceErrorId", false); + Assert.NotNull(errorType5XXProperty); + Assert.True(errorType5XXProperty.IsPrimaryErrorMessage); } [Fact] public void IgnoresErrorCodesWithNoSchema() diff --git a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiDeprecationExtensionTests.cs b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiDeprecationExtensionTests.cs deleted file mode 100644 index 3ea43e80a7..0000000000 --- a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiDeprecationExtensionTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using Kiota.Builder.OpenApiExtensions; -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Writers; -using Xunit; - -namespace Kiota.Builder.Tests.OpenApiExtensions; - -public class OpenApiDeprecationExtensionTests -{ - [Fact] - public void Parses() - { - var oaiValue = new OpenApiObject - { - { "date", new OpenApiDateTime(new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new TimeSpan(4, 0, 0)))}, - { "removalDate", new OpenApiDateTime(new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new TimeSpan(4, 0, 0)))}, - { "version", new OpenApiString("v1.0")}, - { "description", new OpenApiString("removing")} - }; - var value = OpenApiDeprecationExtension.Parse(oaiValue); - Assert.NotNull(value); - Assert.Equal("v1.0", value.Version); - Assert.Equal("removing", value.Description); - Assert.Equal(new DateTimeOffset(2023, 05, 04, 16, 0, 0, 0, 0, new TimeSpan(4, 0, 0)), value.Date); - Assert.Equal(new DateTimeOffset(2023, 05, 04, 16, 0, 0, 0, 0, new TimeSpan(4, 0, 0)), value.RemovalDate); - } - [Fact] - public void Serializes() - { - var value = new OpenApiDeprecationExtension - { - Date = new DateTimeOffset(2023, 05, 04, 16, 0, 0, 0, 0, new TimeSpan(4, 0, 0)), - RemovalDate = new DateTimeOffset(2023, 05, 04, 16, 0, 0, 0, 0, new TimeSpan(4, 0, 0)), - Version = "v1.0", - Description = "removing" - }; - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - - value.Write(writer, OpenApiSpecVersion.OpenApi3_0); - var result = sWriter.ToString(); - Assert.Equal("{\n \"removalDate\": \"2023-05-04T16:00:00.0000000+04:00\",\n \"date\": \"2023-05-04T16:00:00.0000000+04:00\",\n \"version\": \"v1.0\",\n \"description\": \"removing\"\n}", result); - } -} diff --git a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumFlagsExtensionTests.cs b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumFlagsExtensionTests.cs deleted file mode 100644 index 4cee33e972..0000000000 --- a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumFlagsExtensionTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using Kiota.Builder.OpenApiExtensions; - -using Microsoft.OpenApi; -using Microsoft.OpenApi.Writers; - -using Moq; - -using Xunit; - -namespace Kiota.Builder.Tests.OpenApiExtensions; - -public class OpenApiEnumFlagsExtensionTests -{ - [Fact] - public void ExtensionNameMatchesExpected() - { - // Act - string name = OpenApiEnumFlagsExtension.Name; - string expectedName = "x-ms-enum-flags"; - - // Assert - Assert.Equal(expectedName, name); - } - - [Fact] - public void WritesDefaultValues() - { - // Arrange - OpenApiEnumFlagsExtension extension = new(); - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - // Act - extension.Write(writer, OpenApiSpecVersion.OpenApi3_0); - string result = sWriter.ToString(); - - // Assert - Assert.Contains("\"isFlags\": false", result); - Assert.DoesNotContain("\"style\"", result); - Assert.False(extension.IsFlags); - } - - [Fact] - public void WritesAllDefaultValues() - { - // Arrange - OpenApiEnumFlagsExtension extension = new() - { - IsFlags = true - }; - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - // Act - extension.Write(writer, OpenApiSpecVersion.OpenApi3_0); - string result = sWriter.ToString(); - - // Assert - Assert.Contains("\"isFlags\": true", result); - Assert.True(extension.IsFlags); - } - - [Fact] - public void WritesAllValues() - { - // Arrange - OpenApiEnumFlagsExtension extension = new() - { - IsFlags = true - }; - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - // Act - extension.Write(writer, OpenApiSpecVersion.OpenApi3_0); - string result = sWriter.ToString(); - - // Assert - Assert.True(extension.IsFlags); - Assert.Contains("\"isFlags\": true", result); - } -} - diff --git a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTests.cs b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTests.cs deleted file mode 100644 index 9cf6802f29..0000000000 --- a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; - -using Kiota.Builder.OpenApiExtensions; - -using Microsoft.OpenApi; -using Microsoft.OpenApi.Writers; - -using Moq; - -using Xunit; - -namespace Kiota.Builder.Tests.OpenApiExtensions; - -public class OpenApiEnumValuesDescriptionExtensionTests -{ - [Fact] - public void NOOPTestForCoverage() - { - // This class is already covered by the convertion library tests - var value = new OpenApiEnumValuesDescriptionExtension - { - EnumName = "some enum", - ValuesDescriptions = new List - { - new EnumDescription - { - Value = "some value", - }, - }, - }; - var writer = new Mock(); - value.Write(writer.Object, OpenApiSpecVersion.OpenApi3_0); - writer.Verify(static x => x.WriteStartObject(), Times.AtLeastOnce()); - } -} - diff --git a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiPagingExtensionsTests.cs b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiPagingExtensionsTests.cs deleted file mode 100644 index f04847c59f..0000000000 --- a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiPagingExtensionsTests.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.IO; - -using Kiota.Builder.OpenApiExtensions; - -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Writers; - -using Xunit; - -namespace Kiota.Builder.Tests.OpenApiExtensions; - -public class OpenApiPagingExtensionsTests -{ - [Fact] - public void ExtensionNameMatchesExpected() - { - // Act - string name = OpenApiPagingExtension.Name; - var expectedName = "x-ms-pageable"; - - // Assert - Assert.Equal(expectedName, name); - } - - [Fact] - public void ThrowsOnMissingWriter() - { - // Arrange - OpenApiPagingExtension extension = new(); - - // Act - // Assert - Assert.Throws(() => extension.Write(null, OpenApiSpecVersion.OpenApi3_0)); - } - - [Fact] - public void WritesNothingWhenNoValues() - { - // Arrange - OpenApiPagingExtension extension = new(); - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - // Act - extension.Write(writer, OpenApiSpecVersion.OpenApi3_0); - var result = sWriter.ToString(); - - // Assert - Assert.Equal("value", extension.ItemName); - Assert.Equal("nextLink", extension.NextLinkName); - Assert.Empty(extension.OperationName); - } - - [Fact] - public void WritesPagingInfo() - { - // Arrange - OpenApiPagingExtension extension = new(); - extension.NextLinkName = "nextLink"; - extension.OperationName = "usersGet"; - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - // Act - extension.Write(writer, OpenApiSpecVersion.OpenApi3_0); - var result = sWriter.ToString(); - - // Assert - Assert.Contains("value", result); - Assert.Contains("itemName\": \"value", result); - Assert.Contains("nextLinkName\": \"nextLink", result); - Assert.Contains("operationName\": \"usersGet", result); - } - - [Fact] - public void ParsesPagingInfo() - { - // Arrange - var obj = new OpenApiObject - { - ["nextLinkName"] = new OpenApiString("@odata.nextLink"), - ["operationName"] = new OpenApiString("more"), - ["itemName"] = new OpenApiString("item"), - }; - - // Act - var extension = OpenApiPagingExtension.Parse(obj); - - // Assert - Assert.Equal("@odata.nextLink", extension.NextLinkName); - Assert.Equal("item", extension.ItemName); - Assert.Equal("more", extension.OperationName); - } -} diff --git a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiReservedParameterExtensionTests.cs b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiReservedParameterExtensionTests.cs deleted file mode 100644 index dcac5011eb..0000000000 --- a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiReservedParameterExtensionTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.IO; -using Kiota.Builder.OpenApiExtensions; -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Writers; -using Xunit; - -namespace Kiota.Builder.Tests.OpenApiExtensions; - -public class OpenApiReservedParameterExtensionTests -{ - [Fact] - public void Parses() - { - var oaiValue = new OpenApiBoolean(true); - var value = OpenApiReservedParameterExtension.Parse(oaiValue); - Assert.NotNull(value); - Assert.True(value.IsReserved); - } - [Fact] - public void Serializes() - { - var value = new OpenApiReservedParameterExtension - { - IsReserved = true - }; - using TextWriter sWriter = new StringWriter(); - OpenApiJsonWriter writer = new(sWriter); - - value.Write(writer, OpenApiSpecVersion.OpenApi3_0); - var result = sWriter.ToString(); - Assert.Equal("true", result, StringComparer.OrdinalIgnoreCase); - } -} diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs index 33814234d8..3196b658f1 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs @@ -216,5 +216,37 @@ public void WritesDeprecationInformation() var result = tw.ToString(); Assert.Contains("[Obsolete(\"deprecation message as of v1.0 on 2021-01-01 and will be removed 2023-01-01\")]", result); } + [Fact] + public void WritesMessageOverrideOnPrimary() + { + // Given + parentClass.IsErrorDefinition = true; + parentClass.AddProperty(new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + }, + }); + var overrideProperty = parentClass.AddProperty(new CodeProperty + { + Name = "Message", + Kind = CodePropertyKind.ErrorMessageOverride, + Type = new CodeType + { + Name = "string", + }, + }).First(); + + // When + writer.Write(overrideProperty); + var result = tw.ToString(); + + // Then + Assert.Contains("public override string Message { get => Prop1 ?? string.Empty; }", result); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs index 5e79e91707..0e2bb8271a 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs @@ -17,13 +17,14 @@ public class CodeClassEndWriterTests : IDisposable private readonly LanguageWriter writer; private readonly CodeBlockEndWriter codeElementWriter; private readonly CodeClass parentClass; + private readonly CodeNamespace root; public CodeClassEndWriterTests() { codeElementWriter = new CodeBlockEndWriter(); writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Go, DefaultPath, DefaultName); tw = new StringWriter(); writer.SetTextWriter(tw); - var root = CodeNamespace.InitRootNamespace(); + root = CodeNamespace.InitRootNamespace(); parentClass = new CodeClass { Name = "parentClass" @@ -44,13 +45,13 @@ public void ClosesNestedClasses() }).First(); codeElementWriter.WriteCodeElement(child.EndBlock, writer); var result = tw.ToString(); - Assert.Equal(1, result.Count(x => x == '}')); + Assert.Equal(1, result.Count(static x => x == '}')); } [Fact] public void ClosesNonNestedClasses() { codeElementWriter.WriteCodeElement(parentClass.EndBlock, writer); var result = tw.ToString(); - Assert.Equal(1, result.Count(x => x == '}')); + Assert.Equal(1, result.Count(static x => x == '}')); } } diff --git a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs index ba518deaa8..2e31b9fdc6 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs @@ -2057,4 +2057,71 @@ public void WritesComposedMarker() Assert.Contains("return true", result, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("error", result, StringComparison.OrdinalIgnoreCase); } + [Fact] + public void WritesMessageOverrideOnPrimary() + { + // Given + parentClass = root.AddClass(new CodeClass + { + Name = "parentClass", + IsErrorDefinition = true, + Kind = CodeClassKind.Model, + }).First(); + var prop1 = parentClass.AddProperty(new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + }, + }).First(); + parentClass.AddMethod(new CodeMethod + { + Name = "GetProp1", + Kind = CodeMethodKind.Getter, + ReturnType = prop1.Type, + Access = AccessModifier.Public, + AccessedProperty = prop1, + IsAsync = false, + IsStatic = false, + }); + var method = parentClass.AddMethod(new CodeMethod + { + Kind = CodeMethodKind.ErrorMessageOverride, + ReturnType = new CodeType + { + Name = "string", + IsNullable = false, + }, + IsAsync = false, + IsStatic = false, + Name = "Error" + }).First(); + var parentInterface = root.AddInterface(new CodeInterface + { + Name = "parentInterface", + OriginalClass = parentClass, + }).First(); + parentInterface.AddMethod(new CodeMethod + { + Name = "GetProp1", + Kind = CodeMethodKind.Getter, + ReturnType = prop1.Type, + Access = AccessModifier.Public, + AccessedProperty = prop1, + IsAsync = false, + IsStatic = false, + }); + parentClass.AssociatedInterface = parentInterface; + + // When + writer.Write(method); + var result = tw.ToString(); + + // Then + Assert.Contains("Error()(string) {", result); + Assert.Contains("return *(m.GetProp1()", result); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index 3d2cddff44..26cc6dd30e 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -2039,4 +2039,56 @@ public void WritesDeprecationInformation() Assert.Contains("v2.0", result); Assert.Contains("@Deprecated", result); } + [Fact] + public void WritesMessageOverrideOnPrimary() + { + // Given + parentClass = root.AddClass(new CodeClass + { + Name = "parentClass", + IsErrorDefinition = true, + Kind = CodeClassKind.Model, + }).First(); + var prop1 = parentClass.AddProperty(new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + }, + }).First(); + parentClass.AddMethod(new CodeMethod + { + Name = "GetProp1", + Kind = CodeMethodKind.Getter, + ReturnType = prop1.Type, + Access = AccessModifier.Public, + AccessedProperty = prop1, + IsAsync = false, + IsStatic = false, + }); + var method = parentClass.AddMethod(new CodeMethod + { + Kind = CodeMethodKind.ErrorMessageOverride, + ReturnType = new CodeType + { + Name = "string", + IsNullable = false, + }, + IsAsync = false, + IsStatic = false, + Name = "getErrorMessage" + }).First(); + + // When + writer.Write(method); + var result = tw.ToString(); + + // Then + Assert.Contains("@Override", result); + Assert.Contains("String getErrorMessage() ", result); + Assert.Contains("return this.getProp1()", result); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs b/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs new file mode 100644 index 0000000000..32828000bd --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs @@ -0,0 +1,270 @@ +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; +using Kiota.Builder.Writers; +using Xunit; + +namespace Kiota.Builder.Tests.Writers; + +public class ProprietableBlockExtensions +{ + [Fact] + public void GetsTheCodePathForFirstLevelProperty() + { + // Given + var block = new CodeClass + { + Name = "testClass", + }; + block.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + + // When + var result = block.GetPrimaryMessageCodePath( + static x => x.Name.ToFirstCharacterUpperCase(), + static x => x.Name.ToFirstCharacterUpperCase(), + "?." + ); + + // Then + Assert.Equal("Prop1", result); + } + [Fact] + public void GetsNothingOnNoPrimaryMessage() + { + // Given + var block = new CodeClass + { + Name = "testClass", + }; + block.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + + // When + var result = block.GetPrimaryMessageCodePath( + static x => x.Name.ToFirstCharacterUpperCase(), + static x => x.Name.ToFirstCharacterUpperCase(), + "?." + ); + + // Then + Assert.Empty(result); + } + [Fact] + public void GetsTheCodePathForANestedProperty() + { + // Given + var block = new CodeClass + { + Name = "testClass", + }; + var nestedBlockLevel1 = new CodeClass + { + Name = "nestedClassLevel1", + }; + var nestedBlockLevel2 = new CodeClass + { + Name = "nestedClassLevel2", + }; + nestedBlockLevel2.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + nestedBlockLevel1.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = nestedBlockLevel2.Name, + TypeDefinition = nestedBlockLevel2, + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + block.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = nestedBlockLevel1.Name, + TypeDefinition = nestedBlockLevel1, + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + + // When + var result = block.GetPrimaryMessageCodePath( + static x => x.Name.ToFirstCharacterUpperCase(), + static x => x.Name.ToFirstCharacterUpperCase(), + "?." + ); + + // Then + Assert.Equal("Prop1?.Prop1?.Prop1", result); + } + [Fact] + public void GetsTheShortestCodePathForMultiplePrimaryMessages() + { + // Given + var block = new CodeClass + { + Name = "testClass", + }; + var nestedBlockLevel1 = new CodeClass + { + Name = "nestedClassLevel1", + }; + var nestedBlockLevel2 = new CodeClass + { + Name = "nestedClassLevel2", + }; + nestedBlockLevel2.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + nestedBlockLevel1.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = nestedBlockLevel2.Name, + TypeDefinition = nestedBlockLevel2, + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + } + } + ); + block.AddProperty( + new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = nestedBlockLevel1.Name, + TypeDefinition = nestedBlockLevel1, + } + }, + new CodeProperty + { + Name = "prop2", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + } + } + ); + + // When + var result = block.GetPrimaryMessageCodePath( + static x => x.Name.ToFirstCharacterUpperCase(), + static x => x.Name.ToFirstCharacterUpperCase(), + "?." + ); + + // Then + Assert.Equal("Prop1?.Prop2", result); + } +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 5eea164c76..aeca11d3f7 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -368,4 +368,52 @@ public async Task AddsUsingsForErrorTypesForRequestExecutor() Assert.NotNull(serializeFunction); Assert.Contains("createError4XXFromDiscriminatorValue", declaration.Usings.Select(x => x.Declaration?.Name)); } + [Fact] + public async Task WritesMessageOverrideOnPrimary() + { + // Given + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + parentClass.IsErrorDefinition = true; + parentClass.AddMethod(new CodeMethod + { + Kind = CodeMethodKind.Deserializer, + Name = "deserializer", + ReturnType = new CodeType + { + Name = "Dictionary", + }, + }, new CodeMethod + { + Name = "Serializer", + Kind = CodeMethodKind.Serializer, + ReturnType = new CodeType + { + Name = "void", + }, + }); + parentClass.AddProperty(new CodeProperty + { + Name = "prop1", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string", + }, + }); + + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var function = root.FindChildByName("deserializeIntoODataError"); + + // When + writer.Write(function); + var result = tw.ToString(); + + // Then + Assert.Contains("oDataError.message = oDataError.prop1 ?? \"\"", result); + } }