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
44 changes: 6 additions & 38 deletions Dummies/AnyCollection.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#region Usings declarations

using System.Globalization;

#endregion

namespace Dummies;

/// <summary>
Expand All @@ -22,23 +16,9 @@
/// <typeparam name="TItem">The element type.</typeparam>
/// <typeparam name="TResult">The collection type <see cref="Generate" /> produces.</typeparam>
/// <typeparam name="TSelf">The concrete generator type, so the fluent methods return it.</typeparam>
public abstract class AnyCollection<TItem, TResult, TSelf> : IAny<TResult>, IHasRandomSource

Check warning on line 19 in Dummies/AnyCollection.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'AnyCollection' class to no more than the 2 authorized.

Check warning on line 19 in Dummies/AnyCollection.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'AnyCollection' class to no more than the 2 authorized.

Check warning on line 19 in Dummies/AnyCollection.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'AnyCollection' class to no more than the 2 authorized.

Check warning on line 19 in Dummies/AnyCollection.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'AnyCollection' class to no more than the 2 authorized.
where TSelf : AnyCollection<TItem, TResult, TSelf> {

#region Statics members declarations

private static string V(int value) {
return value.ToString(CultureInfo.InvariantCulture);
}

private static int RequireNonNegative(int count, string parameterName) {
if (count < 0) { throw new ArgumentOutOfRangeException(parameterName, count, "The count must not be negative."); }

return count;
}

#endregion

#region Fields declarations

private protected readonly RandomSource? SourceOrNull;
Expand All @@ -57,14 +37,14 @@
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public TSelf NonEmpty() {
return With(State.WithMinCount(1, "NonEmpty()"));
return With(CountConstraints.NonEmpty(State));
}

/// <summary>Fixes the collection to no elements.</summary>
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public TSelf Empty() {
return With(State.WithExactCount(0, "Empty()"));
return With(CountConstraints.Empty(State));
}

/// <summary>Fixes the exact number of elements. Declared once per generator.</summary>
Expand All @@ -73,9 +53,7 @@
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="count" /> is negative.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public TSelf WithCount(int count) {
RequireNonNegative(count, nameof(count));

return With(State.WithExactCount(count, $"WithCount({V(count)})"));
return With(CountConstraints.WithCount(State, count));
}

/// <summary>Requires at least <paramref name="count" /> elements.</summary>
Expand All @@ -84,9 +62,7 @@
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="count" /> is negative.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public TSelf WithMinCount(int count) {
RequireNonNegative(count, nameof(count));

return With(State.WithMinCount(count, $"WithMinCount({V(count)})"));
return With(CountConstraints.WithMinCount(State, count));
}

/// <summary>Requires at most <paramref name="count" /> elements.</summary>
Expand All @@ -95,9 +71,7 @@
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="count" /> is negative.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public TSelf WithMaxCount(int count) {
RequireNonNegative(count, nameof(count));

return With(State.WithMaxCount(count, $"WithMaxCount({V(count)})"));
return With(CountConstraints.WithMaxCount(State, count));
}

/// <summary>Requires a number of elements within the inclusive range [<paramref name="minimum" />, <paramref name="maximum" />].</summary>
Expand All @@ -108,13 +82,7 @@
/// <exception cref="ArgumentException">Thrown when <paramref name="minimum" /> is greater than <paramref name="maximum" />.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public TSelf WithCountBetween(int minimum, int maximum) {
RequireNonNegative(minimum, nameof(minimum));
RequireNonNegative(maximum, nameof(maximum));
if (minimum > maximum) { throw new ArgumentException($"The minimum ({V(minimum)}) must be less than or equal to the maximum ({V(maximum)}).", nameof(minimum)); }

string constraint = $"WithCountBetween({V(minimum)}, {V(maximum)})";

return With(State.WithMinCount(minimum, constraint).WithMaxCount(maximum, constraint));
return With(CountConstraints.WithCountBetween(State, minimum, maximum));
}

