Skip to content

fix(#222): emit numeric bounds for short/byte/ushort/uint/ulong-typed rules#225

Merged
avgalex merged 1 commit into
masterfrom
feature/issue-222-numeric-bounds
Jul 9, 2026
Merged

fix(#222): emit numeric bounds for short/byte/ushort/uint/ulong-typed rules#225
avgalex merged 1 commit into
masterfrom
feature/issue-222-numeric-bounds

Conversation

@avgalex

@avgalex avgalex commented Jul 9, 2026

Copy link
Copy Markdown
Member

Fixes #222.

Problem

Validation bounds whose value is a small integer type (short/byte/ushort/uint/ulong/sbyte) were silently dropped: Between/Comparison rules matched, but the apply action skipped the bound, so minimum/maximum never reached the schema — even though the generator emits "type": "integer" for the property.

public class Sample { public short Quantity { get; set; } }
public class SampleValidator : AbstractValidator<Sample>
{
    public SampleValidator() => RuleFor(x => x.Quantity).InclusiveBetween((short)1, (short)99);
}

Before: "quantity": { "type": "integer", "format": "int16" }
After: "quantity": { "type": "integer", "format": "int16", "minimum": 1, "maximum": 99 }

This could not be worked around at the validator definition site — for a short property, InclusiveBetween/GreaterThanOrEqualTo only accept short bounds, so the boxed value is always a short.

Root cause

IsNumeric in the shared core (MicroElements.OpenApi.FluentValidation/Core/Extensions.cs) only recognized int/long/float/double/decimal/BigInteger. short/byte/ushort/uint/ulong/sbyte fall through to false, so the Between/Comparison apply actions skipped the bound. NumericToDecimal already converts all of them via Convert.ToDecimal.

Fix

IsNumeric now recognizes all integer primitives:

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;

Because the check lives in the shared core, this single change fixes all three providers at once — Swashbuckle, NSwag, and the native Microsoft.AspNetCore.OpenApi transformer (IsNumeric() gates the numeric bounds in each provider's Comparison/Between rules). No overflow risk: decimal covers ulong.MaxValue, and BigInteger keeps its dedicated branch. Thanks to @send0xx for the precise report and the proposed fix.

Tests

Adds Issue222SmallIntegerBoundsTest covering InclusiveBetween/ExclusiveBetween and the comparison rules (GreaterThanOrEqualTo/LessThanOrEqualTo/GreaterThan) across sbyte/byte/short/ushort/uint/ulong, asserting minimum/maximum end-to-end. Verified red without the fix (10/10 fail), green with it.

Full suite: net8.0 128/128, net9.0 128/128, net10.0 96/96.

Release

Bumps version.props 7.1.8 → 7.1.9 (patch) and adds a CHANGELOG entry. 7.1.9 is the canonical SemVer patch over the published 7.1.8 — preferred over a 4-part 7.1.8.1 for NuGet (all tags in this repo are 3-part, and SemVer 2.0 tooling normalizes trailing revisions).

🤖 Generated with Claude Code

…ds are emitted

Validation bounds whose value was a small integer type (short/byte/ushort/uint/
ulong/sbyte) were silently dropped: the Between/Comparison rules matched, but the
apply action skipped the bound because IsNumeric() did not recognize the boxed
value, so minimum/maximum never reached the schema — even though the generator
emits "type": "integer" for the property. This could not be worked around at the
validator definition site, since e.g. InclusiveBetween on a short property only
accepts short bounds.

Fix: IsNumeric (shared core, MicroElements.OpenApi.FluentValidation) now recognizes
all integer primitives (sbyte/byte/short/ushort/int/uint/long/ulong) alongside the
floating/decimal/BigInteger types. NumericToDecimal already converted them all, so
this single change fixes all three providers at once (Swashbuckle, NSwag, and the
native Microsoft.AspNetCore.OpenApi transformer).

Adds regression tests for InclusiveBetween/ExclusiveBetween and the comparison rules
across the small integer types (verified red without the fix, green with it).

Bumps version 7.1.8 -> 7.1.9 and updates CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@avgalex

avgalex commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@claude review this pr

@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @avgalex's task in 59s —— View job


Claude's Review

Tasks

  • Gather context (PR description, changed files)
  • Read changed files in full
  • Review core fix logic (IsNumeric)
  • Review test coverage
  • Review CHANGELOG/version bump
  • Post final review

Solid, well-scoped fix. Summary of what I checked:

Core fix — src/MicroElements.OpenApi.FluentValidation/Core/Extensions.cs:25-27

  • IsNumeric now recognizes sbyte/byte/short/ushort/int/uint/long/ulong in addition to the float/double/decimal/BigInteger it already had. This matches NumericToDecimal (line 32), which was already converting all these types via Convert.ToDecimal — so IsNumeric was the actual gate that was too narrow, exactly as diagnosed.
  • Confirmed via grep that IsNumeric()/NumericToDecimal() are consumed identically by all three providers (DefaultFluentValidationRuleProvider.cs for Swashbuckle and for Microsoft.AspNetCore.OpenApi, plus NSwagFluentValidationRuleProvider.cs), so this one shared-core change does fix all three as claimed, with no per-provider duplication needed.
  • No overflow risk: decimal (±7.9×10^28) comfortably covers ulong.MaxValue (~1.8×10^19), and BigInteger keeps its dedicated branch in NumericToDecimal.

Tests — Issue222SmallIntegerBoundsTest.cs

  • Good coverage: InclusiveBetween/ExclusiveBetween across all 6 new types, plus GreaterThanOrEqualTo/LessThanOrEqualTo/GreaterThan comparison rules, using the existing SchemaBuilder/GetMinimum/GetMaximum/GetExclusiveMinimum/GetExclusiveMaximum test helpers already used elsewhere in the suite — consistent with existing patterns, not new infrastructure.
  • The "fresh SchemaBuilder per case" comment is a nice touch explaining a non-obvious constraint (repository caching) rather than restating what the code does.

CHANGELOG / version bump

  • 7.1.87.1.9 is a correct patch bump for a backward-compatible bug fix, and the changelog entry accurately describes root cause + fix.

Minor, non-blocking observations

  • The XML doc <remarks> on IsNumeric (Extensions.cs:18-24) is a bit long for an internal helper, but it captures real non-obvious context (why short/byte/etc. were missing) so it earns its place.
  • Nothing else jumps out — no missed call sites, no behavior change for the already-supported numeric types, and the fix is minimal and additive.

Note: I could not run dotnet build/dotnet test in this sandboxed environment (network/tool permissions blocked dotnet and git fetch), so I verified via static review only. The PR description reports the full suite green (net8.0 128/128, net9.0 128/128, net10.0 96/96) with the new tests failing red without the fix — that's consistent with what the code changes should produce.

Looks good to merge from my read.

avgalex pushed a commit that referenced this pull request Jul 9, 2026
Distinct patch from PR #225 (Issue #222 -> 7.1.9) so both fixes can ship as
independent NuGet packages regardless of merge order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@avgalex avgalex merged commit 59dd105 into master Jul 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Numeric bounds from short/byte-typed rules are silently dropped (IsNumeric doesn't recognize Int16)

1 participant