Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Changes in 7.1.9
- Fixed: numeric bounds from `short`/`byte`/`ushort`/`uint`/`ulong`/`sbyte`-typed rules were silently dropped (Issue #222)
- `IsNumeric` in the shared core (`MicroElements.OpenApi.FluentValidation`) only recognized `int`/`long`/`float`/`double`/`decimal`/`BigInteger`, so a `Between`/`Comparison` rule whose bound was a small integer type (e.g. `InclusiveBetween((short)1, (short)99)`) matched but produced no `minimum`/`maximum` — even though the generator emits `"type": "integer"` for the property. This could not be worked around at the validator definition site because those rule overloads only accept bounds of the property's own type
- Fix: `IsNumeric` now recognizes all integer primitives (`sbyte`/`byte`/`short`/`ushort`/`int`/`uint`/`long`/`ulong`) in addition to the floating/decimal/`BigInteger` types; `NumericToDecimal` already converted them all. One change in the shared core fixes all three providers (Swashbuckle, NSwag, and the native `Microsoft.AspNetCore.OpenApi` transformer)

# Changes in 7.1.8
- Security (Issue #220): closed a transitive high-severity advisory in the published package. `Swashbuckle.AspNetCore.SwaggerGen` on the net10.0 target was bumped `10.0.0` → `10.2.1`, which resolves `Microsoft.OpenApi` to the patched `2.7.5` (was `2.3.0`). This clears **GHSA-v5pm-xwqc-g5wc / CVE-2026-49451** (CWE-674 uncontrolled recursion — a circular `$ref` schema could stack-overflow the OpenAPI reader). The net8.0/net9.0 targets use Swashbuckle 8.1.1 → Microsoft.OpenApi v1 and were never in the advisory range
- Media type & file size validation for `IFormFile` uploads (Issue #216): stable rollup of everything in `7.1.8-beta.1` and `7.1.8-beta.2` below (new File-level rules `.FileContentType()`, `.MaxFileSize()`, `.MinFileSize()`, `.FileSizeBetween()`; Swashbuckle / NSwag / Microsoft.AspNetCore.OpenApi emit multipart `encoding.contentType` and description annotations)
Expand Down
10 changes: 9 additions & 1 deletion src/MicroElements.OpenApi.FluentValidation/Core/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@
/// <summary>
/// Is supported swagger numeric type.
/// </summary>
internal static bool IsNumeric(this object value) => value is int || value is long || value is float || value is double || value is decimal || value is BigInteger;
/// <remarks>
/// Issue #222: all integer primitives must be recognized. `short`/`byte`/`ushort`/`uint`/`ulong`/`sbyte`
/// bounds (e.g. from <c>InclusiveBetween((short)1, (short)99)</c>) were dropped because they were absent
/// here, so <c>minimum</c>/<c>maximum</c> were never emitted. <see cref="NumericToDecimal"/> already
/// converts them all via <see cref="Convert.ToDecimal(object)"/>.
/// </remarks>
internal static bool IsNumeric(this object value) =>
value is sbyte or byte or short or ushort or int or uint or long or ulong
or float or double or decimal or BigInteger;

/// <summary>
/// Convert numeric to decimal.
Expand All @@ -42,7 +50,7 @@
#if DEBUG
return collection?.ToArray() ?? Array.Empty<TValue>();
#else
return collection;

Check warning on line 53 in src/MicroElements.OpenApi.FluentValidation/Core/Extensions.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Possible null reference return.

Check warning on line 53 in src/MicroElements.OpenApi.FluentValidation/Core/Extensions.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Possible null reference return.
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) MicroElements. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using FluentAssertions;
using FluentValidation;
using Xunit;

namespace MicroElements.Swashbuckle.FluentValidation.Tests
{
/// <summary>
/// Issue #222: numeric bounds whose value is a small integer type (sbyte/byte/short/ushort/uint/ulong)
/// were silently dropped — the Between/Comparison rules matched, but IsNumeric() did not recognize the
/// boxed value, so minimum/maximum were never emitted. For a `short` property FluentValidation's
/// InclusiveBetween/GreaterThanOrEqualTo overloads only accept short bounds, so this could not be worked
/// around at the validator definition site.
/// https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/issues/222
/// </summary>
public class Issue222SmallIntegerBoundsTest : UnitTestBase
{
public class Model
{
public sbyte SByteValue { get; set; }
public byte ByteValue { get; set; }
public short ShortValue { get; set; }
public ushort UShortValue { get; set; }
public uint UIntValue { get; set; }
public ulong ULongValue { get; set; }
}

// A fresh SchemaBuilder per case: its SchemaRepository caches the generated component,
// so one builder must generate exactly one rule/schema.

[Fact]
public void InclusiveBetween_SByte_Should_Emit_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.SByteValue, rule => rule.InclusiveBetween((sbyte)1, (sbyte)9));
schema.GetMinimum().Should().Be(1);
schema.GetMaximum().Should().Be(9);
}

[Fact]
public void InclusiveBetween_Byte_Should_Emit_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.ByteValue, rule => rule.InclusiveBetween((byte)2, (byte)200));
schema.GetMinimum().Should().Be(2);
schema.GetMaximum().Should().Be(200);
}

[Fact]
public void InclusiveBetween_Short_Should_Emit_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.ShortValue, rule => rule.InclusiveBetween((short)1, (short)99));
schema.GetMinimum().Should().Be(1);
schema.GetMaximum().Should().Be(99);
}

[Fact]
public void InclusiveBetween_UShort_Should_Emit_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.UShortValue, rule => rule.InclusiveBetween((ushort)3, (ushort)300));
schema.GetMinimum().Should().Be(3);
schema.GetMaximum().Should().Be(300);
}

[Fact]
public void InclusiveBetween_UInt_Should_Emit_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.UIntValue, rule => rule.InclusiveBetween(4u, 400u));
schema.GetMinimum().Should().Be(4);
schema.GetMaximum().Should().Be(400);
}

[Fact]
public void InclusiveBetween_ULong_Should_Emit_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.ULongValue, rule => rule.InclusiveBetween(5ul, 5000ul));
schema.GetMinimum().Should().Be(5);
schema.GetMaximum().Should().Be(5000);
}

[Fact]
public void ExclusiveBetween_Short_Should_Emit_Exclusive_Bounds()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.ShortValue, rule => rule.ExclusiveBetween((short)1, (short)99));
schema.GetMinimum().Should().Be(1);
schema.GetMaximum().Should().Be(99);
schema.GetExclusiveMinimum().Should().BeTrue();
schema.GetExclusiveMaximum().Should().BeTrue();
}

[Fact]
public void GreaterThanOrEqualTo_Short_Should_Emit_Minimum()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.ShortValue, rule => rule.GreaterThanOrEqualTo((short)1));
schema.GetMinimum().Should().Be(1);
}

[Fact]
public void LessThanOrEqualTo_Byte_Should_Emit_Maximum()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.ByteValue, rule => rule.LessThanOrEqualTo((byte)200));
schema.GetMaximum().Should().Be(200);
}

[Fact]
public void GreaterThan_UInt_Should_Emit_Exclusive_Minimum()
{
var schema = new SchemaBuilder<Model>().AddRule(x => x.UIntValue, rule => rule.GreaterThan(4u));
schema.GetMinimum().Should().Be(4);
schema.GetExclusiveMinimum().Should().BeTrue();
}
}
}
2 changes: 1 addition & 1 deletion version.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>7.1.8</VersionPrefix>
<VersionPrefix>7.1.9</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Loading