Skip to content

Conversation

@samtrion
Copy link
Contributor

@samtrion samtrion commented Jan 26, 2026

Summary by CodeRabbit

  • New Features

    • Expanded validation helpers: added many collection overloads, string length/whitespace checks, count/duplicate/default/empty-GUID validations, generic range checks, and time-based past/future date validators (guarded by runtime support); parameter names inferred where available.
  • Documentation

    • Major README overhaul describing polyfill behavior, cross-framework usage, installation, examples, architecture and contribution guidance.
  • Tests

    • Extensive new unit tests covering all added validators, boundary conditions and comparer scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@samtrion samtrion self-assigned this Jan 26, 2026
@samtrion samtrion added state:ready for merge Indicates that a pull request has been reviewed and approved, and is ready to be merged into the mai type:feature Indicates a new feature or enhancement to be added. labels Jan 26, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Walkthrough

Adds many new argument-validation polyfills (strings, collections, counts, duplicates, defaults, GUIDs, generic ranges, and NET8+ date/time helpers), expands unit tests across these behaviors, and replaces/extends README documentation and project branding to "Arguments".

Changes

Cohort / File(s) Summary
Documentation & Branding
README.md, src/NetEvolve.Arguments/README.md
Rebrand to "Arguments"; replace and expand README content with polyfill-focused sections (features, installation, usage, architecture, versioning, contributing, license).
Core API — ArgumentException polyfills
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs
Large API additions: ThrowIfNullOrEmpty overloads for arrays/collections, string length checks (ThrowIfLengthGreaterThan, ThrowIfLengthLessThan, ThrowIfLengthOutOfRange), ThrowIfContainsWhiteSpace, count validators (ThrowIfCountGreaterThan, ThrowIfCountLessThan, ThrowIfCountOutOfRange) across collection types, ThrowIfContainsDuplicates (with optional comparer), ThrowIfDefault<T>, and ThrowIfEmptyGuid. Preserves CallerArgumentExpression and null semantics.
Core API — ArgumentOutOfRange & date/time polyfills
src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs
Added generic ThrowIfOutOfRange<T>(T value, T min, T max) constrained to IComparable<T>. Under NET8_0_OR_GREATER, added ThrowIfInPast / ThrowIfInFuture for DateTimeOffset, DateTime, and DateOnly using TimeProvider; some methods appear in duplicate conditional regions.
Unit tests — Collection/count validations
tests/.../ArgumentException_ThrowIfCountGreaterThanTests.cs, tests/.../ArgumentException_ThrowIfCountLessThanTests.cs, tests/.../ArgumentException_ThrowIfCountOutOfRangeTests.cs, tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs
New/updated tests covering null handling, empty and boundary conditions, and behavior across IEnumerable, ICollection, IReadOnlyCollection, and arrays. Some tests refactor local types to IEnumerable<string>.
Unit tests — String length & whitespace
tests/.../ArgumentException_ThrowIfLengthGreaterThanTests.cs, tests/.../ArgumentException_ThrowIfLengthLessThanTests.cs, tests/.../ArgumentException_ThrowIfLengthOutOfRangeTests.cs, tests/.../ArgumentException_ThrowIfContainsWhiteSpaceTests.cs
New tests for length validators and whitespace detection (space, tab, newline), covering null, empty, boundary, and non-throwing scenarios.
Unit tests — Duplicates, Default, Guid
tests/.../ArgumentException_ThrowIfContainsDuplicatesTests.cs, tests/.../ArgumentException_ThrowIfDefaultTests.cs
New tests for duplicate detection (with/without comparer), default-value checks for structs, and Guid.Empty validation.
Unit tests — Range & DateTime validations
tests/.../ArgumentOutOfRangeException_ThrowIfOutOfRangeTests.cs, tests/.../ArgumentOutOfRangeException_ThrowIfInFutureTests.cs, tests/.../ArgumentOutOfRangeException_ThrowIfInPastTests.cs
New tests for generic range checks and NET8+ time validations; NET8_0_OR_GREATER-guarded files exercise past/now/future scenarios.
Test project file
tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj
Added IDE1006 to NoWarn list; no behavioral changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code to dot each line,

