From f4eaf3cb020888ae6327519dba403e116b72cff0 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 5 Sep 2023 18:55:04 -0400 Subject: [PATCH 01/20] - removes OSS test explorer from recommended extensions Signed-off-by: Vincent Biret --- .vscode/extensions.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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"] } From d0856832af0f8b21410ecf385d76714a7a93bf2c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 6 Sep 2023 15:14:21 -0400 Subject: [PATCH 02/20] - moves extensions from kiota --- .../OpenApiDeprecationExtensionExtensions.cs | 2 +- .../OpenApiUrlTreeNodeExtensions.cs | 2 +- src/Kiota.Builder/KiotaBuilder.cs | 3 + .../OpenApiDeprecationExtension.cs | 95 --------------- .../OpenApiEnumFlagsExtension.cs | 52 --------- .../OpenApiEnumValuesDescriptionExtension.cs | 110 ------------------ .../OpenApiPagingExtension.cs | 90 -------------- .../OpenApiReservedParameterExtension.cs | 52 --------- src/Kiota.Builder/OpenApiServerComparer.cs | 2 +- src/Kiota.Builder/Writers/CodeTypeComparer.cs | 2 +- ...nApiDeprecationExtensionExtensionsTests.cs | 2 +- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 2 +- .../OpenApiDeprecationExtensionTests.cs | 48 -------- .../OpenApiEnumFlagsExtensionTests.cs | 85 -------------- ...nApiEnumValuesDescriptionExtensionTests.cs | 36 ------ .../OpenApiPagingExtensionsTests.cs | 96 --------------- .../OpenApiReservedParameterExtensionTests.cs | 35 ------ 17 files changed, 9 insertions(+), 705 deletions(-) delete mode 100644 src/Kiota.Builder/OpenApiExtensions/OpenApiDeprecationExtension.cs delete mode 100644 src/Kiota.Builder/OpenApiExtensions/OpenApiEnumFlagsExtension.cs delete mode 100644 src/Kiota.Builder/OpenApiExtensions/OpenApiEnumValuesDescriptionExtension.cs delete mode 100644 src/Kiota.Builder/OpenApiExtensions/OpenApiPagingExtension.cs delete mode 100644 src/Kiota.Builder/OpenApiExtensions/OpenApiReservedParameterExtension.cs delete mode 100644 tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiDeprecationExtensionTests.cs delete mode 100644 tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumFlagsExtensionTests.cs delete mode 100644 tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiEnumValuesDescriptionExtensionTests.cs delete mode 100644 tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiPagingExtensionsTests.cs delete mode 100644 tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiReservedParameterExtensionTests.cs 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..26e5f6869c 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Reflection.Metadata; using System.Text.RegularExpressions; -using Kiota.Builder.OpenApiExtensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Services; namespace Kiota.Builder.Extensions; diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 1309963e3b..ce5f19c615 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -28,11 +28,14 @@ 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; +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Kiota.Builder.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] namespace Kiota.Builder; 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/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/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs index 17c3aeb379..3af6ca9cb6 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using Kiota.Builder.Extensions; -using Kiota.Builder.OpenApiExtensions; +using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 67479cd5aa..842bdf8df6 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; 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); - } -} From 3385d7d25504afa480b12c1c6e127dd323b7c1bc Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 6 Sep 2023 15:37:40 -0400 Subject: [PATCH 03/20] - moves to facilitation method for ms extensions --- src/Kiota.Builder/KiotaBuilder.cs | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index ce5f19c615..3a5a086dbe 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -453,35 +453,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); From 10985e6ec4e03210c11871843d9b23f557c56a62 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 6 Sep 2023 15:49:54 -0400 Subject: [PATCH 04/20] - adds extension mapping to property Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeProperty.cs | 8 ++++++++ src/Kiota.Builder/KiotaBuilder.cs | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/Kiota.Builder/CodeDOM/CodeProperty.cs b/src/Kiota.Builder/CodeDOM/CodeProperty.cs index 67d4d5844f..e2b3732626 100644 --- a/src/Kiota.Builder/CodeDOM/CodeProperty.cs +++ b/src/Kiota.Builder/CodeDOM/CodeProperty.cs @@ -110,6 +110,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 +137,7 @@ public object Clone() NamePrefix = NamePrefix, OriginalPropertyFromBaseType = OriginalPropertyFromBaseType?.Clone() as CodeProperty, Deprecation = Deprecation, + IsPrimaryErrorMessage = IsPrimaryErrorMessage, }; return property; } diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 3a5a086dbe..5b68e960c6 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1148,6 +1148,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)) From cb3a01124453e08c29a6064d83b72624aa511861 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 7 Sep 2023 08:59:39 -0400 Subject: [PATCH 05/20] - adds unit test for primary message property Signed-off-by: Vincent Biret --- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 842bdf8df6..4fdb78f5c1 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -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() From e8bcec06b82b9de68516344148c590e39054c519 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 7 Sep 2023 10:28:24 -0400 Subject: [PATCH 06/20] - adds an extension method to get the code path of the error message Signed-off-by: Vincent Biret --- .../OpenApiUrlTreeNodeExtensions.cs | 3 +- src/Kiota.Builder/KiotaBuilder.cs | 3 +- .../Writers/ProprietableBlockExtensions.cs | 52 ++++ ...nApiDeprecationExtensionExtensionsTests.cs | 2 +- .../ProprietableBlockExtensionsTests.cs | 231 ++++++++++++++++++ 5 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs create mode 100644 tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index 26e5f6869c..79c1795a70 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection.Metadata; using System.Text.RegularExpressions; -using Microsoft.OpenApi.Models; using Microsoft.OpenApi.MicrosoftExtensions; +using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; namespace Kiota.Builder.Extensions; diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 5b68e960c6..dc62b0a306 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,7 +25,6 @@ using Kiota.Builder.Refiners; using Kiota.Builder.Validation; using Kiota.Builder.Writers; - using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.ApiManifest; @@ -34,7 +34,6 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; using HttpMethod = Kiota.Builder.CodeDOM.HttpMethod; -using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Kiota.Builder.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] namespace Kiota.Builder; diff --git a/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs new file mode 100644 index 0000000000..d7c103ae3a --- /dev/null +++ b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs @@ -0,0 +1,52 @@ +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.Properties.FirstOrDefault(isPrimaryErrorMessage) is CodeProperty primaryErrorMessageProperty) + return propertyNameNormalization(primaryErrorMessageProperty); + else if (currentInterface.Methods.FirstOrDefault(x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + return methodNameNormalization(primaryErrorMessageMethod); + else if (currentInterface.Methods + .Where(isGetterMethod) + .Select(x => 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) + .Union(currentInterface.Properties + .Where(isCustomProperty) + .Select(x => 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)) + .Order(StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string primaryMessageCodePath) + return $"{pathSegment}{primaryMessageCodePath}"; + + } + else if (block is CodeClass currentClass) + { + if (currentClass.Properties.FirstOrDefault(isPrimaryErrorMessage) is CodeProperty primaryErrorMessageProperty) + return propertyNameNormalization(primaryErrorMessageProperty); + else if (currentClass.Methods.FirstOrDefault(x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + return methodNameNormalization(primaryErrorMessageMethod); + else if (currentClass.Methods + .Where(isGetterMethod) + .Select(x => 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) + .Union(currentClass.Properties + .Where(isCustomProperty) + .Select(x => 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)) + .Order(StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string primaryMessageCodePath) + return primaryMessageCodePath; + } + return string.Empty; + } +} diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiDeprecationExtensionExtensionsTests.cs index 3af6ca9cb6..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 Microsoft.OpenApi.MicrosoftExtensions; 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/Writers/ProprietableBlockExtensionsTests.cs b/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs new file mode 100644 index 0000000000..e414627240 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs @@ -0,0 +1,231 @@ +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 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); + } +} From 09931cda231ade3b8e62b0c37f1b1ae4417759d9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 7 Sep 2023 10:58:59 -0400 Subject: [PATCH 07/20] - adds primary error message mapping in CSharp Signed-off-by: Vincent Biret --- .../CSharp/CodeClassDeclarationWriter.cs | 12 +++--- .../CSharp/CodeClassDeclarationWriterTests.cs | 23 +++++++++++ .../ProprietableBlockExtensionsTests.cs | 39 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs index 97d98bb1c2..cd7a690436 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs @@ -14,6 +14,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 +37,12 @@ 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.StartBlock($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); + if (parentClass.IsErrorDefinition && parentClass.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) { - conventions.WriteLongDescription(parentClass.Documentation, writer); - conventions.WriteDeprecationAttribute(parentClass, writer); + writer.WriteLine($"public override string Message {{ get => {primaryMessageCodePath} ?? string.Empty; }}"); } - writer.WriteLine($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); - writer.IncreaseIndent(); } } diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs index eb79ea470d..c05df7c3a7 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs @@ -107,4 +107,27 @@ public void WritesImports() Assert.Contains("Project.Graph", result); Assert.Contains("System.Util", 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", + }, + }); + + // When + codeElementWriter.WriteCodeElement(parentClass.StartBlock, writer); + var result = tw.ToString(); + + // Then + Assert.Contains("public override string Message { get => Prop1 ?? string.Empty; }", result); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs b/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs index e414627240..32828000bd 100644 --- a/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/ProprietableBlockExtensionsTests.cs @@ -48,6 +48,45 @@ public void GetsTheCodePathForFirstLevelProperty() 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 From c8797e6d79132260d5b950c2cb1eb5a1015b8085 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 7 Sep 2023 13:47:00 -0400 Subject: [PATCH 08/20] - adds support for primary error message mapping in go Signed-off-by: Vincent Biret --- .../Writers/Go/CodeBlockEndWriter.cs | 7 +++ .../Writers/ProprietableBlockExtensions.cs | 34 +++++------ .../Writers/Go/CodeClassEndWriterTests.cs | 56 ++++++++++++++++++- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs index ad63d9ba6e..296f0455be 100644 --- a/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs @@ -1,5 +1,6 @@ using System; using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; namespace Kiota.Builder.Writers.Go; public class CodeBlockEndWriter : ICodeElementWriter @@ -10,5 +11,11 @@ public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer) ArgumentNullException.ThrowIfNull(codeElement); if (codeElement.Parent is CodeNamespace || codeElement.Parent is CodeEnum) return; writer.CloseBlock(); + if (codeElement.Parent is CodeInterface parentInterface && parentInterface.OriginalClass is not null && parentInterface.OriginalClass.IsErrorDefinition && parentInterface.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase() + "()") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) + { + writer.StartBlock($"func (e *{parentInterface.OriginalClass.Name.ToFirstCharacterUpperCase()}) Error() string {{"); + writer.WriteLine($"return *(e.{primaryMessageCodePath})"); + writer.CloseBlock(); + } } } diff --git a/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs index d7c103ae3a..bec2c410dc 100644 --- a/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs +++ b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs @@ -16,36 +16,38 @@ internal static string GetPrimaryMessageCodePath( { if (block is CodeInterface currentInterface) { - if (currentInterface.Properties.FirstOrDefault(isPrimaryErrorMessage) is CodeProperty primaryErrorMessageProperty) - return propertyNameNormalization(primaryErrorMessageProperty); - else if (currentInterface.Methods.FirstOrDefault(x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + if (currentInterface.Methods.FirstOrDefault(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 => 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) + .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 => 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)) - .Order(StringComparer.OrdinalIgnoreCase) - .FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string primaryMessageCodePath) - return $"{pathSegment}{primaryMessageCodePath}"; + .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.Properties.FirstOrDefault(isPrimaryErrorMessage) is CodeProperty primaryErrorMessageProperty) - return propertyNameNormalization(primaryErrorMessageProperty); - else if (currentClass.Methods.FirstOrDefault(x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + if (currentClass.Methods.FirstOrDefault(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 => 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) + .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 => 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)) - .Order(StringComparer.OrdinalIgnoreCase) - .FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string primaryMessageCodePath) - return primaryMessageCodePath; + .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/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs index 5e79e91707..b774a8d570 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,62 @@ 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 == '}')); + } + [Fact] + public void WritesMessageOverrideOnPrimary() + { + // Given + parentClass.IsErrorDefinition = true; + 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 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, + }); + + // When + codeElementWriter.WriteCodeElement(parentInterface.EndBlock, writer); + var result = tw.ToString(); + + // Then + Assert.Contains("Error() string {", result); + Assert.Contains("return *(e.GetProp1()", result); } } From 5f0143fec01984e074b3951a509d1f75cf55c2bb Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 7 Sep 2023 15:23:32 -0400 Subject: [PATCH 09/20] - code linting Signed-off-by: Vincent Biret --- src/Kiota.Builder/KiotaBuilder.cs | 2 +- src/Kiota.Builder/Writers/Php/PhpConventionService.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index dc62b0a306..a8fc56d995 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -507,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); 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()}"))}"; } From 9eca889bda8c3c95a0f561a86bb16b337afc4bd5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 7 Sep 2023 15:51:16 -0400 Subject: [PATCH 10/20] - bumps Openapi.net version --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d860197f5b..d3c2d9d948 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,7 +52,7 @@ "args": [ "generate", "--openapi", - "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", + "C:/sources/github/msgraph-sdk-powershell/openApiDocs/v1.0/Mail.yml", "--language", "csharp", "-o", @@ -115,7 +115,7 @@ "args": [ "generate", "--openapi", - "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", + "C:/sources/github/msgraph-sdk-powershell/openApiDocs/v1.0/Mail.yml", "--language", "go", "-o", From 78c590d0ec0a22f383e2d8c741feef78ed25d94d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 09:02:58 -0400 Subject: [PATCH 11/20] - updates tasks configuration so it also builds test projects Signed-off-by: Vincent Biret --- .vscode/tasks.json | 53 +++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) 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" ], From df7960f4a9c77469aea565bfe97d29f865570ec9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 09:13:16 -0400 Subject: [PATCH 12/20] - switches error override implementations to property/method writers in csharp and go --- src/Kiota.Builder/CodeDOM/CodeClass.cs | 7 ++ src/Kiota.Builder/CodeDOM/CodeMethod.cs | 4 ++ src/Kiota.Builder/CodeDOM/CodeProperty.cs | 4 ++ src/Kiota.Builder/Refiners/CSharpRefiner.cs | 5 ++ .../Refiners/CommonLanguageRefiner.cs | 41 +++++++++++- src/Kiota.Builder/Refiners/GoRefiner.cs | 4 ++ .../CSharp/CodeClassDeclarationWriter.cs | 6 -- .../Writers/CSharp/CodeMethodWriter.cs | 4 +- .../Writers/CSharp/CodePropertyWriter.cs | 5 ++ .../Writers/Go/CodeBlockEndWriter.cs | 7 -- .../Writers/Go/CodeMethodWriter.cs | 17 ++++- .../Writers/Go/CodePropertyWriter.cs | 2 + .../CSharp/CodeClassDeclarationWriterTests.cs | 23 ------- .../Writers/CSharp/CodePropertyWriterTests.cs | 32 +++++++++ .../Writers/Go/CodeClassEndWriterTests.cs | 49 -------------- .../Writers/Go/CodeMethodWriterTests.cs | 67 +++++++++++++++++++ 16 files changed, 188 insertions(+), 89 deletions(-) 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 e2b3732626..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 diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index 0ac175dad2..7e337d5a61 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 }, + true + ); MoveRequestBuilderPropertiesToBaseType(generatedCode, new CodeUsing { diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index ebccb2bad5..51bdd6c6c5 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 currentClass && currentClass.IsErrorDefinition) + { + 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..26e58c7c6a 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 } + ); GenerateCodeFiles(generatedCode); }, cancellationToken); } diff --git a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs index cd7a690436..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; @@ -40,9 +38,5 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit conventions.WriteLongDescription(parentClass.Documentation, writer); conventions.WriteDeprecationAttribute(parentClass, writer); writer.StartBlock($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); - if (parentClass.IsErrorDefinition && parentClass.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) - { - writer.WriteLine($"public override string Message {{ get => {primaryMessageCodePath} ?? string.Empty; }}"); - } } } 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..9fb66c850c 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs @@ -57,6 +57,11 @@ private void WritePropertyInternal(CodeProperty codeElement, LanguageWriter writ writer.DecreaseIndent(); writer.WriteLine("}"); break; + case CodePropertyKind.ErrorMessageOverride when parentClass.IsErrorDefinition && parentClass.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath): + writer.WriteLine($"public override string Message {{ get => {primaryMessageCodePath} ?? string.Empty; }}"); + break; + case CodePropertyKind.ErrorMessageOverride: + throw new InvalidOperationException($"The property {codeElement.Name} is of kind {nameof(CodePropertyKind.ErrorMessageOverride)} but its parent is not an error definition or the code path to the error message could not be found"); case CodePropertyKind.QueryParameter when codeElement.IsNameEscaped: writer.WriteLine($"[QueryParameter(\"{codeElement.SerializationName}\")]"); goto default; diff --git a/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs index 296f0455be..ad63d9ba6e 100644 --- a/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeBlockEndWriter.cs @@ -1,6 +1,5 @@ using System; using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; namespace Kiota.Builder.Writers.Go; public class CodeBlockEndWriter : ICodeElementWriter @@ -11,11 +10,5 @@ public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer) ArgumentNullException.ThrowIfNull(codeElement); if (codeElement.Parent is CodeNamespace || codeElement.Parent is CodeEnum) return; writer.CloseBlock(); - if (codeElement.Parent is CodeInterface parentInterface && parentInterface.OriginalClass is not null && parentInterface.OriginalClass.IsErrorDefinition && parentInterface.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase() + "()") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) - { - writer.StartBlock($"func (e *{parentInterface.OriginalClass.Name.ToFirstCharacterUpperCase()}) Error() string {{"); - writer.WriteLine($"return *(e.{primaryMessageCodePath})"); - writer.CloseBlock(); - } } } 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/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs index c05df7c3a7..eb79ea470d 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs @@ -107,27 +107,4 @@ public void WritesImports() Assert.Contains("Project.Graph", result); Assert.Contains("System.Util", 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", - }, - }); - - // When - codeElementWriter.WriteCodeElement(parentClass.StartBlock, writer); - var result = tw.ToString(); - - // Then - Assert.Contains("public override string Message { get => Prop1 ?? string.Empty; }", result); - } } 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 b774a8d570..0e2bb8271a 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeClassEndWriterTests.cs @@ -54,53 +54,4 @@ public void ClosesNonNestedClasses() var result = tw.ToString(); Assert.Equal(1, result.Count(static x => x == '}')); } - [Fact] - public void WritesMessageOverrideOnPrimary() - { - // Given - parentClass.IsErrorDefinition = true; - 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 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, - }); - - // When - codeElementWriter.WriteCodeElement(parentInterface.EndBlock, writer); - var result = tw.ToString(); - - // Then - Assert.Contains("Error() string {", result); - Assert.Contains("return *(e.GetProp1()", result); - } } 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); + } } From 9790377990d15505b58f6fed4ad4025be1ee08be Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 09:55:41 -0400 Subject: [PATCH 13/20] - implements primary error message mapping for java Signed-off-by: Vincent Biret --- src/Kiota.Builder/Refiners/JavaRefiner.cs | 4 ++ .../Writers/Java/CodeMethodWriter.cs | 18 +++++++ .../Writers/Java/CodePropertyWriter.cs | 2 + .../Writers/Java/CodeMethodWriterTests.cs | 52 +++++++++++++++++++ 4 files changed, 76 insertions(+) diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index e29910b493..62aece42c9 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 } + ); }, cancellationToken); } private const int MaxDiscriminatorLength = 500; diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index ac60397f51..4b3ea44642 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -83,6 +83,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: @@ -91,6 +94,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"); @@ -682,6 +696,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 68dfa0c763..a3753220e7 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/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index 7f56c8f4ca..12ceb51f34 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -2013,4 +2013,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); + } } From 03dadab9026a0c44cd1cebef31345b83761fcd78 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 13:36:04 -0400 Subject: [PATCH 14/20] - adds support for primary error message mapping in TS Signed-off-by: Vincent Biret --- .../Refiners/TypeScriptRefiner.cs | 1 + .../Writers/CSharp/CodePropertyWriter.cs | 2 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 13 ++++- .../Writers/TypeScript/CodeMethodWriter.cs | 2 + .../Writers/TypeScript/CodePropertyWriter.cs | 11 +++-- .../TypeScript/CodeFunctionWriterTests.cs | 48 +++++++++++++++++++ 6 files changed, 70 insertions(+), 7 deletions(-) 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/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs index 9fb66c850c..238e0f10ea 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs @@ -58,7 +58,7 @@ private void WritePropertyInternal(CodeProperty codeElement, LanguageWriter writ writer.WriteLine("}"); break; case CodePropertyKind.ErrorMessageOverride when parentClass.IsErrorDefinition && parentClass.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterUpperCase(), static x => x.Name.ToFirstCharacterUpperCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath): - writer.WriteLine($"public override string Message {{ get => {primaryMessageCodePath} ?? string.Empty; }}"); + writer.WriteLine($"public override {propertyType} {codeElement.Name.ToFirstCharacterUpperCase()} {{ get => {primaryMessageCodePath} ?? string.Empty; }}"); break; case CodePropertyKind.ErrorMessageOverride: throw new InvalidOperationException($"The property {codeElement.Name} is of kind {nameof(CodePropertyKind.ErrorMessageOverride)} but its parent is not an error definition or the code path to the error message could not be found"); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 9a94b97cf1..16c3d001f8 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).First(); + } + 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/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); + } } From 3aaee6f5e4e847bc320ada891117708b061e2d95 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 13:39:09 -0400 Subject: [PATCH 15/20] - adds changelog entry for primary error mapping Signed-off-by: Vincent Biret --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c36774f222..0f3a9daccf 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 From 7281b0bc0ae30384e3ebdfdb1360cc21cab69261 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 13:40:45 -0400 Subject: [PATCH 16/20] - reverts changes to launch settings --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d3c2d9d948..d860197f5b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,7 +52,7 @@ "args": [ "generate", "--openapi", - "C:/sources/github/msgraph-sdk-powershell/openApiDocs/v1.0/Mail.yml", + "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", "--language", "csharp", "-o", @@ -115,7 +115,7 @@ "args": [ "generate", "--openapi", - "C:/sources/github/msgraph-sdk-powershell/openApiDocs/v1.0/Mail.yml", + "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", "--language", "go", "-o", From 4692dd2ae9ea363343746915f390d1da3d9e47f5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 13:44:00 -0400 Subject: [PATCH 17/20] - code linting Signed-off-by: Vincent Biret --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 16c3d001f8..5a7c0a2eaa 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -195,7 +195,7 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter 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).First(); + primaryErrorMappingKey = primaryMessageCodePath.Split("?.", StringSplitOptions.RemoveEmptyEntries)[0]; } foreach (var otherProp in properties) From f72bc541d0497f19258bbecd6977c8bb79e5ec8a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 13:59:35 -0400 Subject: [PATCH 18/20] - avoids throwing when primary message is not found in CSHarp Signed-off-by: Vincent Biret --- src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs index 238e0f10ea..df0d692458 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodePropertyWriter.cs @@ -57,11 +57,12 @@ private void WritePropertyInternal(CodeProperty codeElement, LanguageWriter writ writer.DecreaseIndent(); writer.WriteLine("}"); break; - case CodePropertyKind.ErrorMessageOverride when parentClass.IsErrorDefinition && 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; }}"); + 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.ErrorMessageOverride: - throw new InvalidOperationException($"The property {codeElement.Name} is of kind {nameof(CodePropertyKind.ErrorMessageOverride)} but its parent is not an error definition or the code path to the error message could not be found"); case CodePropertyKind.QueryParameter when codeElement.IsNameEscaped: writer.WriteLine($"[QueryParameter(\"{codeElement.SerializationName}\")]"); goto default; From 907b8b975274546f7b0f3b7b8357a5b94a0108b1 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 8 Sep 2023 14:23:09 -0400 Subject: [PATCH 19/20] - fixes a bug where override primary error message type would be considered reserved --- src/Kiota.Builder/Refiners/CSharpRefiner.cs | 2 +- src/Kiota.Builder/Refiners/GoRefiner.cs | 2 +- src/Kiota.Builder/Refiners/JavaRefiner.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs index 7e337d5a61..5689d654c1 100644 --- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs +++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs @@ -18,7 +18,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); AddPrimaryErrorMessage(generatedCode, "Message", - () => new CodeType { Name = "string", IsNullable = false }, + () => new CodeType { Name = "string", IsNullable = false, IsExternal = true }, true ); MoveRequestBuilderPropertiesToBaseType(generatedCode, diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs index 26e58c7c6a..80ed71ac75 100644 --- a/src/Kiota.Builder/Refiners/GoRefiner.cs +++ b/src/Kiota.Builder/Refiners/GoRefiner.cs @@ -176,7 +176,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance static s => s.ToFirstCharacterUpperCase()); AddPrimaryErrorMessage(generatedCode, "Error", - () => new CodeType { Name = "string", IsNullable = false } + () => 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 62aece42c9..362d3e8599 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -137,7 +137,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance SplitLongDiscriminatorMethods(generatedCode); AddPrimaryErrorMessage(generatedCode, "getMessage", - () => new CodeType { Name = "string", IsNullable = false } + () => new CodeType { Name = "string", IsNullable = false, IsExternal = true } ); }, cancellationToken); } From a14835bdfab33ddf3c8a5100c8421d9f20fc0433 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 11 Sep 2023 08:21:57 -0400 Subject: [PATCH 20/20] Apply suggestions from code review Co-authored-by: Eastman --- src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs | 2 +- src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 51bdd6c6c5..f3ec22130b 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -1464,7 +1464,7 @@ protected static void RemoveRequestConfigurationClasses(CodeElement currentEleme } internal static void AddPrimaryErrorMessage(CodeElement currentElement, string name, Func type, bool asProperty = false) { - if (currentElement is CodeClass currentClass && currentClass.IsErrorDefinition) + if (currentElement is CodeClass { IsErrorDefinition: true } currentClass) { if (asProperty) { diff --git a/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs index bec2c410dc..6c71045564 100644 --- a/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs +++ b/src/Kiota.Builder/Writers/ProprietableBlockExtensions.cs @@ -16,7 +16,7 @@ internal static string GetPrimaryMessageCodePath( { if (block is CodeInterface currentInterface) { - if (currentInterface.Methods.FirstOrDefault(x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + 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); @@ -34,7 +34,7 @@ internal static string GetPrimaryMessageCodePath( } else if (block is CodeClass currentClass) { - if (currentClass.Methods.FirstOrDefault(x => isGetterMethod(x) && x.AccessedProperty is not null && isPrimaryErrorMessage(x.AccessedProperty)) is CodeMethod primaryErrorMessageMethod) + 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);