/// <summary>
Expand Down
44 changes: 6 additions & 38 deletions Dummies/AnyDictionary.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#region Usings declarations

using System.Globalization;

#endregion

namespace Dummies;

/// <summary>
Expand All @@ -18,20 +12,6 @@ namespace Dummies;
public sealed class AnyDictionary<TKey, TValue> : IAny<Dictionary<TKey, TValue>>, IHasRandomSource
where TKey : notnull {

#region Statics members declarations

private static string V(int value) {
return value.ToString(CultureInfo.InvariantCulture);
}

private static int RequireNonNegative(int count, string parameterName) {
if (count < 0) { throw new ArgumentOutOfRangeException(parameterName, count, "The count must not be negative."); }

return count;
}

#endregion

#region Fields declarations

private readonly CollectionState<TKey> _keys;
Expand All @@ -52,14 +32,14 @@ internal AnyDictionary(RandomSource? source, CollectionState<TKey> keys, IAny<TV
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> NonEmpty() {
return With(_keys.WithMinCount(1, "NonEmpty()"));
return With(CountConstraints.NonEmpty(_keys));
}

/// <summary>Fixes the dictionary to no entries.</summary>
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> Empty() {
return With(_keys.WithExactCount(0, "Empty()"));
return With(CountConstraints.Empty(_keys));
}

/// <summary>Fixes the exact number of entries. Declared once per generator.</summary>
Expand All @@ -68,9 +48,7 @@ public AnyDictionary<TKey, TValue> Empty() {
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="count" /> is negative.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> WithCount(int count) {
RequireNonNegative(count, nameof(count));

return With(_keys.WithExactCount(count, $"WithCount({V(count)})"));
return With(CountConstraints.WithCount(_keys, count));
}

/// <summary>Requires at least <paramref name="count" /> entries.</summary>
Expand All @@ -79,9 +57,7 @@ public AnyDictionary<TKey, TValue> WithCount(int count) {
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="count" /> is negative.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> WithMinCount(int count) {
RequireNonNegative(count, nameof(count));

return With(_keys.WithMinCount(count, $"WithMinCount({V(count)})"));
return With(CountConstraints.WithMinCount(_keys, count));
}

/// <summary>Requires at most <paramref name="count" /> entries.</summary>
Expand All @@ -90,9 +66,7 @@ public AnyDictionary<TKey, TValue> WithMinCount(int count) {
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="count" /> is negative.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> WithMaxCount(int count) {
RequireNonNegative(count, nameof(count));

return With(_keys.WithMaxCount(count, $"WithMaxCount({V(count)})"));
return With(CountConstraints.WithMaxCount(_keys, count));
}

/// <summary>Requires a number of entries within the inclusive range [<paramref name="minimum" />, <paramref name="maximum" />].</summary>
Expand All @@ -103,13 +77,7 @@ public AnyDictionary<TKey, TValue> WithMaxCount(int count) {
/// <exception cref="ArgumentException">Thrown when <paramref name="minimum" /> is greater than <paramref name="maximum" />.</exception>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> WithCountBetween(int minimum, int maximum) {
RequireNonNegative(minimum, nameof(minimum));
RequireNonNegative(maximum, nameof(maximum));
if (minimum > maximum) { throw new ArgumentException($"The minimum ({V(minimum)}) must be less than or equal to the maximum ({V(maximum)}).", nameof(minimum)); }

string constraint = $"WithCountBetween({V(minimum)}, {V(maximum)})";

return With(_keys.WithMinCount(minimum, constraint).WithMaxCount(maximum, constraint));
return With(CountConstraints.WithCountBetween(_keys, minimum, maximum));
}

/// <inheritdoc />
Expand Down
82 changes: 82 additions & 0 deletions Dummies/CountConstraints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#region Usings declarations

using System.Globalization;

#endregion

namespace Dummies;

/// <summary>
/// The count-constraint facade shared by every collection generator, defined once over
/// <see cref="CollectionState{T}" />. The <see cref="AnyCollection{TItem,TResult,TSelf}" /> builders
/// (a list, an array, a sequence, a set) and <see cref="AnyDictionary{TKey,TValue}" /> — whose keys run through
/// the very same <see cref="CollectionState{T}" /> — each expose <c>NonEmpty</c>/<c>Empty</c>/<c>WithCount</c>/
/// <c>WithMinCount</c>/<c>WithMaxCount</c>/<c>WithCountBetween</c> by delegating here, so the argument
/// validation and the constraint labels that surface in a <see cref="ConflictingAnyConstraintException" /> live
/// in exactly one place and cannot drift between the two surfaces.
/// </summary>
/// <remarks>
/// Each method takes a state and returns the tightened state; the caller wraps that state back into its own
/// immutable generator. The label strings (<c>"NonEmpty()"</c>, <c>"WithCount(3)"</c>, ...) are part of the
/// user-facing conflict messages, so they are produced here rather than in the label-agnostic
/// <see cref="CollectionState{T}" />, which only records whichever label its caller hands it.
/// </remarks>
internal static class CountConstraints {

#region Statics members declarations

private static string V(int value) {
return value.ToString(CultureInfo.InvariantCulture);
}

private static int RequireNonNegative(int count, string parameterName) {

Check warning on line 32 in Dummies/CountConstraints.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Change return type to 'void'; not a single caller uses the returned value.

Check warning on line 32 in Dummies/CountConstraints.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Change return type to 'void'; not a single caller uses the returned value.

Check warning on line 32 in Dummies/CountConstraints.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Change return type to 'void'; not a single caller uses the returned value.

Check warning on line 32 in Dummies/CountConstraints.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Change return type to 'void'; not a single caller uses the returned value.
if (count < 0) { throw new ArgumentOutOfRangeException(parameterName, count, "The count must not be negative."); }

return count;
}

#endregion

/// <summary>Requires at least one element.</summary>
internal static CollectionState<T> NonEmpty<T>(CollectionState<T> state) {
return state.WithMinCount(1, "NonEmpty()");
}

/// <summary>Fixes the collection to no elements.</summary>
internal static CollectionState<T> Empty<T>(CollectionState<T> state) {
return state.WithExactCount(0, "Empty()");
}

/// <summary>Fixes the exact number of elements.</summary>
internal static CollectionState<T> WithCount<T>(CollectionState<T> state, int count) {
RequireNonNegative(count, nameof(count));

return state.WithExactCount(count, $"WithCount({V(count)})");
}

/// <summary>Requires at least <paramref name="count" /> elements.</summary>
internal static CollectionState<T> WithMinCount<T>(CollectionState<T> state, int count) {
RequireNonNegative(count, nameof(count));

return state.WithMinCount(count, $"WithMinCount({V(count)})");
}

/// <summary>Requires at most <paramref name="count" /> elements.</summary>
internal static CollectionState<T> WithMaxCount<T>(CollectionState<T> state, int count) {
RequireNonNegative(count, nameof(count));

return state.WithMaxCount(count, $"WithMaxCount({V(count)})");
}

/// <summary>Requires a number of elements within the inclusive range [<paramref name="minimum" />, <paramref name="maximum" />].</summary>
internal static CollectionState<T> WithCountBetween<T>(CollectionState<T> state, int minimum, int maximum) {
RequireNonNegative(minimum, nameof(minimum));
RequireNonNegative(maximum, nameof(maximum));
if (minimum > maximum) { throw new ArgumentException($"The minimum ({V(minimum)}) must be less than or equal to the maximum ({V(maximum)}).", nameof(minimum)); }

string constraint = $"WithCountBetween({V(minimum)}, {V(maximum)})";

return state.WithMinCount(minimum, constraint).WithMaxCount(maximum, constraint);
}

}