diff --git a/Code/Light.GuardClauses/Light.GuardClauses.csproj b/Code/Light.GuardClauses/Light.GuardClauses.csproj index 7780ff0..845a5fe 100644 --- a/Code/Light.GuardClauses/Light.GuardClauses.csproj +++ b/Code/Light.GuardClauses/Light.GuardClauses.csproj @@ -1,59 +1,55 @@ - + - - netstandard2.0;netstandard2.1;net8.0 - A lightweight .NET library for expressive Guard Clauses. - Kenny Pflug - Kenny Pflug - enable - true - Copyright © Kenny Pflug 2016, 2025 - 12 - true - true - MIT - light-logo.png - https://github.com/feO2x/Light.GuardClauses - https://github.com/feO2x/Light.GuardClauses.git - git - true - true - snupkg - true - Assertions;Preconditions;GuardClauses;DesignByContract;DbC - true - README.md - -Light.GuardClauses 13.0.0 --------------------------------- + + netstandard2.0;netstandard2.1;net8.0 + A lightweight .NET library for expressive Guard Clauses. + Kenny Pflug + Kenny Pflug + enable + true + Copyright © Kenny Pflug 2016, 2025 + 12 + true + true + MIT + light-logo.png + https://github.com/feO2x/Light.GuardClauses + https://github.com/feO2x/Light.GuardClauses.git + git + true + true + snupkg + true + Assertions;Preconditions;GuardClauses;DesignByContract;DbC + true + README.md + + Light.GuardClauses 13.1.0 + -------------------------------- -- new assertions: IsApproximately<T>, IsLessThanOrApproximately<T>, IsGreaterThanOrApproximately<T>, MustBeApproximately, MustNotBeApproximately, MustBeLessThanOrApproximately, MustBeGreaterThanOrApproximately, IsEmptyOrWhiteSpace, IsFileExtension -- email regex is now precompiled on .NET 8 and newer, the regex is compiled at runtime on .NET Standard 2.0 and 2.1 -- breaking: Throw class is now located in new namespace Light.GuardClauses.ExceptionFactory -- breaking: Throw members regarding spans now only support ReadOnlySpan<T>, in keywords were removed -- breaking: IsApproximately now uses less-than-or-equal-to operator (<=) instead of less-than operator (<) -- breaking: Email regex is less strict and support additional patterns like domains with more than 3 letters (e.g. .info or .travel) - - - - - - - - - - + - new assertions for ImmutableArray<T>: MustNotBeDefaultOrEmpty, MustHaveLength, MustHaveLengthIn, MustHaveMinimumLength, MustHaveMaximumLength + - new dependency: System.Collections.Immutable + + - - - + + + + + + + - - - - + + + + + + + + \ No newline at end of file diff --git a/Code/Version.props b/Code/Version.props index 83928e4..e4e99d8 100644 --- a/Code/Version.props +++ b/Code/Version.props @@ -1,5 +1,5 @@  - 13.0.0 + 13.1.0 \ No newline at end of file diff --git a/Light.GuardClauses.SingleFile.cs b/Light.GuardClauses.SingleFile.cs index d984d0b..758820c 100644 --- a/Light.GuardClauses.SingleFile.cs +++ b/Light.GuardClauses.SingleFile.cs @@ -1,5 +1,5 @@ /* ------------------------------ - Light.GuardClauses 13.0.0 + Light.GuardClauses 13.1.0 ------------------------------ License information for Light.GuardClauses @@ -270,6 +270,50 @@ public static string MustNotContain([NotNull][ValidatedNotNull] this string? par return parameter; } + /// + /// Ensures that the does not contain the specified item, or otherwise throws an . + /// + /// The to be checked. + /// The item that must not be part of the . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + /// + /// The default instance of cannot contain any items, so this method will not throw for default instances. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustNotContain(this ImmutableArray parameter, T item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (!parameter.IsDefault && parameter.Contains(item)) + { + Throw.ExistingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the does not contain the specified item, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The item that must not be part of the . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when contains . + /// + /// The default instance of cannot contain any items, so this method will not throw for default instances. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static ImmutableArray MustNotContain(this ImmutableArray parameter, T item, Func, T, Exception> exceptionFactory) + { + if (!parameter.IsDefault && parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + /// /// Checks if the specified string is trimmed at the end, i.e. it does not end with /// white space characters. Inputting an empty string will return true. @@ -2764,6 +2808,42 @@ public static Uri MustHaveScheme([NotNull][ValidatedNotNull] this Uri? parameter /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsLessThanOrApproximately(this float value, float other) => value < other || value.IsApproximately(other); + /// + /// Ensures that the specified is not default or empty, or otherwise throws an . + /// + /// The to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is default or empty. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustNotBeDefaultOrEmpty(this ImmutableArray parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (parameter.IsDefaultOrEmpty) + { + Throw.EmptyCollection(parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not default or empty, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when is default or empty. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static ImmutableArray MustNotBeDefaultOrEmpty(this ImmutableArray parameter, Func, Exception> exceptionFactory) + { + if (parameter.IsDefaultOrEmpty) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } + /// /// Ensures that the specified URI has the "http" scheme, or otherwise throws an . /// @@ -4227,6 +4307,46 @@ public static string MustHaveLengthIn([NotNull][ValidatedNotNull] this string? p return parameter; } + /// + /// Ensures that the 's length is within the specified range, or otherwise throws an . + /// + /// The to be checked. + /// The range where the 's length must be in-between. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the length of is not within the specified . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveLengthIn(this ImmutableArray parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + var length = parameter.IsDefault ? 0 : parameter.Length; + if (!range.IsValueWithinRange(length)) + { + Throw.ImmutableArrayLengthNotInRange(parameter, range, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the 's length is within the specified range, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The range where the 's length must be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the length of is not within the specified range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static ImmutableArray MustHaveLengthIn(this ImmutableArray parameter, Range range, Func, Range, Exception> exceptionFactory) + { + var length = parameter.IsDefault ? 0 : parameter.Length; + if (!range.IsValueWithinRange(length)) + { + Throw.CustomException(exceptionFactory, parameter, range); + } + + return parameter; + } + /// /// Ensures that the specified parameter is not the default value, or otherwise throws an /// for reference types, or an for value types. @@ -4402,6 +4522,47 @@ public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan paramete return parameter; } + /// + /// Ensures that the has at most the specified length, or otherwise throws an . + /// + /// The to be checked. + /// The maximum length the should have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has more than the specified length. + /// The default instance of will be treated as having length 0. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveMaximumLength(this ImmutableArray parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + var parameterLength = parameter.IsDefault ? 0 : parameter.Length; + if (parameterLength > length) + { + Throw.InvalidMaximumImmutableArrayLength(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the has at most the specified length, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The maximum length the should have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when has more than the specified length. + /// The default instance of will be treated as having length 0. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveMaximumLength(this ImmutableArray parameter, int length, Func, int, Exception> exceptionFactory) + { + var parameterLength = parameter.IsDefault ? 0 : parameter.Length; + if (parameterLength > length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + /// /// Checks if the specified collection is null or empty. /// @@ -4481,6 +4642,47 @@ public static string MustBeTrimmedAtEnd([NotNull][ValidatedNotNull] this string? return parameter; } + /// + /// Ensures that the has at least the specified length, or otherwise throws an . + /// + /// The to be checked. + /// The minimum length the should have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has less than the specified length. + /// The default instance of will be treated as having length 0. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveMinimumLength(this ImmutableArray parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + var parameterLength = parameter.IsDefault ? 0 : parameter.Length; + if (parameterLength < length) + { + Throw.InvalidMinimumImmutableArrayLength(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the has at least the specified length, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The minimum length the should have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when has less than the specified length. + /// The default instance of will be treated as having length 0. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveMinimumLength(this ImmutableArray parameter, int length, Func, int, Exception> exceptionFactory) + { + var parameterLength = parameter.IsDefault ? 0 : parameter.Length; + if (parameterLength < length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + /// /// Ensures that the specified parameter is not null when is a reference type, or otherwise /// throws an . PLEASE NOTICE: you should only use this assertion in generic contexts, @@ -4720,6 +4922,45 @@ public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, return parameter; } + /// + /// Ensures that the immutable array has the specified length, or otherwise throws an . + /// + /// The immutable array to be checked. + /// The length that the immutable array must have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveLength(this ImmutableArray parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + var actualLength = parameter.IsDefault ? 0 : parameter.Length; + if (actualLength != length) + { + Throw.InvalidImmutableArrayLength(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the immutable array has the specified length, or otherwise throws your custom exception. + /// + /// The immutable array to be checked. + /// The length that the immutable array must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveLength(this ImmutableArray parameter, int length, Func, int, Exception> exceptionFactory) + { + var actualLength = parameter.IsDefault ? 0 : parameter.Length; + if (actualLength != length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + /// /// Checks if the specified type derives from the other type. Internally, this method uses /// by default so that constructed generic types and their corresponding generic type definitions are regarded as equal. @@ -5329,6 +5570,43 @@ public static string MustContain([NotNull][ValidatedNotNull] this string? parame return parameter; } + /// + /// Ensures that the immutable array contains the specified item, or otherwise throws a . + /// + /// The immutable array to be checked. + /// The item that must be part of the immutable array. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustContain(this ImmutableArray parameter, T item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (!parameter.Contains(item)) + { + Throw.MissingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the immutable array contains the specified item, or otherwise throws your custom exception. + /// + /// The immutable array to be checked. + /// The item that must be part of the immutable array. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustContain(this ImmutableArray parameter, T item, Func, T, Exception> exceptionFactory) + { + if (!parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + /// /// Ensures that the specified is not less than the given value, or otherwise throws an . /// @@ -6634,6 +6912,17 @@ internal static class Throw [ContractAnnotation("=> halt")] [DoesNotReturn] public static void EmptyString(string? parameterName = null, string? message = null) => throw new EmptyStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be an empty string, but it actually is."); + /// + /// Throws the default indicating that an has less than a + /// minimum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMinimumImmutableArrayLength(ImmutableArray parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The immutable array"} must have at least a length of {length}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has a length of {parameter.Length}")}."); + } + /// /// Throws the default indicating that a comparable value must not be /// less than or equal to the given boundary value, using the optional parameter name and message. @@ -7144,6 +7433,18 @@ public static void MustNotBeGreaterThanOrEqualTo(T parameter, T boundary, [Ca [ContractAnnotation("=> halt")] [DoesNotReturn] public static void StringDoesNotMatch(string parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringDoesNotMatchException(parameterName, message ?? $"{parameterName ?? "The string"} must match the regular expression \"{regex}\", but it actually is \"{parameter}\"."); + /// + /// Throws the default indicating that an immutable array has an invalid length, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidImmutableArrayLength(ImmutableArray parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + var actualLength = parameter.IsDefault ? 0 : parameter.Length; + throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The immutable array"} must have length {length}, but it actually has length {actualLength}."); + } + /// /// Throws the default indicating that a value is not one of a specified /// collection of items, using the optional parameter name and message. @@ -7151,6 +7452,17 @@ public static void MustNotBeGreaterThanOrEqualTo(T parameter, T boundary, [Ca [ContractAnnotation("=> halt")] [DoesNotReturn] public static void ValueNotOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValueIsNotOneOfException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The value"} must be one of the following items").AppendItemsWithNewLine(items).AppendLine($"but it actually is {parameter.ToStringOrNull()}.").ToString()); + /// + /// Throws the default indicating that an has more than a + /// maximum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMaximumImmutableArrayLength(ImmutableArray parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The immutable array"} must have at most a length of {length}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has a length of {parameter.Length}")}."); + } + /// /// Throws the default indicating that a value must not be approximately /// equal to another value within a specified tolerance, using the optional parameter name and message. @@ -7209,6 +7521,13 @@ public static void MustNotBeGreaterThan(T parameter, T boundary, [CallerArgum [DoesNotReturn] public static void StringContains(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); /// + /// Throws the default indicating that an 's length is not within the + /// given range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ImmutableArrayLengthNotInRange(ImmutableArray parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The immutable array"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has length {parameter.Length}")}."); + /// /// Throws the default indicating that a string does not start with another one, /// using the optional parameter name and message. /// diff --git a/README.md b/README.md index 64c403f..d4062b7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ **A lightweight .NET library for expressive Guard Clauses.** [![License](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/blob/master/LICENSE) -[![NuGet](https://img.shields.io/badge/NuGet-13.0.0-blue.svg?style=for-the-badge)](https://www.nuget.org/packages/Light.GuardClauses/) -[![Source Code](https://img.shields.io/badge/Source%20Code-13.0.0-blue.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/blob/master/Light.GuardClauses.SingleFile.cs) +[![NuGet](https://img.shields.io/badge/NuGet-13.1.0-blue.svg?style=for-the-badge)](https://www.nuget.org/packages/Light.GuardClauses/) +[![Source Code](https://img.shields.io/badge/Source%20Code-13.1.0-blue.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/blob/master/Light.GuardClauses.SingleFile.cs) [![Documentation](https://img.shields.io/badge/Docs-Wiki-yellowgreen.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/wiki) [![Documentation](https://img.shields.io/badge/Docs-Changelog-yellowgreen.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/releases) @@ -94,7 +94,7 @@ Every assertion is well-documented - explore them using IntelliSense or check ou ## Light.GuardClauses is optimized -Since version 4, **Light.GuardClauses** is optimized for performance (measured in .NET 4.8 and .NET 6). With the incredible help of [@redknightlois](https://github.com/redknightlois) and the awesome tool [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet), most assertions are as fast as your imperative code would be. +Since version 4, **Light.GuardClauses** is optimized for performance (measured in .NET 4.8 and .NET 8). With the incredible help of [@redknightlois](https://github.com/redknightlois) and the awesome tool [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet), most assertions are as fast as your imperative code would be. **Light.GuardClauses** has support for [.NET analyzers / FxCopAnalyzers](https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview) with the `ValidatedNotNullAttribute` and the `NotNullAttribute`. Analyzers will know when an assertion validated that a parameters is not null and consequently, CA1062 will not be raised. @@ -116,7 +116,7 @@ Light.GuardClauses is available as a [NuGet package](https://www.nuget.org/packa - **dotnet CLI**: `dotnet add package Light.GuardClauses` - **Visual Studio Package Manager Console**: `Install-Package Light.GuardClauses` -- **Package Reference in csproj**: `` +- **Package Reference in csproj**: `` Also, you can incorporate Light.GuardClauses as a **single source file** where the API is changed to `internal`. This is especially interesting for framework / library developers that do not want to have a dependency on the Light.GuardClauses DLL. You can grab the default .NET Standard 2.0 version in [Light.GuardClauses.SingleFile.cs](https://github.com/feO2x/Light.GuardClauses/blob/master/Light.GuardClauses.SingleFile.cs) or you can use the [Light.GuardClauses.SourceCodeTransformation](https://github.com/feO2x/Light.GuardClauses/tree/master/Code/Light.GuardClauses.SourceCodeTransformation) project to create your custom file. You can learn more about it [here](https://github.com/feO2x/Light.GuardClauses/wiki/Including-Light.GuardClauses-as-source-code).