New ThrowIf*s in tidy design,
Counts and lengths and GUIDs aligned,
Dates that mind the present time,
Tests like carrots — bright and fine.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is overly vague and generic, using the non-descriptive phrase 'better support' without specifying what methods were added or what improvements they enable. Revise the title to be more specific about the primary changes, such as: 'feat: Add collection and string validation helpers' or 'feat: Extend ThrowIf* methods for collections and value types'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs`:
- Around line 260-278: The IEnumerable overloads (e.g.,
ThrowIfCountGreaterThan<T>(IEnumerable<T>? argument, int maxCount, ...))
currently skip validation when TryGetNonEnumeratedCount returns false; change
these methods so that after the null check they attempt TryGetNonEnumeratedCount
and if it returns false fall back to enumerating the sequence to compute the
actual count (stop early once count > maxCount for efficiency) and then throw
the existing ArgumentException when the computed count violates the constraint;
apply the same fallback enumeration change to the other two IEnumerable-based
count validators that use TryGetNonEnumeratedCount so non-countable iterators
are validated consistently.

In `@src/NetEvolve.Arguments/README.md`:
- Around line 7-12: The README currently lists two different minimum .NET
Framework versions (4.6.2 in the intro and 4.7.2 in the Features/Universal
Compatibility section); update the intro text to use the same version as the
Features section (change "4.6.2+" to "4.7.2+") so the minimum framework
requirement is consistent across the file, and scan other occurrences of
"4.6.2+" (e.g., near the "ArgumentNullException.ThrowIf*" mention and the
"Universal Compatibility" bullet) to replace them with "4.7.2+".

In
`@tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeException_ThrowIfInPastTests.cs`:
- Around line 21-30: The test method name
ArgumentOutOfRangeException_ThrowIfInPastTests.ThrowIfInPast_DateTimeOffset_WhenValueIsNow_DoesNotThrow
is misleading because it creates a value with
DateTimeOffset.UtcNow.AddSeconds(1) (a future value); either rename the test to
reflect that (e.g.,
ThrowIfInPast_DateTimeOffset_WhenValueIsSlightlyInFuture_DoesNotThrow) or change
the arranged value to DateTimeOffset.UtcNow (or another explicit boundary value)
so the name and behavior match; update the test method identifier and any
references accordingly and keep the assertion using
ArgumentOutOfRangeException.ThrowIfInPast and the subsequent Assert unchanged.
🧹 Nitpick comments (3)
tests/NetEvolve.Arguments.Tests.Unit/ArgumentException_ThrowIfCountGreaterThanTests.cs (1)

56-93: Consider adding null tests for ICollection, IReadOnlyCollection, and Array overloads.

The IEnumerable tests include a null-input test (lines 8-19), but the ICollection, IReadOnlyCollection, and Array sections only test the "exceeds maximum" scenario. For consistency with ArgumentException_ThrowIfCountLessThanTests.cs (which tests null for all collection types), consider adding:

  • ThrowIfCountGreaterThan_ICollection_WhenNull_ThrowsArgumentNullException
  • ThrowIfCountGreaterThan_IReadOnlyCollection_WhenNull_ThrowsArgumentNullException
  • ThrowIfCountGreaterThan_Array_WhenNull_ThrowsArgumentNullException
tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeException_ThrowIfInPastTests.cs (1)

43-65: Consider adding a "now" boundary test for DateTime.

The DateTimeOffset section includes a "WhenValueIsNow" test (lines 21-30), but the DateTime section only tests past and future cases. For consistency, consider adding a ThrowIfInPast_DateTime_WhenValueIsNow_DoesNotThrow test.

tests/NetEvolve.Arguments.Tests.Unit/ArgumentException_ThrowIfCountLessThanTests.cs (1)

56-126: Consider adding "less than minimum" throwing tests for ICollection, IReadOnlyCollection, and Array.

The IEnumerable section thoroughly tests all scenarios (null, less than, equals, greater than), but the ICollection, IReadOnlyCollection, and Array sections only test null and "greater than minimum" cases. For consistency, consider adding tests like:

  • ThrowIfCountLessThan_ICollection_WhenCountLessThanMinimum_ThrowsArgumentException
  • ThrowIfCountLessThan_IReadOnlyCollection_WhenCountLessThanMinimum_ThrowsArgumentException
  • ThrowIfCountLessThan_Array_WhenCountLessThanMinimum_ThrowsArgumentException

This would ensure each overload's throwing behavior is explicitly verified.

@samtrion samtrion force-pushed the feature/extended-methods branch from 4db1408 to 25afa49 Compare January 27, 2026 13:40
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)

64-84: IEnumerable ThrowIfNullOrEmpty skips empty iterators when count isn’t available.

If TryGetNonEnumeratedCount returns false, empty iterators pass without throwing, which violates the method’s contract. Consider a lightweight fallback that checks MoveNext() before returning.

🛠️ Proposed fix (early enumeration fallback)
-            if (argument.TryGetNonEnumeratedCount(out var count) && count == 0)
-            {
-                throw new ArgumentException("The collection cannot be empty.", paramName);
-            }
+            if (argument.TryGetNonEnumeratedCount(out var count))
+            {
+                if (count == 0)
+                {
+                    throw new ArgumentException("The collection cannot be empty.", paramName);
+                }
+            }
+            else
+            {
+                using var enumerator = argument.GetEnumerator();
+                if (!enumerator.MoveNext())
+                {
+                    throw new ArgumentException("The collection cannot be empty.", paramName);
+                }
+            }
🤖 Fix all issues with AI agents
In `@README.md`:
- Line 226: Replace the bare email address in the README sentence that currently
reads "Please report unacceptable behavior to info@daily-devops.net." with a
mailto link (e.g., <mailto:info@daily-devops.net>) so the line becomes a
clickable mailto anchor and satisfies markdownlint rule MD034; simply update the
Markdown content in README.md to use the mailto-style link.

In `@src/NetEvolve.Arguments/README.md`:
- Around line 491-493: Update the README heading "Backward Compatible Argument
Class (Obsolete)" to hyphenate the compound adjective so it reads
"Backward-Compatible Argument Class (Obsolete)"; locate the heading text in the
README.md and change only the heading text, leaving the `Argument` class name
and surrounding content untouched.
🧹 Nitpick comments (1)
tests/NetEvolve.Arguments.Tests.Unit/ArgumentException_ThrowIfCountLessThanTests.cs (1)

69-126: Add boundary tests for ICollection/IReadOnlyCollection/array overloads.

To match the IEnumerable coverage, consider adding “count < min” and “count == min” tests for the other overloads too.

🧪 Example additions (ICollection)
+    [Test]
+    public void ThrowIfCountLessThan_ICollection_WhenCountLessThanMinimum_ThrowsArgumentException()
+    {
+        // Arrange
+        ICollection<int> argument = [1, 2];
+
+        // Act
+        void Act() => ArgumentException.ThrowIfCountLessThan(argument, 5);
+
+        // Assert
+        _ = Assert.Throws<ArgumentException>("argument", Act);
+    }
+
+    [Test]
+    public async Task ThrowIfCountLessThan_ICollection_WhenCountEqualsMinimum_DoesNotThrow()
+    {
+        // Arrange
+        ICollection<int> argument = [1, 2, 3, 4, 5];
+
+        // Act & Assert
+        ArgumentException.ThrowIfCountLessThan(argument, 5);
+        _ = await Assert.That(argument.Count).IsEqualTo(5);
+    }

Simplified collection count and duplicate checks in ArgumentExceptionPolyfills. Improved default value comparison. Added comprehensive unit tests for null and empty collections across all overloads. Updated test for Guid default value. Suppressed IDE1006 warnings in test project.
@samtrion samtrion force-pushed the feature/extended-methods branch from caf35a6 to f4f5c4b Compare January 27, 2026 14:25
@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

❌ Patch coverage is 68.75000% with 75 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.94%. Comparing base (dfea67a) to head (f4f5c4b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...c/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs 59.45% 57 Missing and 18 partials ⚠️

❌ Your patch status has failed because the patch coverage (68.75%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##              main     #523       +/-   ##
============================================
- Coverage   100.00%   77.94%   -22.06%     
============================================
  Files           13       13               
  Lines          100      340      +240     
  Branches        26       92       +66     
============================================
+ Hits           100      265      +165     
- Misses           0       57       +57     
- Partials         0       18       +18     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)

64-84: Ensure IEnumerable emptiness is validated when count is unavailable.

If TryGetNonEnumeratedCount returns false, empty iterators currently pass validation silently, which breaks the method contract.

✅ Proposed fix
-            if (argument.TryGetNonEnumeratedCount(out var count) && count == 0)
-            {
-                throw new ArgumentException("The collection cannot be empty.", paramName);
-            }
+            if (argument.TryGetNonEnumeratedCount(out var count))
+            {
+                if (count == 0)
+                {
+                    throw new ArgumentException("The collection cannot be empty.", paramName);
+                }
+            }
+            else
+            {
+                using var enumerator = argument.GetEnumerator();
+                if (!enumerator.MoveNext())
+                {
+                    throw new ArgumentException("The collection cannot be empty.", paramName);
+                }
+            }
🧹 Nitpick comments (3)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)

379-423: Short‑circuit non‑countable enumerables to avoid full/infinite enumeration.

The fallback branches fully enumerate sequences even when the threshold is satisfied; this can be costly or hang on unbounded sequences.

♻️ Suggested refactor
         else
         {
-            var actualCount = 0;
-            foreach (var _ in argument)
-            {
-                actualCount++;
-            }
-
-            if (actualCount < minCount)
-            {
-                throw new ArgumentException(
-                    $"The collection count {actualCount} is less than the minimum required count {minCount}.",
-                    paramName
-                );
-            }
+            if (minCount <= 0)
+            {
+                return;
+            }
+
+            var actualCount = 0;
+            foreach (var _ in argument)
+            {
+                if (++actualCount >= minCount)
+                {
+                    return;
+                }
+            }
+
+            throw new ArgumentException(
+                $"The collection count {actualCount} is less than the minimum required count {minCount}.",
+                paramName
+            );
         }
         else
         {
             var actualCount = 0;
             foreach (var _ in argument)
             {
-                actualCount++;
+                actualCount++;
+                if (actualCount > maxCount)
+                {
+                    throw new ArgumentException(
+                        $"The collection count {actualCount} is outside the allowed range [{minCount}, {maxCount}].",
+                        paramName
+                    );
+                }
             }
 
-            if (actualCount < minCount || actualCount > maxCount)
+            if (actualCount < minCount)
             {
                 throw new ArgumentException(
                     $"The collection count {actualCount} is outside the allowed range [{minCount}, {maxCount}].",
                     paramName
                 );
             }
         }

Also applies to: 506-552

tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeException_ThrowIfInPastTests.cs (1)

21-30: Capture a single “now” per test to reduce timing flakiness.

Multiple UtcNow calls can drift; caching the baseline improves stability.

✍️ Example tweak
     public async Task ThrowIfInPast_DateTimeOffset_WhenValueIsSlightlyInFuture_DoesNotThrow()
     {
         // Arrange
-        var argument = DateTimeOffset.UtcNow.AddSeconds(1);
+        var now = DateTimeOffset.UtcNow;
+        var argument = now.AddSeconds(1);
 
         // Act & Assert
         ArgumentOutOfRangeException.ThrowIfInPast(argument);
-        _ = await Assert.That(argument).IsGreaterThan(DateTimeOffset.UtcNow.AddSeconds(-1));
+        _ = await Assert.That(argument).IsGreaterThan(now.AddSeconds(-1));
     }

Apply the same pattern to the other UtcNow / UtcNow.Add* assertions in this file.

tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs (1)

95-107: Add iterator-based IEnumerable test to cover non-countable sequences.

This ensures ThrowIfNullOrEmpty(IEnumerable<T>) validates empty iterators, not just countable collections.

🧪 Suggested test
     public void ThrowIfNullOrEmpty_Enumerable_WhenArgumentIsNull_ThrowsArgumentNullException()
     {
         // Arrange
         IEnumerable<int>? argument = null;
@@
         _ = Assert.Throws<ArgumentNullException>("argument", Act);
     }
+
+    [Test]
+    public void ThrowIfNullOrEmpty_Enumerable_WhenArgumentIsEmptyIterator_ThrowsArgumentException()
+    {
+        // Arrange
+        IEnumerable<int> argument = Empty();
+
+        // Act
+        void Act() => ArgumentException.ThrowIfNullOrEmpty(argument);
+
+        // Assert
+        _ = Assert.Throws<ArgumentException>("argument", Act);
+
+        static IEnumerable<int> Empty()
+        {
+            yield break;
+        }
+    }

@samtrion samtrion merged commit d6c7286 into main Jan 27, 2026
9 of 11 checks passed
@samtrion samtrion deleted the feature/extended-methods branch January 27, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:ready for merge Indicates that a pull request has been reviewed and approved, and is ready to be merged into the mai type:feature Indicates a new feature or enhancement to be added.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants