Skip to content
Open
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
16 changes: 16 additions & 0 deletions src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ internal static void ApplyDefaultValue(this JsonNode schema, object? defaultValu
}
else
{
// The default value type may differ from the target type when attributes like
// DefaultParameterValue carry a literal (e.g. int32 literal for a uint64 parameter).
// Convert to the target type so SerializeToNode can unbox it correctly.
var targetType = Nullable.GetUnderlyingType(jsonTypeInfo.Type) ?? jsonTypeInfo.Type;
if (defaultValue.GetType() != targetType)
{
try
{
defaultValue = Convert.ChangeType(defaultValue, targetType, CultureInfo.InvariantCulture);
}
catch
{
// Value is not convertible to the target type; skip applying the default.
return;
}
}
schema[schemaAttribute] = JsonSerializer.SerializeToNode(defaultValue, jsonTypeInfo);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Microsoft.AspNetCore.OpenApi.Tests;
Expand Down Expand Up @@ -147,4 +148,41 @@ public static void ApplyValidationAttributes_Handles_Invalid_RangeAttribute_Valu
Assert.False(schema.TryGetPropertyValue("exclusiveMinimum", out _));
Assert.False(schema.TryGetPropertyValue("exclusiveMaximum", out _));
}

[Theory]
// int32 literal for uint64 — what F# emits for DefaultParameterValue(10) on a uint64 parameter
[InlineData(typeof(ulong), 10, (ulong)10)]
// uint32 literal for uint64 — F# DefaultParameterValue(10ul) on uint64
[InlineData(typeof(ulong), (uint)10, (ulong)10)]
// int32 literal for int64 — common numeric promotion case
[InlineData(typeof(long), 10, (long)10)]
// int32 literal for int16
[InlineData(typeof(short), 10, (short)10)]
public static void ApplyDefaultValue_ConvertsLiteralTypeMismatch(Type targetType, object defaultValue, object expected)
{
// Arrange — simulates what happens when DefaultParameterValue carries a literal whose
// CLR type doesn't exactly match the parameter type (e.g. F# int32 literal on uint64).
var schema = new JsonObject();
var jsonTypeInfo = JsonSerializerOptions.Default.GetTypeInfo(targetType);

// Act — must not throw InvalidCastException
schema.ApplyDefaultValue(defaultValue, jsonTypeInfo);

// Assert
Assert.Equal(expected, JsonSerializer.Deserialize(schema["default"]!, targetType));
}

[Fact]
public static void ApplyDefaultValue_SkipsNonConvertibleTypeMismatch()
{
// Arrange — a string default on a ulong parameter cannot be converted; should not throw.
var schema = new JsonObject();
var jsonTypeInfo = JsonSerializerOptions.Default.GetTypeInfo<ulong>();

// Act
schema.ApplyDefaultValue("not-a-number", jsonTypeInfo);

// Assert — default should be absent rather than throwing
Assert.False(schema.TryGetPropertyValue("default", out _));
}
}
Loading