From b59864c2387c9410e71b0caa8d439e7f122ddc24 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 18 Feb 2025 14:31:21 -0500 Subject: [PATCH 1/2] fix: an issue where deprecation extension parsing would fail --- .../OpenApiDeprecationExtension.cs | 41 ++++++++++++++++--- .../OpenApiDeprecationExtensionTests.cs | 25 +++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs index a5bae9fa9..df1f6dcaa 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs @@ -9,6 +9,8 @@ using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; using System.Text.Json.Nodes; +using System.Text.Json; +using System.Globalization; namespace Microsoft.OpenApi.MicrosoftExtensions; @@ -71,6 +73,35 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) writer.WriteEndObject(); } } + private static readonly DateTimeStyles datesStyle = DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind; + private static DateTimeOffset? GetDateTimeOffsetValue(string propertyName, JsonObject rawObject) + { + if (!rawObject.TryGetPropertyValue(propertyName.ToFirstCharacterLowerCase(), out var jsonNode) || + jsonNode is not JsonValue jsonValue || + jsonNode.GetValueKind() is not JsonValueKind.String) + return null; + + if (jsonValue.TryGetValue(out var strValue) && + DateTimeOffset.TryParse(strValue, CultureInfo.InvariantCulture, datesStyle, out var parsedValue)) + { + return parsedValue; + } + if (jsonValue.TryGetValue(out var returnedDto)) + { + return returnedDto; + } + if (jsonValue.TryGetValue(out var returnedDt)) + { + return new DateTimeOffset(returnedDt, TimeSpan.FromHours(0)); + } + #if NET6_0_OR_GREATER + if (jsonValue.TryGetValue(out var returnedDo)) + { + return new(returnedDo.Year, returnedDo.Month, returnedDo.Day, 0, 0, 0, TimeSpan.FromHours(0)); + } + #endif + return null; + } /// /// Parses the to . /// @@ -80,11 +111,11 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) public static OpenApiDeprecationExtension Parse(JsonNode source) { if (source is not JsonObject rawObject) return null; - var extension = new OpenApiDeprecationExtension(); - if (rawObject.TryGetPropertyValue(nameof(RemovalDate).ToFirstCharacterLowerCase(), out var removalDate) && removalDate is JsonNode removalDateValue) - extension.RemovalDate = removalDateValue.GetValue(); - if (rawObject.TryGetPropertyValue(nameof(Date).ToFirstCharacterLowerCase(), out var date) && date is JsonNode dateValue) - extension.Date = dateValue.GetValue(); + var extension = new OpenApiDeprecationExtension + { + RemovalDate = GetDateTimeOffsetValue(nameof(RemovalDate), rawObject), + Date = GetDateTimeOffsetValue(nameof(Date), rawObject) + }; if (rawObject.TryGetPropertyValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is JsonNode versionValue) extension.Version = versionValue.GetValue(); if (rawObject.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is JsonNode descriptionValue) diff --git a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs index 6849e5e9c..f4364d032 100644 --- a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs +++ b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs @@ -75,10 +75,10 @@ public void Parses() { var oaiValue = new JsonObject { - { "date", new OpenApiAny(new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0))).Node}, - { "removalDate", new OpenApiAny(new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0))).Node}, - { "version", new OpenApiAny("v1.0").Node}, - { "description", new OpenApiAny("removing").Node} + { "date", new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0))}, + { "removalDate", new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0))}, + { "version", "v1.0"}, + { "description", "removing"} }; var value = OpenApiDeprecationExtension.Parse(oaiValue); Assert.NotNull(value); @@ -88,6 +88,23 @@ public void Parses() Assert.Equal(new DateTimeOffset(2023, 05, 04, 16, 0, 0, 0, 0, new(4, 0, 0)), value.RemovalDate); } [Fact] + public void ParsesStringValues() + { + var oaiValue = new JsonObject + { + { "date", "2023-05-04T16:00:00Z"}, + { "removalDate", "2023-05-04"}, + { "version", "v1.0"}, + { "description", "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(0, 0, 0)), value.Date); + Assert.Equal(new DateTimeOffset(2023, 05, 04, 0, 0, 0, 0, 0, new(0, 0, 0)), value.RemovalDate); + } + [Fact] public void Serializes() { var value = new OpenApiDeprecationExtension From d49c38dd8d9dbfe2d16023ed1a05632c801a48da Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 18 Feb 2025 14:52:07 -0500 Subject: [PATCH 2/2] chore: cleanup of GetValues where possible --- .../Extensions/OpenApiExtensibleExtensions.cs | 5 +++-- .../OpenApiDeprecationExtension.cs | 8 ++++---- .../OpenApiEnumFlagsExtension.cs | 4 ++-- .../OpenApiEnumValuesDescriptionExtension.cs | 18 +++++++++--------- .../OpenApiPagingExtension.cs | 12 ++++++------ .../OpenApiPrimaryErrorMessageExtension.cs | 4 ++-- .../OpenApiReservedParameterExtension.cs | 4 ++-- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs b/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs index ee57125dd..f4b4f77c5 100644 --- a/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs +++ b/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using System.Collections.Generic; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Hidi.Extensions { @@ -14,9 +15,9 @@ internal static class OpenApiExtensibleExtensions /// A value matching the provided extensionKey. Return null when extensionKey is not found. internal static string GetExtension(this IDictionary extensions, string extensionKey) { - if (extensions.TryGetValue(extensionKey, out var value) && value is OpenApiAny castValue) + if (extensions.TryGetValue(extensionKey, out var value) && value is OpenApiAny { Node: JsonValue castValue } && castValue.TryGetValue(out var stringValue)) { - return castValue.Node.GetValue(); + return stringValue; } return string.Empty; } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs index df1f6dcaa..6fa71600d 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs @@ -116,10 +116,10 @@ public static OpenApiDeprecationExtension Parse(JsonNode source) RemovalDate = GetDateTimeOffsetValue(nameof(RemovalDate), rawObject), Date = GetDateTimeOffsetValue(nameof(Date), rawObject) }; - if (rawObject.TryGetPropertyValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is JsonNode versionValue) - extension.Version = versionValue.GetValue(); - if (rawObject.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is JsonNode descriptionValue) - extension.Description = descriptionValue.GetValue(); + if (rawObject.TryGetPropertyValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is JsonValue versionValue && versionValue.TryGetValue(out var versionStr)) + extension.Version = versionStr; + if (rawObject.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is JsonValue descriptionValue && descriptionValue.TryGetValue(out var descriptionStr)) + extension.Description = descriptionStr; return extension; } } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs index 22b9f0df2..df0d236c6 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs @@ -47,9 +47,9 @@ public static OpenApiEnumFlagsExtension Parse(JsonNode source) { if (source is not JsonObject rawObject) throw new ArgumentOutOfRangeException(nameof(source)); var extension = new OpenApiEnumFlagsExtension(); - if (rawObject.TryGetPropertyValue(nameof(IsFlags).ToFirstCharacterLowerCase(), out var flagsValue) && flagsValue is JsonNode isFlags) + if (rawObject.TryGetPropertyValue(nameof(IsFlags).ToFirstCharacterLowerCase(), out var flagsValue) && flagsValue is JsonValue isFlags && isFlags.TryGetValue(out var isFlagsValue)) { - extension.IsFlags = isFlags.GetValue(); + extension.IsFlags = isFlagsValue; } return extension; } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs index df1e664e1..19b370518 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs @@ -96,15 +96,15 @@ public EnumDescription() public EnumDescription(JsonObject source) { if (source is null) throw new ArgumentNullException(nameof(source)); - if (source.TryGetPropertyValue(nameof(Value).ToFirstCharacterLowerCase(), out var rawValue) && rawValue is JsonNode value) - if (value.GetValueKind() == JsonValueKind.Number) - Value = value.GetValue().ToString(CultureInfo.InvariantCulture); - else - Value = value.GetValue(); - if (source.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var rawDescription) && rawDescription is JsonNode description) - Description = description.GetValue(); - if (source.TryGetPropertyValue(nameof(Name).ToFirstCharacterLowerCase(), out var rawName) && rawName is JsonNode name) - Name = name.GetValue(); + if (source.TryGetPropertyValue(nameof(Value).ToFirstCharacterLowerCase(), out var rawValue) && rawValue is JsonValue value) + if (value.GetValueKind() == JsonValueKind.Number && value.TryGetValue(out var decimalValue)) + Value = decimalValue.ToString(CultureInfo.InvariantCulture); + else if (value.TryGetValue(out var stringValue)) + Value = stringValue; + if (source.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var rawDescription) && rawDescription is JsonValue description && description.TryGetValue(out var stringValueDescription)) + Description = stringValueDescription; + if (source.TryGetPropertyValue(nameof(Name).ToFirstCharacterLowerCase(), out var rawName) && rawName is JsonValue name && name.TryGetValue(out var stringValueName)) + Name = stringValueName; } /// /// The description for the enum symbol diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs index 57d057e59..2e9a0c3f3 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs @@ -75,19 +75,19 @@ public static OpenApiPagingExtension Parse(JsonNode source) { if (source is not JsonObject rawObject) return null; var extension = new OpenApiPagingExtension(); - if (rawObject.TryGetPropertyValue(nameof(NextLinkName).ToFirstCharacterLowerCase(), out var nextLinkName) && nextLinkName is JsonNode nextLinkNameStr) + if (rawObject.TryGetPropertyValue(nameof(NextLinkName).ToFirstCharacterLowerCase(), out var nextLinkName) && nextLinkName is JsonValue nextLinkNameValue && nextLinkNameValue.TryGetValue(out var nextLinkNameStr)) { - extension.NextLinkName = nextLinkNameStr.GetValue(); + extension.NextLinkName = nextLinkNameStr; } - if (rawObject.TryGetPropertyValue(nameof(OperationName).ToFirstCharacterLowerCase(), out var opName) && opName is JsonNode opNameStr) + if (rawObject.TryGetPropertyValue(nameof(OperationName).ToFirstCharacterLowerCase(), out var opName) && opName is JsonValue opNameValue && opNameValue.TryGetValue(out var opNameStr)) { - extension.OperationName = opNameStr.GetValue(); + extension.OperationName = opNameStr; } - if (rawObject.TryGetPropertyValue(nameof(ItemName).ToFirstCharacterLowerCase(), out var itemName) && itemName is JsonNode itemNameStr) + if (rawObject.TryGetPropertyValue(nameof(ItemName).ToFirstCharacterLowerCase(), out var itemName) && itemName is JsonValue itemNameValue && itemNameValue.TryGetValue(out var itemNameStr)) { - extension.ItemName = itemNameStr.GetValue(); + extension.ItemName = itemNameStr; } return extension; diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs index ad47db39b..a9e2f055a 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs @@ -40,10 +40,10 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) /// The . public static OpenApiPrimaryErrorMessageExtension Parse(JsonNode source) { - if (source is not JsonNode rawObject) return null; + if (source is not JsonValue rawObject) return null; return new() { - IsPrimaryErrorMessage = rawObject.GetValue() + IsPrimaryErrorMessage = rawObject.TryGetValue(out var value) && value }; } } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs index 2d3a8c117..612e4cb74 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs @@ -42,10 +42,10 @@ public bool? IsReserved /// public static OpenApiReservedParameterExtension Parse(JsonNode source) { - if (source is not JsonNode rawBoolean) return null; + if (source is not JsonValue rawBoolean) return null; return new() { - IsReserved = rawBoolean.GetValue() + IsReserved = rawBoolean.TryGetValue(out var value) && value }; } }