Skip to content

fix: an issue where deprecation extension parsing would fail #2157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -14,9 +15,9 @@ internal static class OpenApiExtensibleExtensions
/// <returns>A <see cref="string"/> value matching the provided extensionKey. Return null when extensionKey is not found. </returns>
internal static string GetExtension(this IDictionary<string, IOpenApiExtension> 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<string>(out var stringValue))
{
return castValue.Node.GetValue<string>();
return stringValue;
}
return string.Empty;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<string>(out var strValue) &&
DateTimeOffset.TryParse(strValue, CultureInfo.InvariantCulture, datesStyle, out var parsedValue))
{
return parsedValue;
}
if (jsonValue.TryGetValue<DateTimeOffset>(out var returnedDto))
{
return returnedDto;
}
if (jsonValue.TryGetValue<DateTime>(out var returnedDt))
{
return new DateTimeOffset(returnedDt, TimeSpan.FromHours(0));
}
#if NET6_0_OR_GREATER
if (jsonValue.TryGetValue<DateOnly>(out var returnedDo))
{
return new(returnedDo.Year, returnedDo.Month, returnedDo.Day, 0, 0, 0, TimeSpan.FromHours(0));
}
#endif
return null;
}
/// <summary>
/// Parses the <see cref="OpenApiAny"/> to <see cref="OpenApiDeprecationExtension"/>.
/// </summary>
Expand All @@ -80,15 +111,15 @@ 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<DateTimeOffset>();
if (rawObject.TryGetPropertyValue(nameof(Date).ToFirstCharacterLowerCase(), out var date) && date is JsonNode dateValue)
extension.Date = dateValue.GetValue<DateTimeOffset>();
if (rawObject.TryGetPropertyValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is JsonNode versionValue)
extension.Version = versionValue.GetValue<string>();
if (rawObject.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is JsonNode descriptionValue)
extension.Description = descriptionValue.GetValue<string>();
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 JsonValue versionValue && versionValue.TryGetValue<string>(out var versionStr))
extension.Version = versionStr;
if (rawObject.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is JsonValue descriptionValue && descriptionValue.TryGetValue<string>(out var descriptionStr))
extension.Description = descriptionStr;
return extension;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>(out var isFlagsValue))
{
extension.IsFlags = isFlags.GetValue<bool>();
extension.IsFlags = isFlagsValue;
}
return extension;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<decimal>().ToString(CultureInfo.InvariantCulture);
else
Value = value.GetValue<string>();
if (source.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var rawDescription) && rawDescription is JsonNode description)
Description = description.GetValue<string>();
if (source.TryGetPropertyValue(nameof(Name).ToFirstCharacterLowerCase(), out var rawName) && rawName is JsonNode name)
Name = name.GetValue<string>();
if (source.TryGetPropertyValue(nameof(Value).ToFirstCharacterLowerCase(), out var rawValue) && rawValue is JsonValue value)
if (value.GetValueKind() == JsonValueKind.Number && value.TryGetValue<decimal>(out var decimalValue))
Value = decimalValue.ToString(CultureInfo.InvariantCulture);
else if (value.TryGetValue<string>(out var stringValue))
Value = stringValue;
if (source.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var rawDescription) && rawDescription is JsonValue description && description.TryGetValue<string>(out var stringValueDescription))
Description = stringValueDescription;
if (source.TryGetPropertyValue(nameof(Name).ToFirstCharacterLowerCase(), out var rawName) && rawName is JsonValue name && name.TryGetValue<string>(out var stringValueName))
Name = stringValueName;
}
/// <summary>
/// The description for the enum symbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(out var nextLinkNameStr))
{
extension.NextLinkName = nextLinkNameStr.GetValue<string>();
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<string>(out var opNameStr))
{
extension.OperationName = opNameStr.GetValue<string>();
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<string>(out var itemNameStr))
{
extension.ItemName = itemNameStr.GetValue<string>();
extension.ItemName = itemNameStr;
}

return extension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
/// <returns>The <see cref="OpenApiPrimaryErrorMessageExtension"/>.</returns>
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<bool>()
IsPrimaryErrorMessage = rawObject.TryGetValue<bool>(out var value) && value
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public bool? IsReserved
/// <returns></returns>
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<bool>()
IsReserved = rawBoolean.TryGetValue<bool>(out var value) && value
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down