From 8ef399c9b3ff969bd84140c4604a5bf18c87810a Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Thu, 31 Mar 2022 22:28:26 -0400 Subject: [PATCH 01/25] #12 #16 Initial commit. --- README.md | 2 +- .../Algorithms/Checksum/LuhnFormula.cs | 14 +- src/Validations/Arguments.cs | 162 ++++++++++-------- .../ArgumentsHelpers/Extensions.cs | 35 ++-- .../ArgumentsHelpers/NullAndEmptyChecks.cs | 36 ++-- .../ArgumentsHelpers/OutOfRangeChecks.cs | 68 ++++---- src/Validations/GlobalUsings.cs | 4 + src/Validations/Properties/AssemblyInfo.cs | 2 +- src/Validations/State.cs | 10 +- src/Validations/Validations.csproj | 8 +- 10 files changed, 194 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index caa5180..169bc42 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Validation library inspired by the concepts of ***Secure by Design***, by Dan Be ## Project's Code Health ## ### Overall ### - [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=alert_status&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) +[![Quality Gate](https://sonarcloud.io/api/project_badges/quality_gate?project=lsolano_triplex&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) ### Ratings ### [![SQALE Rating](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=sqale_rating&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) [![SQALE Index](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=sqale_index&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=reliability_rating&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) diff --git a/src/Validations/Algorithms/Checksum/LuhnFormula.cs b/src/Validations/Algorithms/Checksum/LuhnFormula.cs index 8b8224e..177af4d 100644 --- a/src/Validations/Algorithms/Checksum/LuhnFormula.cs +++ b/src/Validations/Algorithms/Checksum/LuhnFormula.cs @@ -1,7 +1,4 @@ -using Triplex.Validations.ArgumentsHelpers; -using Triplex.Validations.Utilities; - -namespace Triplex.Validations.Algorithms.Checksum; +namespace Triplex.Validations.Algorithms.Checksum; /// /// Implementation of the Luhn algorithm in its two variants: as validation and as checksum generator. @@ -25,7 +22,7 @@ public static class LuhnFormula /// /// If contains elements not within range [0-9]. /// - public static bool IsValid([ValidatedNotNull] int[]? fullDigits) + public static bool IsValid([NotNull, ValidatedNotNull] int[]? fullDigits) { int[] validatedDigits = fullDigits.ValueOrThrowIfNullOrWithLessThanElements(MinimumElements, nameof(fullDigits)); @@ -54,7 +51,7 @@ public static bool IsValid([ValidatedNotNull] int[]? fullDigits) /// /// If contains characters other than digits. /// - public static bool IsValid([ValidatedNotNull] string? fullDigits) + public static bool IsValid([NotNull, ValidatedNotNull] string? fullDigits) { string notNullDigits = ValidateDigitsAsString(fullDigits); @@ -63,7 +60,8 @@ public static bool IsValid([ValidatedNotNull] string? fullDigits) return DoDigitCheck(validatedDigits); } - private static string ValidateDigitsAsString([ValidatedNotNull] string? fullDigits) + [return: NotNull] + private static string ValidateDigitsAsString([NotNull, ValidatedNotNull] string? fullDigits) { string notNullDigits = fullDigits.ValueOrThrowIfNullOrZeroLength(nameof(fullDigits)); @@ -101,7 +99,7 @@ private static bool DoDigitCheck(int[] sanitizedDigits) /// /// If is has less than one digits. /// - public static int GetCheckDigit([ValidatedNotNull] int[]? digitsWithoutCheck) + public static int GetCheckDigit([NotNull, ValidatedNotNull] int[]? digitsWithoutCheck) { const int minimumElements = 1; diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index 59e8530..451fe81 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -1,7 +1,4 @@ using Triplex.Validations.Algorithms.Checksum; -using Triplex.Validations.ArgumentsHelpers; -using Triplex.Validations.Exceptions; -using Triplex.Validations.Utilities; #pragma warning disable CA1303 // Do not pass literals as localized parameters namespace Triplex.Validations; @@ -22,8 +19,9 @@ public static class Arguments /// /// If is . [DebuggerStepThrough] - public static TParamType NotNull([ValidatedNotNull] TParamType? value, - [ValidatedNotNull] string paramName) where TParamType : class + [return: NotNull] + public static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, + [NotNull, ValidatedNotNull] string paramName) where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName); /// @@ -45,8 +43,10 @@ public static TParamType NotNull([ValidatedNotNull] TParamType? valu /// /// If is . [DebuggerStepThrough] - public static TParamType NotNull([ValidatedNotNull] TParamType? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) where TParamType : class + [return: NotNull] + public static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName, customMessage); /// @@ -63,8 +63,9 @@ public static TParamType NotNull([ValidatedNotNull] TParamType? valu /// [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmptyOrWhiteSpaceOnly(string?, string) instead.", error: false)] [DebuggerStepThrough] - public static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName) + [return: NotNull] + public static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName) => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName); /// @@ -82,20 +83,23 @@ public static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? val /// [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmptyOrWhiteSpaceOnly(string?, string, string) instead.", error: false)] [DebuggerStepThrough] - public static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) + [return: NotNull] + public static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); /// [DebuggerStepThrough] - public static string NotEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName) + [return: NotNull] + public static string NotEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName) => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName); /// [DebuggerStepThrough] - public static string NotEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) + [return: NotNull] + public static string NotEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); /// @@ -111,7 +115,9 @@ public static string NotEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, /// [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmpty(string?, string) instead.", error: false)] [DebuggerStepThrough] - public static string NotNullOrEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName) + [return: NotNull] + public static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName) => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); /// @@ -125,19 +131,23 @@ public static string NotNullOrEmpty([ValidatedNotNull] string? value, [Validated /// If length is zero. [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmpty(string?, string, string) instead.", error: false)] [DebuggerStepThrough] - public static string NotNullOrEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + [return: NotNull] + public static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); /// [DebuggerStepThrough] - public static string NotEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName) + [return: NotNull] + public static string NotEmpty([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName) => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); /// [DebuggerStepThrough] - public static string NotEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + [return: NotNull] + public static string NotEmpty([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); #endregion @@ -151,7 +161,7 @@ public static string NotEmpty([ValidatedNotNull] string? value, [ValidatedNotNul /// /// If is an empty . [DebuggerStepThrough] - public static Guid NotEmpty(Guid value, [ValidatedNotNull] string paramName) + public static Guid NotEmpty(Guid value, [NotNull, ValidatedNotNull] string paramName) { string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); @@ -181,8 +191,8 @@ public static Guid NotEmpty(Guid value, [ValidatedNotNull] string paramName) /// /// If is an empty . [DebuggerStepThrough] - public static Guid NotEmpty(Guid value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + public static Guid NotEmpty(Guid value, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) { (string validParamName, string validCustomMessage) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), @@ -213,6 +223,7 @@ public static Guid NotEmpty(Guid value, [ValidatedNotNull] string paramName, /// When is not within /// [DebuggerStepThrough] + [return: NotNull] public static TEnumType ValidEnumerationMember(TEnumType value, string paramName) where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName); @@ -229,6 +240,7 @@ public static TEnumType ValidEnumerationMember(TEnumType value, strin /// When is not within /// [DebuggerStepThrough] + [return: NotNull] public static TEnumType ValidEnumerationMember(TEnumType value, string paramName, string customMessage) where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName, customMessage); @@ -251,8 +263,9 @@ public static TEnumType ValidEnumerationMember(TEnumType value, strin /// If is not less than /// [DebuggerStepThrough] - public static TComparable LessThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + public static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName); @@ -271,9 +284,10 @@ public static TComparable LessThan([ValidatedNotNull] TComparable? /// If is not less than /// [DebuggerStepThrough] - public static TComparable LessThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + public static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName, customMessage); /// @@ -291,8 +305,9 @@ public static TComparable LessThan([ValidatedNotNull] TComparable? /// If is not less than or equal to /// [DebuggerStepThrough] - public static TComparable LessThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + public static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName); @@ -311,9 +326,10 @@ public static TComparable LessThanOrEqualTo([ValidatedNotNull] TCom /// If is not less than or equal to /// [DebuggerStepThrough] - public static TComparable LessThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + [return: NotNull] + public static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName, customMessage); @@ -331,8 +347,9 @@ public static TComparable LessThanOrEqualTo([ValidatedNotNull] TCom /// If is not greater than /// [DebuggerStepThrough] - public static TComparable GreaterThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + public static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName); @@ -351,9 +368,10 @@ public static TComparable GreaterThan([ValidatedNotNull] TComparabl /// If is not greater than /// [DebuggerStepThrough] - public static TComparable GreaterThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + public static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName, customMessage); /// @@ -371,8 +389,9 @@ public static TComparable GreaterThan([ValidatedNotNull] TComparabl /// If is not greater than or equal to /// [DebuggerStepThrough] - public static TComparable GreaterThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + public static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName); @@ -392,9 +411,10 @@ public static TComparable GreaterThanOrEqualTo([ValidatedNotNull] T /// If is not greater than or equal to /// [DebuggerStepThrough] - public static TComparable GreaterThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + public static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName, customMessage); /// @@ -415,12 +435,13 @@ public static TComparable GreaterThanOrEqualTo([ValidatedNotNull] T /// ] /// [DebuggerStepThrough] + [return: NotNull] public static TComparable Between( - [ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? fromInclusive, - [ValidatedNotNull] TComparable? toInclusive, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? fromInclusive, + [NotNull, ValidatedNotNull] TComparable? toInclusive, + [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName, customMessage); #endregion @@ -445,8 +466,9 @@ public static TComparable Between( /// If is not valid as described by the Luhn algorithm. /// [DebuggerStepThrough] - public static string ValidLuhnChecksum([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) + [return: NotNull] + public static string ValidLuhnChecksum([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) { (string validParamName, string validCustomMessage) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), @@ -488,7 +510,7 @@ private static int[] ToDigitsArray(string notNullValue) /// When any parameter is /// If is not a valid Base64 String. [DebuggerStepThrough] - public static string ValidBase64([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName) + public static string ValidBase64([NotNull, ValidatedNotNull] string? value, [NotNull, ValidatedNotNull] string paramName) { string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); @@ -509,8 +531,8 @@ public static string ValidBase64([ValidatedNotNull] string? value, [ValidatedNot /// When any parameter is /// If is not a valid Base64 String. [DebuggerStepThrough] - public static string ValidBase64([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + public static string ValidBase64([NotNull, ValidatedNotNull] string? value, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) { (string validParamName, string validCustomMessage) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), @@ -543,8 +565,8 @@ private static bool IsBase64String(string base64) /// Description for the custom precondition. [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use CompliesWith(T?, Func, string, string), or DoesNotComplyWith(T?, Func, string, string) instead.", error: false)] [DebuggerStepThrough] - public static void CompliesWith(bool precondition, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string preconditionDescription) + public static void CompliesWith(bool precondition, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string preconditionDescription) { (string validParamName, string validPreconditionDescription) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), @@ -567,10 +589,10 @@ public static void CompliesWith(bool precondition, [ValidatedNotNull] string par /// [DebuggerStepThrough] public static TNullable CompliesWith( - [ValidatedNotNull] TNullable? value, - [ValidatedNotNull] Func validator, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string preconditionDescription) + [NotNull, ValidatedNotNull] TNullable? value, + [NotNull, ValidatedNotNull] Func validator, + [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string preconditionDescription) where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, true); @@ -585,20 +607,20 @@ public static TNullable CompliesWith( /// [DebuggerStepThrough] public static TNullable DoesNotComplyWith( - [ValidatedNotNull] TNullable? value, - [ValidatedNotNull] Func validator, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string preconditionDescription) + [NotNull, ValidatedNotNull] TNullable? value, + [NotNull, ValidatedNotNull] Func validator, + [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string preconditionDescription) where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, false); + [return: NotNull] private static TNullable CompliesWithExpected( - [ValidatedNotNull] TNullable? value, - [ValidatedNotNull] Func validator, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string preconditionDescription, - bool expected) - where TNullable : class + [NotNull, ValidatedNotNull] TNullable? value, + [NotNull, ValidatedNotNull] Func validator, + [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string preconditionDescription, + bool expected) where TNullable : class { TNullable notNullValue = value.ValueOrThrowIfNull(nameof(value)); Func notNullValidator = validator.ValueOrThrowIfNull(nameof(validator)); diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index c39ab6a..0c1898d 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -1,12 +1,10 @@ -using Triplex.Validations.Exceptions; -using Triplex.Validations.Utilities; - -namespace Triplex.Validations.ArgumentsHelpers; +namespace Triplex.Validations.ArgumentsHelpers; #pragma warning disable CA1303 // Do not pass literals as localized parameters internal static class Extensions { - internal static T ValueOrThrowIfNull([ValidatedNotNull] this T? value, string paramName) + [return: NotNull] + internal static T ValueOrThrowIfNull([NotNull, ValidatedNotNull] this T? value, string paramName) { if (value is not null) { @@ -16,7 +14,8 @@ internal static T ValueOrThrowIfNull([ValidatedNotNull] this T? value, string throw new ArgumentNullException(paramName); } - internal static T ValueOrThrowIfNull([ValidatedNotNull] this T? value, string paramName, + [return: NotNull] + internal static T ValueOrThrowIfNull([NotNull, ValidatedNotNull] this T? value, string paramName, string customMessage) { if (value is not null) @@ -27,9 +26,11 @@ internal static T ValueOrThrowIfNull([ValidatedNotNull] this T? value, string throw new ArgumentNullException(paramName, customMessage); } + [return: NotNull] internal static string ValueOrThrowIfZeroLength(this string value, string paramName) => ValueOrThrowIfZeroLength(value, paramName, "Can not be empty (zero length)."); + [return: NotNull] internal static string ValueOrThrowIfZeroLength(this string value, string paramName, string customMessage) { if (value.Length is not 0) @@ -40,9 +41,11 @@ internal static string ValueOrThrowIfZeroLength(this string value, string paramN throw new ArgumentOutOfRangeException(paramName, value.Length, customMessage); } + [return: NotNull] internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string paramName) => ValueOrThrowIfWhiteSpaceOnly(value, paramName, "Can not be white-space only."); + [return: NotNull] internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string paramName, string customMessage) { if (value.Any(ch => ch.IsNotWhiteSpace())) @@ -53,22 +56,27 @@ internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string pa throw new ArgumentFormatException(paramName: paramName, message: customMessage); } - internal static string ValueOrThrowIfNullOrZeroLength([ValidatedNotNull] this string? value, string paramName) + [return: NotNull] + internal static string ValueOrThrowIfNullOrZeroLength([NotNull, ValidatedNotNull] this string? value, + string paramName) => ValueOrThrowIfNull(value, paramName) .ValueOrThrowIfZeroLength(paramName); - internal static string ValueOrThrowIfNullOrZeroLength([ValidatedNotNull] this string? value, string paramName, - string customMessage) + [return: NotNull] + internal static string ValueOrThrowIfNullOrZeroLength([NotNull, ValidatedNotNull] this string? value, + string paramName, string customMessage) => ValueOrThrowIfNull(value, paramName, customMessage) .ValueOrThrowIfZeroLength(paramName, customMessage); - internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([ValidatedNotNull] this string? value, + [return: NotNull] + internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([NotNull, ValidatedNotNull] this string? value, string paramName) => ValueOrThrowIfNull(value, paramName) .ValueOrThrowIfZeroLength(paramName) .ValueOrThrowIfWhiteSpaceOnly(paramName); - internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([ValidatedNotNull] this string? value, + [return: NotNull] + internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([NotNull, ValidatedNotNull] this string? value, string paramName, string customMessage) => ValueOrThrowIfNull(value, paramName, customMessage) .ValueOrThrowIfZeroLength(paramName, customMessage) @@ -100,8 +108,9 @@ internal static TEnumType ValueOrThrowIfNotDefined(this TEnumType val throw new ArgumentOutOfRangeException(paramName, value, finalMessage); } - internal static TType[] ValueOrThrowIfNullOrWithLessThanElements([ValidatedNotNull] this TType[]? value, - int minimumElements, string paramName) + [return: NotNull] + internal static TType[] ValueOrThrowIfNullOrWithLessThanElements( + [NotNull, ValidatedNotNull] this TType[]? value, int minimumElements, string paramName) { OutOfRangeChecks.GreaterThanOrEqualTo(ValueOrThrowIfNull(value, paramName).Length, minimumElements, paramName); diff --git a/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs b/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs index 9f0f9eb..57e9a63 100644 --- a/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs +++ b/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs @@ -1,34 +1,40 @@ -using Triplex.Validations.Utilities; - -namespace Triplex.Validations.ArgumentsHelpers; +namespace Triplex.Validations.ArgumentsHelpers; internal static class NullAndEmptyChecks { - internal static TParamType NotNull([ValidatedNotNull] TParamType? value, - [ValidatedNotNull] string paramName) where TParamType : class + [return: NotNull] + internal static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, + [NotNull, ValidatedNotNull] string paramName) where TParamType : class => value.ValueOrThrowIfNull(paramName.ValueOrThrowIfNull(nameof(paramName))); - internal static TParamType NotNull([ValidatedNotNull] TParamType? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) where TParamType : class - => value.ValueOrThrowIfNull(paramName.ValueOrThrowIfNull(nameof(paramName)), + [return: NotNull] + internal static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + where TParamType : class + => value.ValueOrThrowIfNull(paramName.ValueOrThrowIfNull(nameof(paramName)), customMessage.ValueOrThrowIfNull(nameof(customMessage))); - internal static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName) + [return: NotNull] + internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName) => NotNullOrEmpty(value, paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName))) .ValueOrThrowIfWhiteSpaceOnly(paramName); - internal static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) + [return: NotNull] + internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) => NotNullOrEmpty(value, paramName, customMessage) .ValueOrThrowIfWhiteSpaceOnly(paramName, customMessage); - internal static string NotNullOrEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName) + [return: NotNull] + internal static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName) => value.ValueOrThrowIfNullOrZeroLength( paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName))); - internal static string NotNullOrEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + [return: NotNull] + internal static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, + [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) => value.ValueOrThrowIfNullOrZeroLength( paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(customMessage))); diff --git a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs index 957a6c2..28c1c4f 100644 --- a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs +++ b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs @@ -1,11 +1,10 @@ -using Triplex.Validations.Utilities; - -namespace Triplex.Validations.ArgumentsHelpers; +namespace Triplex.Validations.ArgumentsHelpers; internal static class OutOfRangeChecks { - internal static TComparable LessThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + internal static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxExclusiveOnly( @@ -13,9 +12,10 @@ internal static TComparable LessThan([ValidatedNotNull] TComparable return CheckBoundaries(value, range, paramName, null); } - internal static TComparable LessThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + internal static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxExclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -23,8 +23,9 @@ internal static TComparable LessThan([ValidatedNotNull] TComparable return CheckBoundaries(value, range, paramName, customMessage.ValueOrThrowIfNull(nameof(customMessage))); } - internal static TComparable LessThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + internal static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxInclusiveOnly( @@ -33,9 +34,10 @@ internal static TComparable LessThanOrEqualTo([ValidatedNotNull] TC return CheckBoundaries(value, range, paramName, null); } - internal static TComparable LessThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + [return: NotNull] + internal static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxInclusiveOnly( @@ -44,8 +46,9 @@ internal static TComparable LessThanOrEqualTo([ValidatedNotNull] TC return CheckBoundaries(value, range, paramName, customMessage.ValueOrThrowIfNull(nameof(customMessage))); } - internal static TComparable GreaterThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + internal static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinExclusiveOnly( @@ -54,9 +57,10 @@ internal static TComparable GreaterThan([ValidatedNotNull] TCompara return CheckBoundaries(value, range, paramName, null); } - internal static TComparable GreaterThan([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + internal static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinExclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -64,8 +68,9 @@ internal static TComparable GreaterThan([ValidatedNotNull] TCompara return CheckBoundaries(value, range, paramName, customMessage.ValueOrThrowIfNull(nameof(customMessage))); } - internal static TComparable GreaterThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName) + [return: NotNull] + internal static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( @@ -74,9 +79,10 @@ internal static TComparable GreaterThanOrEqualTo([ValidatedNotNull] return CheckBoundaries(value, range, paramName, null); } - internal static TComparable GreaterThanOrEqualTo([ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? other, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + internal static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -84,12 +90,13 @@ internal static TComparable GreaterThanOrEqualTo([ValidatedNotNull] return CheckBoundaries(value, range, paramName, customMessage.ValueOrThrowIfNull(nameof(customMessage))); } + [return: NotNull] internal static TComparable Between( - [ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? fromInclusive, - [ValidatedNotNull] TComparable? toInclusive, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [NotNull, ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? fromInclusive, + [NotNull, ValidatedNotNull] TComparable? toInclusive, + [NotNull, ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable { ComparableRange range = new( SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull(nameof(fromInclusive))), @@ -102,10 +109,11 @@ internal static TComparable Between( } + [return: NotNull] private static TComparable CheckBoundaries( - [ValidatedNotNull] TComparable? value, + [NotNull, ValidatedNotNull] TComparable? value, ComparableRange range, - [ValidatedNotNull] string paramName, + [NotNull, ValidatedNotNull] string paramName, string? customMessage) where TComparable : IComparable { return range.IsWithin(value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(paramName)), diff --git a/src/Validations/GlobalUsings.cs b/src/Validations/GlobalUsings.cs index 9baef4a..d5feb8a 100644 --- a/src/Validations/GlobalUsings.cs +++ b/src/Validations/GlobalUsings.cs @@ -1,4 +1,8 @@ global using System; global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; global using System.Linq; global using System.Text.RegularExpressions; +global using Triplex.Validations.ArgumentsHelpers; +global using Triplex.Validations.Exceptions; +global using Triplex.Validations.Utilities; diff --git a/src/Validations/Properties/AssemblyInfo.cs b/src/Validations/Properties/AssemblyInfo.cs index 0011bc6..dcd1cc2 100644 --- a/src/Validations/Properties/AssemblyInfo.cs +++ b/src/Validations/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -[assembly: System.CLSCompliant(true)] +[assembly: CLSCompliant(true)] [assembly: System.Resources.NeutralResourcesLanguage("en")] [assembly: System.Runtime.InteropServices.ComVisible(false)] diff --git a/src/Validations/State.cs b/src/Validations/State.cs index bec0454..8a66b6a 100644 --- a/src/Validations/State.cs +++ b/src/Validations/State.cs @@ -1,5 +1,3 @@ -using Triplex.Validations.Utilities; - namespace Triplex.Validations; /// @@ -17,7 +15,7 @@ public static class State /// Can not be /// [DebuggerStepThrough] - public static void IsTrue(bool stateQuery, [ValidatedNotNull] string message) + public static void IsTrue(bool stateQuery, [NotNull, ValidatedNotNull] string message) { Arguments.NotNull(message, nameof(message)); @@ -34,7 +32,7 @@ public static void IsTrue(bool stateQuery, [ValidatedNotNull] string message) /// Can not be /// [DebuggerStepThrough] - public static void IsFalse(bool stateQuery, [ValidatedNotNull] string message) + public static void IsFalse(bool stateQuery, [NotNull, ValidatedNotNull] string message) { Arguments.NotNull(message, nameof(message)); @@ -55,7 +53,7 @@ public static void IsFalse(bool stateQuery, [ValidatedNotNull] string message) /// Expected to be true /// Can not be [DebuggerStepThrough] - public static void StillHolds(bool invariant, [ValidatedNotNull] string message) + public static void StillHolds(bool invariant, [NotNull, ValidatedNotNull] string message) { Arguments.NotNull(message, nameof(message)); @@ -71,7 +69,7 @@ public static void StillHolds(bool invariant, [ValidatedNotNull] string message) /// Expected to be false /// Can not be [DebuggerStepThrough] - public static void StillNotHolds(bool invariant, [ValidatedNotNull] string message) + public static void StillNotHolds(bool invariant, [NotNull, ValidatedNotNull] string message) { Arguments.NotNull(message, nameof(message)); diff --git a/src/Validations/Validations.csproj b/src/Validations/Validations.csproj index 1477db2..9df43b5 100644 --- a/src/Validations/Validations.csproj +++ b/src/Validations/Validations.csproj @@ -2,11 +2,11 @@ net6.0 - 10.0 + latest disable false Triplex.Validations - Triplex.Validations + $(RootNamespace) true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml PackageReference @@ -23,7 +23,7 @@ true Preconditions;Postconditions;Invariants;DDD;Domain Driven Design icon.png - 3.0.5-alpha + 3.1.0 enable true latest @@ -41,6 +41,8 @@ Features - Introduce implicit null check for NotNullEmptyOrWhiteSpaceOnly (now NotEmptyOrWhiteSpaceOnly) and NotNullOrEmpty (now NotEmpty). - T CompliesWith(T?, Func<T, bool>, string, string) - T DoesNotComplyWith(T?, Func<T, bool>, string, string) + - Add System.Diagnostics.CodeAnalysis.NotNull attribute to suitable parameters and method's return. + - Remove small quality gate status badge and leave only the big one (#16) Improvements - Migrate to .NET 6 From 9d57d180c1eaf15470b8524f8e835f7ae7403bcb Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Thu, 31 Mar 2022 23:08:51 -0400 Subject: [PATCH 02/25] #12 #16 Re arrange package properties and add snupkg symbols format. --- src/Validations/Validations.csproj | 93 +++++++++++-------- .../Validations.Tests.csproj | 3 +- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/Validations/Validations.csproj b/src/Validations/Validations.csproj index 9df43b5..6468aec 100644 --- a/src/Validations/Validations.csproj +++ b/src/Validations/Validations.csproj @@ -1,34 +1,45 @@  + + 3.1.0 + + net6.0 + latest - - net6.0 - latest - disable - false - Triplex.Validations - $(RootNamespace) - true - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - PackageReference - Lorenzo Solano Martinez - Validation library inspired by the concepts of Secure by Design, by Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano (MEAP 2019 Manning Publications). - LICENSE - $([System.DateTime]::Now.Year.ToString("####")) - 2019 - -$(Year) - (C) Lorenzo Solano Martinez (https://lorenzosolano.com/) $(CopyrightStartYear)$(CopyrightEndYear). All Rights Reserved. - git - https://github.com/lsolano/triplex - true - true - Preconditions;Postconditions;Invariants;DDD;Domain Driven Design - icon.png - 3.1.0 - enable - true - latest - AllEnabledByDefault - + Triplex.Validations + $(RootNamespace) + true + false + $(RootNamespace) + + disable + PackageReference + + true + snupkg + true + true + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + + enable + true + latest + AllEnabledByDefault + + Lorenzo Solano Martinez + Validation library inspired by the concepts of Secure by Design, by Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano (MEAP 2019 Manning Publications). + LICENSE + $([System.DateTime]::Now.Year.ToString("####")) + 2019 + -$(Year) + (C) Lorenzo Solano Martinez (https://lorenzosolano.com/) $(CopyrightStartYear)$(CopyrightEndYear). All Rights Reserved. + git + https://github.com/lsolano/triplex + true + true + Preconditions;Postconditions;Invariants;DDD;Domain Driven Design + icon.png + + Breaking Changes - Obsolete members from Triplex.Validations.Arguments: - string NotNullEmptyOrWhiteSpaceOnly(in string?, in string) @@ -48,15 +59,19 @@ Improvements - Migrate to .NET 6 - Increase test coverage to 100 percent - Analyse project using sonarcloud.io (see https://sonarcloud.io/project/overview?id=lsolano_triplex) - - - - - - True - - - - + + + + + + True + + + + + + + true + diff --git a/tests/unit/Validations.Tests/Validations.Tests.csproj b/tests/unit/Validations.Tests/Validations.Tests.csproj index 7745da7..864fe11 100644 --- a/tests/unit/Validations.Tests/Validations.Tests.csproj +++ b/tests/unit/Validations.Tests/Validations.Tests.csproj @@ -1,9 +1,10 @@  net6.0 - 10.0 + latest disable true + false Triplex.Validations.Tests Triplex.Validations.Tests bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml From 65ebafc13428db556508ac9db2456f6bdbb07207 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Wed, 11 May 2022 22:17:02 -0400 Subject: [PATCH 03/25] =?UTF-8?q?#30=20chore-=F0=9F=98=92:=20Declare=20new?= =?UTF-8?q?=20desing=20on=20README.md=20and=20version=20bump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Declare new desing on README.md 2. Version bump (3.x to 4.0.0-alpha) 3. Add net6.0 target framework. --- README.md | 161 ++++++++++++++---- docs/imgs/Class_With_Nullable_Warning_01.png | Bin 0 -> 41431 bytes docs/imgs/Class_With_Nullable_Warning_02.png | Bin 0 -> 71364 bytes docs/imgs/Class_With_Nullable_Warning_03.png | Bin 0 -> 13445 bytes docs/imgs/Removing_Nullable_Warning_01.png | Bin 0 -> 18420 bytes docs/imgs/Removing_Nullable_Warning_02.png | Bin 0 -> 14553 bytes src/Validations/Validations.csproj | 15 +- .../Validations.Tests.csproj | 2 +- 8 files changed, 137 insertions(+), 41 deletions(-) create mode 100644 docs/imgs/Class_With_Nullable_Warning_01.png create mode 100644 docs/imgs/Class_With_Nullable_Warning_02.png create mode 100644 docs/imgs/Class_With_Nullable_Warning_03.png create mode 100644 docs/imgs/Removing_Nullable_Warning_01.png create mode 100644 docs/imgs/Removing_Nullable_Warning_02.png diff --git a/README.md b/README.md index 169bc42..d71b4ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Triplex # +[![NuGet downloads](https://img.shields.io/nuget/dt/Triplex.Validations?color=blue&label=nuget-downloads&logo=nuget)](https://www.nuget.org/packages/Triplex.Validations/) + Validation library inspired by the concepts of ***Secure by Design***, by Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano (MEAP 2019 Manning Publications). (Preconditions, Postconditions, Invariants & Proto-Primitives at [https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts](https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts "C# Code Contracts")) ## Project's Code Health ## @@ -18,57 +20,148 @@ Sometimes we need to check internal state before or after certain operation to e --- +## Motivations ## +* Do reject bad input as soon as possible specially `null`s. +* Control data coming from public interfaces. +* Help API creators to enforce code contracts. +* Remove compiler warnings around `null` checks. + +## Design ## +For arguments, all checks imply a `null` check first. So if you see something like `_value = Arguments.OrException(value);` +means "return the exact same value or throw an exception if it is `null`". By the same logic, `Arguments.LessThan(value, other);` +means "return the exact same value if is less than `other` otherwise throw an exception, but if some argument value is +`null` throw `ArgumentNullException`. + + ## Examples +### Removing Compiler Warnings ### +1. Simle `Email` wrapper class with Warning + +![Email With Warning 1 of 2](docs/imgs/Class_With_Nullable_Warning_01.png) + +*(Fig. 01 - `Email` class showing warning)* + +![Email With Warning 2 of 2](docs/imgs/Class_With_Nullable_Warning_02.png) + +*(Fig. 02 - `Email` class showing expanded warning)* + +2. Same issue even when we declare argument as "non-nullable" `string` instead of `string?` + +![Email With Warning 1 of 2](docs/imgs/Class_With_Nullable_Warning_03.png) + +*(Fig. 03 - `Email` class showing even with non-null reference type)* + +3. Removing warning using `Arguments.OrException(value)` + +![Email With Warning 1 of 2](docs/imgs/Removing_Nullable_Warning_01.png) + +*(Fig. 04 - Issue solved using block style)* + +![Email With Warning 1 of 2](docs/imgs/Removing_Nullable_Warning_02.png) + +*(Fig. 05 - Issue solved using expression-method style)* + ### Arguments ### - using Triplex.Validations; +```csharp +using Triplex.Validations; + +public sealed class Email +{ + private readonly string _value; - public sealed class Email + public Email(string value) { - private readonly string _value; - - public Email(string value) { - Arguments.NotNull(value); - Arguments.LengthBetween(value, 12, 60); - Arguments.MatchesPattern(value.ToLowerInvariant(), "^[a-zA-Z.]+@toboso.com$"); - - _value = value.ToLowerInvariant(); - } + //a) Simple form (param name for ArgumentException is automatically taken from 1st parameter) + Arguments.OrException(value); + + //b) With custom message (param name for ArgumentException is automatically taken from 1st parameter) + Arguments.OrExceptionWithMessage(value, "Invalid email"); + + // Use value ensuring that it is not null now + _value = value.ToLowerInvariant(); + + // or use returned value from argument check + _value = Arguments.OrException(value); //return same input (literally same reference) } -All methods has three forms: + // Collapse Check-Then-Assign pattern using returned value from checks. + public Email(string username, string domain) + => _value = $"{Arguments.OrException(username)}@{Arguments.OrException(domain)}"; +} +``` -1. Data only, no argument name, nor custom message. Like `Arguments.NotNull(someArg);` -2. Data, argument name, but no custom message. Like `Arguments.NotNull(someArg, nameof(someArg));` -3. Data, argument name, and custom message. Like `Arguments.NotNull(someArg, nameof(someArg), "Your custom exception message goes here");` +All checks has two forms: +1. Data and ***inferred*** argument name. Like `Arguments.OrException(someArg);` +2. Data, custom message, and ***inferred*** argument name. Like `Arguments.OrExceptionWithMessage(someArg, "Your custom exception message goes here");` + +If you need to use a different argument name, both forms support a last optional parameter: + +1. `Arguments.OrException(someArg, "manualArgumentName");` +2. `Arguments.OrExceptionWithMessage(someArg, "Custom ex. message", "manualArgumentName");` + +**It is not required to use `nameof(someArg)`** the following forms are equivalent +1. `Arguments.OrException(someArg);` ≡ `Arguments.OrException(someArg, nameof(someArg));` +2. `Arguments.OrExceptionWithMessage(someArg, "Custom ex. message");` ≡ `Arguments.OrExceptionWithMessage(someArg, "Custom ex. message", nameof(someArg));` ### Object's state ### - using Triplex.Validations; +```csharp +using Triplex.Validations; - public sealed class Point - { - //private Point stuff here... +public sealed class MyList +{ + public T Remove(int index) { + //0. Pre-condition + State.IsTrue(_size > 0, "Unable to remove elements when empty."); - //Useful constructor(s) here - //public Point(...) { } + //1. Contract + Arguments.Between(index, 0, _size - 1, "Index out of bounds."); - public void MoveBy(int x, int y, int z) { - //1. Any relevant arguments validations here using Arguments util - Arguments.All(x, y, z).NotEqualTo(default(int)); + //2. Do your stuff + T removed = DoRemoveElementAt(index); + + //3. After removing size must be zero or greater + State.StillHolds(_size >= 0, "Internal error, size must not be negative."); - //2. Do your stuff - ComplexMoveLogic(x, y, z); - - //3. Afther moving, could not be outside map's boundaries - State.StillHolds(_theMap.IsStrictlyWithin(this)); // throws if false - } + return removed; } +} +``` +State checks semantics: -All methods has two forms: - -1. Data only, no custom message. Like `State.StillHolds(somePostActionValidation);` -2. Data, argument name, but no custom message. Like `State.StillHolds(somePostActionValidation, "Your custom exception message goes here");` +1. Preconditions: `Is*(expression)` where `expression` depends on the actual check. Could be `boolean` or just a value (like in `State.IsNotNull(value)`). +2. Postconditions: `Still*(expression)` where expression depends on the actual check. Could be `boolean` or just a value. There is no argument name, because `State` is used to validate objects internal state, as a hole, or invariants. +## Contribute We like to get help from everybody, if you want to contribute to this tool, found some issue or just want a feature request please read, first, the [Contributing guide](./docs/CONTRIBUTING.md). + +### Where to start? +1. Fork + Clone this repo +2. Build and Run Tests locally + 1. Build only `dotnet build` + 2. Full clean, build and test `dotnet clean && dotnet test /p:CollectCoverage=true` +3. Incremental build and test (regular dev workflow) + 1. `dotnet test /p:CollectCoverage=true` + +At the end of a test run you should see something like +```sh +Calculating coverage result... + Generating report '~/tests/unit/Validations.Tests/coverage.net6.0.json' + ++---------------------+------+--------+--------+ +| Module | Line | Branch | Method | ++---------------------+------+--------+--------+ +| Triplex.Validations | 100% | 100% | 100% | ++---------------------+------+--------+--------+ + ++---------+------+--------+--------+ +| | Line | Branch | Method | ++---------+------+--------+--------+ +| Total | 100% | 100% | 100% | ++---------+------+--------+--------+ +| Average | 100% | 100% | 100% | ++---------+------+--------+--------+ +``` +You should maintain a 100% of test coverage, please do not submit Pull Requests if you are unable to do that. If you need help, or does not know how to achieve the expected coverage percentage please contact maintainers for advice. diff --git a/docs/imgs/Class_With_Nullable_Warning_01.png b/docs/imgs/Class_With_Nullable_Warning_01.png new file mode 100644 index 0000000000000000000000000000000000000000..8a2390d19a109398fb2186939e171cc188115029 GIT binary patch literal 41431 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 R;h}(sf~<;6v9#gW{{dD~Kym;8 literal 0 HcmV?d00001 diff --git a/docs/imgs/Class_With_Nullable_Warning_02.png b/docs/imgs/Class_With_Nullable_Warning_02.png new file mode 100644 index 0000000000000000000000000000000000000000..475c22421f9290221bb96be4f1620474f19b9d16 GIT binary patch literal 71364 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 literal 0 HcmV?d00001 diff --git a/docs/imgs/Removing_Nullable_Warning_01.png b/docs/imgs/Removing_Nullable_Warning_01.png new file mode 100644 index 0000000000000000000000000000000000000000..376e84301aa0be8662bb7f860ce3d539fd8e3947 GIT binary patch literal 18420 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 OF?hQAxvXVcG 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 - 3.1.0 + 4.0.0 + alpha - net6.0 + net5.0;net6.0 latest Triplex.Validations @@ -25,15 +26,17 @@ latest AllEnabledByDefault - Lorenzo Solano Martinez - Validation library inspired by the concepts of Secure by Design, by Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano (MEAP 2019 Manning Publications). + git + https://github.com/lsolano/triplex + LICENSE $([System.DateTime]::Now.Year.ToString("####")) 2019 -$(Year) (C) Lorenzo Solano Martinez (https://lorenzosolano.com/) $(CopyrightStartYear)$(CopyrightEndYear). All Rights Reserved. - git - https://github.com/lsolano/triplex + Lorenzo Solano Martinez + Validation library inspired by the concepts of Secure by Design, by Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano (MEAP 2019 Manning Publications). + true true Preconditions;Postconditions;Invariants;DDD;Domain Driven Design diff --git a/tests/unit/Validations.Tests/Validations.Tests.csproj b/tests/unit/Validations.Tests/Validations.Tests.csproj index 864fe11..617be64 100644 --- a/tests/unit/Validations.Tests/Validations.Tests.csproj +++ b/tests/unit/Validations.Tests/Validations.Tests.csproj @@ -1,6 +1,6 @@  - net6.0 + net5.0;net6.0 latest disable true From d5de279fad0eb8401e1d02733edffd46771e87cb Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Wed, 11 May 2022 22:23:30 -0400 Subject: [PATCH 04/25] =?UTF-8?q?#30=20feat-=E2=AD=90=EF=B8=8F!:=20Mass=20?= =?UTF-8?q?changes=20renames,=20new=20opts,=20del?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove deprecated methods 2. Rename multiple methods 3. Split methods with optional messages (overloads) in two with more explicit names. BREAKING CHANGE: Remove deprecated methods from Arguments --- .../Algorithms/Checksum/LuhnFormula.cs | 12 +- src/Validations/Arguments.cs | 300 +++++++++--------- .../ArgumentsHelpers/Extensions.cs | 37 ++- .../ArgumentsHelpers/NullAndEmptyChecks.cs | 32 +- .../ArgumentsHelpers/OutOfRangeChecks.cs | 74 +++-- src/Validations/State.cs | 41 ++- .../Utilities/ValidatedNotNullAttribute.cs | 11 - .../LuhnFormulaFacts/IsValidMessageFacts.cs | 5 +- .../ArgumentsFacts/BetweenMessageFacts.cs | 6 +- .../BetweenOrExceptionMessage.cs | 46 +++ .../ArgumentsFacts/Colors.cs | 3 + .../CompliesWithMessageFacts.cs | 64 ++-- .../ArgumentsFacts/GreaterThanMessage.cs | 6 +- .../GreaterThanOrEqualToMessage.cs | 6 +- .../ArgumentsFacts/LessThanMessage.cs | 6 +- .../LessThanOrEqualToMessage.cs | 6 +- .../MemberOfOrExceptionMessage.cs | 40 +++ .../MemberOfOrExceptionWithMessageMessage.cs | 44 +++ .../ArgumentsFacts/NotEmptyGuidMessage.cs | 4 +- .../ArgumentsFacts/NotEmptyMessageFacts.cs | 25 +- ...mptyNorWhiteSpaceOnlyOrExceptionMessage.cs | 75 +++++ ...ptyOrWhiteSpaceOnly_Diadic_MessageFacts.cs | 81 ----- ...tyOrWhiteSpaceOnly_Triadic_MessageFacts.cs | 50 +-- .../NotNullEmptyOrWhiteSpaceOnly_Facts.cs | 214 ++++++------- ...teSpaceOnly_Without_CustomMessage_Facts.cs | 25 +- .../ArgumentsFacts/NotNullMessage.cs | 65 ---- .../NotNullOrEmptyMessageFacts.cs | 164 +++++----- .../ArgumentsFacts/OrExceptionMessage.cs | 78 +++++ .../OrExceptionWithMessageMessage.cs | 79 +++++ .../ArgumentsFacts/ValidBase64MessageFacts.cs | 2 +- .../ValidEnumerationMemberMessage.cs | 72 ----- .../ValidLuhnChecksumMessageFacts.cs | 2 +- .../FluentConstraintsExtensions.cs | 28 ++ .../StateFacts/IsNotNullMessageFacts.cs | 37 +++ 34 files changed, 1002 insertions(+), 738 deletions(-) delete mode 100644 src/Validations/Utilities/ValidatedNotNullAttribute.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/BetweenOrExceptionMessage.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/Colors.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionMessage.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionWithMessageMessage.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyNorWhiteSpaceOnlyOrExceptionMessage.cs delete mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Diadic_MessageFacts.cs delete mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/NotNullMessage.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs create mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionWithMessageMessage.cs delete mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/ValidEnumerationMemberMessage.cs create mode 100644 tests/unit/Validations.Tests/FluentConstraintsExtensions.cs create mode 100644 tests/unit/Validations.Tests/StateFacts/IsNotNullMessageFacts.cs diff --git a/src/Validations/Algorithms/Checksum/LuhnFormula.cs b/src/Validations/Algorithms/Checksum/LuhnFormula.cs index 177af4d..154f4e1 100644 --- a/src/Validations/Algorithms/Checksum/LuhnFormula.cs +++ b/src/Validations/Algorithms/Checksum/LuhnFormula.cs @@ -22,7 +22,7 @@ public static class LuhnFormula /// /// If contains elements not within range [0-9]. /// - public static bool IsValid([NotNull, ValidatedNotNull] int[]? fullDigits) + public static bool IsValid([NotNull] int[]? fullDigits) { int[] validatedDigits = fullDigits.ValueOrThrowIfNullOrWithLessThanElements(MinimumElements, nameof(fullDigits)); @@ -51,7 +51,7 @@ public static bool IsValid([NotNull, ValidatedNotNull] int[]? fullDigits) /// /// If contains characters other than digits. /// - public static bool IsValid([NotNull, ValidatedNotNull] string? fullDigits) + public static bool IsValid([NotNull] string? fullDigits) { string notNullDigits = ValidateDigitsAsString(fullDigits); @@ -61,19 +61,19 @@ public static bool IsValid([NotNull, ValidatedNotNull] string? fullDigits) } [return: NotNull] - private static string ValidateDigitsAsString([NotNull, ValidatedNotNull] string? fullDigits) + private static string ValidateDigitsAsString([NotNull] string? fullDigits) { string notNullDigits = fullDigits.ValueOrThrowIfNullOrZeroLength(nameof(fullDigits)); if (notNullDigits.Length < MinimumElements) { - throw new ArgumentOutOfRangeException(nameof(fullDigits), + throw new ArgumentFormatException(nameof(fullDigits), $"Length must be at least {MinimumElements} elements."); } if (!DigitsRegex.IsMatch(notNullDigits)) { - throw new FormatException("Invalid input, only digits [0-9] are allowed."); + throw new ArgumentFormatException(nameof(fullDigits), "Invalid input, only digits [0-9] are allowed."); } return notNullDigits; @@ -99,7 +99,7 @@ private static bool DoDigitCheck(int[] sanitizedDigits) /// /// If is has less than one digits. /// - public static int GetCheckDigit([NotNull, ValidatedNotNull] int[]? digitsWithoutCheck) + public static int GetCheckDigit([NotNull] int[]? digitsWithoutCheck) { const int minimumElements = 1; diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index 451fe81..7cdaa9b 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -1,11 +1,13 @@ -using Triplex.Validations.Algorithms.Checksum; +using System.Runtime.CompilerServices; +using Triplex.Validations.Algorithms.Checksum; -#pragma warning disable CA1303 // Do not pass literals as localized parameters namespace Triplex.Validations; ///

/// Utility class used to validate arguments. Useful to check constructor and public methods arguments. /// If checks are violated an instance of is thrown. +/// All checks imply an initial Not-Null check for all values checked, +/// so Arguments.OrException(someParam); means "Give 'someParam' value or and exception if it is null." /// public static class Arguments { @@ -20,8 +22,9 @@ public static class Arguments /// If is . [DebuggerStepThrough] [return: NotNull] - public static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, - [NotNull, ValidatedNotNull] string paramName) where TParamType : class + public static TParamType OrException( + [NotNull] TParamType? value, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName); /// @@ -44,8 +47,10 @@ public static TParamType NotNull([NotNull, ValidatedNotNull] TParamT /// If is . [DebuggerStepThrough] [return: NotNull] - public static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + public static TParamType OrExceptionWithMessage( + [NotNull] TParamType? value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName, customMessage); @@ -61,11 +66,11 @@ public static TParamType NotNull([NotNull, ValidatedNotNull] TParamT /// /// If contains only white-space characters /// - [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmptyOrWhiteSpaceOnly(string?, string) instead.", error: false)] [DebuggerStepThrough] [return: NotNull] - public static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName) + public static string NotEmptyNorWhiteSpaceOnlyOrException( + [NotNull] string? value, + [NotNull, CallerArgumentExpression("value")] string paramName = "") => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName); /// @@ -81,25 +86,13 @@ public static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] st /// /// If contains only white-space characters /// - [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmptyOrWhiteSpaceOnly(string?, string, string) instead.", error: false)] [DebuggerStepThrough] [return: NotNull] - public static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) - => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); - - /// - [DebuggerStepThrough] - [return: NotNull] - public static string NotEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName) - => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName); - - /// - [DebuggerStepThrough] - [return: NotNull] - public static string NotEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + //TODO: Refactor tests for NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage + public static string NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( + [NotNull] string? value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); /// @@ -113,11 +106,12 @@ public static string NotEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string /// /// If contains only white-space characters /// - [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmpty(string?, string) instead.", error: false)] [DebuggerStepThrough] [return: NotNull] - public static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName) + //TODO: Refactor tests for NotEmptyOrException + public static string NotEmptyOrException( + [NotNull] string? value, + [NotNull, CallerArgumentExpression("value")] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); /// @@ -129,25 +123,13 @@ public static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, /// /// If any paramete is . /// If length is zero. - [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use NotEmpty(string?, string, string) instead.", error: false)] - [DebuggerStepThrough] - [return: NotNull] - public static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) - => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); - - /// - [DebuggerStepThrough] - [return: NotNull] - public static string NotEmpty([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName) - => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); - - /// [DebuggerStepThrough] [return: NotNull] - public static string NotEmpty([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + //TODO: Refactor tests for NotEmptyOrExceptionWithMessage + public static string NotEmptyOrExceptionWithMessage( + [NotNull] string? value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); #endregion @@ -161,16 +143,13 @@ public static string NotEmpty([NotNull, ValidatedNotNull] string? value, /// /// If is an empty . [DebuggerStepThrough] - public static Guid NotEmpty(Guid value, [NotNull, ValidatedNotNull] string paramName) + //TODO: Refactor tests for NotEmptyOrException + public static Guid NotEmptyOrException(Guid value, + [NotNull, CallerArgumentExpression("value")] string paramName = "") { - string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); + string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); - if (IsEmpty(value)) - { - throw new ArgumentException(validParamName); - } - - return value; + return IsEmpty(value) ? throw new ArgumentException(validParamName) : value; } /// @@ -191,19 +170,18 @@ public static Guid NotEmpty(Guid value, [NotNull, ValidatedNotNull] string param /// /// If is an empty . [DebuggerStepThrough] - public static Guid NotEmpty(Guid value, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) + //TODO: Refactor tests for NotEmptyOrExceptionWithMessage + public static Guid NotEmptyOrExceptionWithMessage(Guid value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") { (string validParamName, string validCustomMessage) = - (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), - customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(customMessage))); - - if (IsEmpty(value)) - { - throw new ArgumentException(paramName: validParamName, message: validCustomMessage); - } + (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), + customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); - return value; + return IsEmpty(value) ? + throw new ArgumentException(paramName: validParamName, message: validCustomMessage) + : value; } private static bool IsEmpty(Guid value) => value == default; @@ -224,7 +202,9 @@ public static Guid NotEmpty(Guid value, [NotNull, ValidatedNotNull] string param /// [DebuggerStepThrough] [return: NotNull] - public static TEnumType ValidEnumerationMember(TEnumType value, string paramName) + //TODO: Refactor tests for MemberOfOrException + public static TEnumType MemberOfOrException(TEnumType value, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName); @@ -241,8 +221,10 @@ public static TEnumType ValidEnumerationMember(TEnumType value, strin /// [DebuggerStepThrough] [return: NotNull] - public static TEnumType ValidEnumerationMember(TEnumType value, string paramName, - string customMessage) where TEnumType : Enum + //TODO: Refactor tests for MemberOfOrExceptionWithMessage + public static TEnumType MemberOfOrExceptionWithMessage(TEnumType value, + string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName, customMessage); #endregion @@ -264,9 +246,11 @@ public static TEnumType ValidEnumerationMember(TEnumType value, strin /// [DebuggerStepThrough] [return: NotNull] - public static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) - where TComparable : IComparable + //TODO: Refactor tests for LessThanOrException + public static TComparable LessThanOrException( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName); /// @@ -285,9 +269,12 @@ public static TComparable LessThan([NotNull, ValidatedNotNull] TCom /// [DebuggerStepThrough] [return: NotNull] - public static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + //TODO: Refactor tests for LessThanOrExceptionWithMessage + public static TComparable LessThanOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName, customMessage); /// @@ -306,9 +293,11 @@ public static TComparable LessThan([NotNull, ValidatedNotNull] TCom /// [DebuggerStepThrough] [return: NotNull] - public static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) - where TComparable : IComparable + //TODO: Refactor tests for LessThanOrEqualToOrException + public static TComparable LessThanOrEqualToOrException( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName); /// @@ -327,10 +316,12 @@ public static TComparable LessThanOrEqualTo([NotNull, ValidatedNotN /// [DebuggerStepThrough] [return: NotNull] - public static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) - where TComparable : IComparable + //TODO: Refactor tests for LessThanOrEqualToOrExceptionWithMessage + public static TComparable LessThanOrEqualToOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName, customMessage); /// @@ -348,9 +339,11 @@ public static TComparable LessThanOrEqualTo([NotNull, ValidatedNotN /// [DebuggerStepThrough] [return: NotNull] - public static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) - where TComparable : IComparable + //TODO: Refactor tests for GreaterThanOrException + public static TComparable GreaterThanOrException( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName); /// @@ -369,9 +362,12 @@ public static TComparable GreaterThan([NotNull, ValidatedNotNull] T /// [DebuggerStepThrough] [return: NotNull] - public static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + //TODO: Refactor tests for GreaterThanOrExceptionWithMessage + public static TComparable GreaterThanOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName, customMessage); /// @@ -390,8 +386,11 @@ public static TComparable GreaterThan([NotNull, ValidatedNotNull] T /// [DebuggerStepThrough] [return: NotNull] - public static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) + //TODO: Refactor tests for GreaterThanOrEqualToOrException + public static TComparable GreaterThanOrEqualToOrException( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName); @@ -412,11 +411,39 @@ public static TComparable GreaterThanOrEqualTo([NotNull, ValidatedN /// [DebuggerStepThrough] [return: NotNull] - public static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + //TODO: Refactor tests for GreaterThanOrEqualToOrExceptionWithMessage + public static TComparable GreaterThanOrEqualToOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable? other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName, customMessage); + /// + ///

Checks that the given is between [, + /// ] (closed range).

+ ///

This method relies on the contract.

+ ///
+ /// Value to check, can not be + /// Lower bound, can not be + /// Upper bound, can not be + /// Parameter name, can not be + /// + /// + /// When any parameter is + /// + /// If is not between [, + /// ] + /// + [DebuggerStepThrough] + [return: NotNull] + public static TComparable BetweenOrException( + [NotNull] TComparable? value, + [NotNull] TComparable? fromInclusive, + [NotNull] TComparable? toInclusive, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName); + /// ///

Checks that the given is between [, /// ] (closed range).

@@ -436,12 +463,13 @@ public static TComparable GreaterThanOrEqualTo([NotNull, ValidatedN /// [DebuggerStepThrough] [return: NotNull] - public static TComparable Between( - [NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? fromInclusive, - [NotNull, ValidatedNotNull] TComparable? toInclusive, - [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + //TODO: Refactor tests for BetweenOrExceptionWithMessage + public static TComparable BetweenOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable? fromInclusive, + [NotNull] TComparable? toInclusive, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName, customMessage); #endregion @@ -467,12 +495,13 @@ public static TComparable Between( /// [DebuggerStepThrough] [return: NotNull] - public static string ValidLuhnChecksum([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + //TODO: Refactor tests for ValidLuhnChecksum + public static string ValidLuhnChecksum([NotNull] string? value, [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") { (string validParamName, string validCustomMessage) = - (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), - customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(customMessage))); + (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), + customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); string notNullValue = NullAndEmptyChecks.NotNull(value, validParamName); @@ -510,10 +539,11 @@ private static int[] ToDigitsArray(string notNullValue) /// When any parameter is /// If is not a valid Base64 String. [DebuggerStepThrough] - public static string ValidBase64([NotNull, ValidatedNotNull] string? value, [NotNull, ValidatedNotNull] string paramName) + //TODO: Refactor tests for ValidBase64 + public static string ValidBase64([NotNull] string? value, [NotNull, CallerArgumentExpression("value")] string paramName = "") { string validParamName = - paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); + paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); string notNullValue = value.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(validParamName); @@ -531,12 +561,13 @@ public static string ValidBase64([NotNull, ValidatedNotNull] string? value, [Not /// When any parameter is /// If is not a valid Base64 String. [DebuggerStepThrough] - public static string ValidBase64([NotNull, ValidatedNotNull] string? value, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) + //TODO: Refactor tests for ValidBase64 + public static string ValidBase64([NotNull] string? value, [NotNull] string customMessage, + [NotNull, CallerArgumentExpression("value")] string paramName = "") { (string validParamName, string validCustomMessage) = - (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), - customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(customMessage))); + (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), + customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); string notNullValue = value.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(validParamName, validCustomMessage); @@ -556,28 +587,6 @@ private static bool IsBase64String(string base64) #region General Purpose Checks - /// - /// Succeeds if (value or expression) is . - /// - /// Boolean expression that must be for the argument check to - /// succeed. - /// Parameter name, from caller's context. - /// Description for the custom precondition. - [Obsolete("Please stop using this method, it will be removed on mayor release 4.x. Use CompliesWith(T?, Func, string, string), or DoesNotComplyWith(T?, Func, string, string) instead.", error: false)] - [DebuggerStepThrough] - public static void CompliesWith(bool precondition, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string preconditionDescription) - { - (string validParamName, string validPreconditionDescription) = - (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), - preconditionDescription.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(preconditionDescription))); - - if (!precondition) - { - throw new ArgumentException(paramName: validParamName, message: validPreconditionDescription); - } - } - /// /// Checks the given value for and then if the its complies with the validator function. /// @@ -588,11 +597,12 @@ public static void CompliesWith(bool precondition, [NotNull, ValidatedNotNull] s /// Description for the custom precondition. /// [DebuggerStepThrough] + //TODO: Refactor tests for CompliesWith public static TNullable CompliesWith( - [NotNull, ValidatedNotNull] TNullable? value, - [NotNull, ValidatedNotNull] Func validator, - [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string preconditionDescription) + [NotNull] TNullable? value, + [NotNull] Func validator, + [NotNull] string preconditionDescription, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, true); @@ -606,27 +616,28 @@ public static TNullable CompliesWith( /// Description for the custom precondition. /// [DebuggerStepThrough] + //TODO: Refactor tests for DoesNotComplyWith public static TNullable DoesNotComplyWith( - [NotNull, ValidatedNotNull] TNullable? value, - [NotNull, ValidatedNotNull] Func validator, - [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string preconditionDescription) + [NotNull] TNullable? value, + [NotNull] Func validator, + [NotNull] string preconditionDescription, + [NotNull, CallerArgumentExpression("value")] string paramName = "") where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, false); [return: NotNull] private static TNullable CompliesWithExpected( - [NotNull, ValidatedNotNull] TNullable? value, - [NotNull, ValidatedNotNull] Func validator, - [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string preconditionDescription, + [NotNull] TNullable? value, + [NotNull] Func validator, + [NotNull] string preconditionDescription, + [NotNull] string paramName, bool expected) where TNullable : class { TNullable notNullValue = value.ValueOrThrowIfNull(nameof(value)); - Func notNullValidator = validator.ValueOrThrowIfNull(nameof(validator)); - string notNullParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); + Func notNullValidator = validator.ValueOrThrowIfNull(); + string notNullParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); string notNullPreconditionDescription - = preconditionDescription.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(preconditionDescription)); + = preconditionDescription.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); if (notNullValidator(notNullValue) != expected) { @@ -638,4 +649,3 @@ string notNullPreconditionDescription #endregion //General Purpose Checks } -#pragma warning restore CA1303 // Do not pass literals as localized parameters diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index 0c1898d..f0f4fd0 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -1,21 +1,23 @@ -namespace Triplex.Validations.ArgumentsHelpers; +using System.Runtime.CompilerServices; + +namespace Triplex.Validations.ArgumentsHelpers; #pragma warning disable CA1303 // Do not pass literals as localized parameters internal static class Extensions { [return: NotNull] - internal static T ValueOrThrowIfNull([NotNull, ValidatedNotNull] this T? value, string paramName) - { - if (value is not null) - { - return value; - } + internal static T ValueOrThrowIfNull([NotNull] this T? value, + [CallerArgumentExpression("value")] string paramName = "") + => value ?? throw new ArgumentNullException(paramName); - throw new ArgumentNullException(paramName); - } + [return: NotNull] + internal static T ValueOrThrowInvalidOperationIfNull([NotNull] this T? stateElement, + string elementName) + => stateElement + ?? throw new InvalidOperationException($"Operation not allowed when {elementName} is null."); [return: NotNull] - internal static T ValueOrThrowIfNull([NotNull, ValidatedNotNull] this T? value, string paramName, + internal static T ValueOrThrowIfNull([NotNull] this T? value, string paramName, string customMessage) { if (value is not null) @@ -38,7 +40,7 @@ internal static string ValueOrThrowIfZeroLength(this string value, string paramN return value; } - throw new ArgumentOutOfRangeException(paramName, value.Length, customMessage); + throw new ArgumentFormatException(paramName: paramName, message: customMessage); } [return: NotNull] @@ -57,26 +59,27 @@ internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string pa } [return: NotNull] - internal static string ValueOrThrowIfNullOrZeroLength([NotNull, ValidatedNotNull] this string? value, + internal static string ValueOrThrowIfNullOrZeroLength([NotNull] this string? value, string paramName) => ValueOrThrowIfNull(value, paramName) .ValueOrThrowIfZeroLength(paramName); [return: NotNull] - internal static string ValueOrThrowIfNullOrZeroLength([NotNull, ValidatedNotNull] this string? value, + internal static string ValueOrThrowIfNullOrZeroLength([NotNull] this string? value, string paramName, string customMessage) => ValueOrThrowIfNull(value, paramName, customMessage) .ValueOrThrowIfZeroLength(paramName, customMessage); [return: NotNull] - internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([NotNull, ValidatedNotNull] this string? value, - string paramName) + internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly( + [NotNull] this string? value, + [CallerArgumentExpression("value")] string paramName = "") => ValueOrThrowIfNull(value, paramName) .ValueOrThrowIfZeroLength(paramName) .ValueOrThrowIfWhiteSpaceOnly(paramName); [return: NotNull] - internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([NotNull, ValidatedNotNull] this string? value, + internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([NotNull] this string? value, string paramName, string customMessage) => ValueOrThrowIfNull(value, paramName, customMessage) .ValueOrThrowIfZeroLength(paramName, customMessage) @@ -110,7 +113,7 @@ internal static TEnumType ValueOrThrowIfNotDefined(this TEnumType val [return: NotNull] internal static TType[] ValueOrThrowIfNullOrWithLessThanElements( - [NotNull, ValidatedNotNull] this TType[]? value, int minimumElements, string paramName) + [NotNull] this TType[]? value, int minimumElements, string paramName) { OutOfRangeChecks.GreaterThanOrEqualTo(ValueOrThrowIfNull(value, paramName).Length, minimumElements, paramName); diff --git a/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs b/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs index 57e9a63..57263fb 100644 --- a/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs +++ b/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs @@ -3,39 +3,39 @@ internal static class NullAndEmptyChecks { [return: NotNull] - internal static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, - [NotNull, ValidatedNotNull] string paramName) where TParamType : class + internal static TParamType NotNull([NotNull] TParamType? value, + [NotNull] string paramName) where TParamType : class => value.ValueOrThrowIfNull(paramName.ValueOrThrowIfNull(nameof(paramName))); [return: NotNull] - internal static TParamType NotNull([NotNull, ValidatedNotNull] TParamType? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + internal static TParamType NotNull([NotNull] TParamType? value, + [NotNull] string paramName, [NotNull] string customMessage) where TParamType : class => value.ValueOrThrowIfNull(paramName.ValueOrThrowIfNull(nameof(paramName)), customMessage.ValueOrThrowIfNull(nameof(customMessage))); [return: NotNull] - internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName) - => NotNullOrEmpty(value, paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName))) + internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull] string? value, + [NotNull] string paramName) + => NotNullOrEmpty(value, paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()) .ValueOrThrowIfWhiteSpaceOnly(paramName); [return: NotNull] - internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull] string? value, + [NotNull] string paramName, [NotNull] string customMessage) => NotNullOrEmpty(value, paramName, customMessage) .ValueOrThrowIfWhiteSpaceOnly(paramName, customMessage); [return: NotNull] - internal static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName) + internal static string NotNullOrEmpty([NotNull] string? value, + [NotNull] string paramName) => value.ValueOrThrowIfNullOrZeroLength( - paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName))); + paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); [return: NotNull] - internal static string NotNullOrEmpty([NotNull, ValidatedNotNull] string? value, - [NotNull, ValidatedNotNull] string paramName, [NotNull, ValidatedNotNull] string customMessage) + internal static string NotNullOrEmpty([NotNull] string? value, + [NotNull] string paramName, [NotNull] string customMessage) => value.ValueOrThrowIfNullOrZeroLength( - paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), - customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(customMessage))); + paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), + customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); } diff --git a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs index 28c1c4f..f28cd33 100644 --- a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs +++ b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs @@ -3,8 +3,8 @@ internal static class OutOfRangeChecks { [return: NotNull] - internal static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) + internal static TComparable LessThan([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxExclusiveOnly( @@ -13,9 +13,9 @@ internal static TComparable LessThan([NotNull, ValidatedNotNull] TC } [return: NotNull] - internal static TComparable LessThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + internal static TComparable LessThan([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxExclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -24,8 +24,8 @@ internal static TComparable LessThan([NotNull, ValidatedNotNull] TC } [return: NotNull] - internal static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) + internal static TComparable LessThanOrEqualTo([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxInclusiveOnly( @@ -35,9 +35,9 @@ internal static TComparable LessThanOrEqualTo([NotNull, ValidatedNo } [return: NotNull] - internal static TComparable LessThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) + internal static TComparable LessThanOrEqualTo([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxInclusiveOnly( @@ -47,8 +47,8 @@ internal static TComparable LessThanOrEqualTo([NotNull, ValidatedNo } [return: NotNull] - internal static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) + internal static TComparable GreaterThan([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinExclusiveOnly( @@ -58,9 +58,9 @@ internal static TComparable GreaterThan([NotNull, ValidatedNotNull] } [return: NotNull] - internal static TComparable GreaterThan([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + internal static TComparable GreaterThan([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinExclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -69,8 +69,8 @@ internal static TComparable GreaterThan([NotNull, ValidatedNotNull] } [return: NotNull] - internal static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName) + internal static TComparable GreaterThanOrEqualTo([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( @@ -80,9 +80,9 @@ internal static TComparable GreaterThanOrEqualTo([NotNull, Validate } [return: NotNull] - internal static TComparable GreaterThanOrEqualTo([NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? other, [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + internal static TComparable GreaterThanOrEqualTo([NotNull] TComparable? value, + [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -92,11 +92,31 @@ internal static TComparable GreaterThanOrEqualTo([NotNull, Validate [return: NotNull] internal static TComparable Between( - [NotNull, ValidatedNotNull] TComparable? value, - [NotNull, ValidatedNotNull] TComparable? fromInclusive, - [NotNull, ValidatedNotNull] TComparable? toInclusive, - [NotNull, ValidatedNotNull] string paramName, - [NotNull, ValidatedNotNull] string customMessage) where TComparable : IComparable + [NotNull] TComparable? value, + [NotNull] TComparable? fromInclusive, + [NotNull] TComparable? toInclusive, + [NotNull] string paramName) where TComparable : IComparable + { + ComparableRange range = new( + SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull(nameof(fromInclusive))), + SimpleOption.SomeNotNull(toInclusive.ValueOrThrowIfNull(nameof(toInclusive)))); + + string notNullParamName = paramName.ValueOrThrowIfNull(nameof(paramName)); + + return range.IsWithin( + value.ValueOrThrowIfNull(notNullParamName), + notNullParamName, + customMessage: null!); + + } + + [return: NotNull] + internal static TComparable Between( + [NotNull] TComparable? value, + [NotNull] TComparable? fromInclusive, + [NotNull] TComparable? toInclusive, + [NotNull] string paramName, + [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = new( SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull(nameof(fromInclusive))), @@ -111,9 +131,9 @@ internal static TComparable Between( [return: NotNull] private static TComparable CheckBoundaries( - [NotNull, ValidatedNotNull] TComparable? value, + [NotNull] TComparable? value, ComparableRange range, - [NotNull, ValidatedNotNull] string paramName, + [NotNull] string paramName, string? customMessage) where TComparable : IComparable { return range.IsWithin(value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(paramName)), diff --git a/src/Validations/State.cs b/src/Validations/State.cs index 8a66b6a..00620cf 100644 --- a/src/Validations/State.cs +++ b/src/Validations/State.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; + namespace Triplex.Validations; /// @@ -15,9 +17,9 @@ public static class State /// Can not be /// [DebuggerStepThrough] - public static void IsTrue(bool stateQuery, [NotNull, ValidatedNotNull] string message) + public static void IsTrue(bool stateQuery, [NotNull] string message) { - Arguments.NotNull(message, nameof(message)); + NullAndEmptyChecks.NotNull(message, nameof(message)); if (!stateQuery) { @@ -32,9 +34,9 @@ public static void IsTrue(bool stateQuery, [NotNull, ValidatedNotNull] string me /// Can not be /// [DebuggerStepThrough] - public static void IsFalse(bool stateQuery, [NotNull, ValidatedNotNull] string message) + public static void IsFalse(bool stateQuery, [NotNull] string message) { - Arguments.NotNull(message, nameof(message)); + NullAndEmptyChecks.NotNull(message, nameof(message)); if (stateQuery) { @@ -42,6 +44,29 @@ public static void IsFalse(bool stateQuery, [NotNull, ValidatedNotNull] string m } } + /// + /// Object state part null check. + /// + /// State part to check for + /// Can not be + /// or throws + /// + /// When is . + /// + /// + /// When is . + /// + [DebuggerStepThrough] + [return: NotNull] + public static T IsNotNull( + [NotNull] T stateElement, + [NotNull, CallerArgumentExpression("stateElement")] string elementName = "") + { + NullAndEmptyChecks.NotNull(elementName, nameof(elementName)); + + return stateElement.ValueOrThrowInvalidOperationIfNull(elementName); + } + #endregion //Preconditions @@ -53,9 +78,9 @@ public static void IsFalse(bool stateQuery, [NotNull, ValidatedNotNull] string m /// Expected to be true /// Can not be [DebuggerStepThrough] - public static void StillHolds(bool invariant, [NotNull, ValidatedNotNull] string message) + public static void StillHolds(bool invariant, [NotNull] string message) { - Arguments.NotNull(message, nameof(message)); + NullAndEmptyChecks.NotNull(message, nameof(message)); if (!invariant) { @@ -69,9 +94,9 @@ public static void StillHolds(bool invariant, [NotNull, ValidatedNotNull] string /// Expected to be false /// Can not be [DebuggerStepThrough] - public static void StillNotHolds(bool invariant, [NotNull, ValidatedNotNull] string message) + public static void StillNotHolds(bool invariant, [NotNull] string message) { - Arguments.NotNull(message, nameof(message)); + NullAndEmptyChecks.NotNull(message, nameof(message)); if (invariant) { diff --git a/src/Validations/Utilities/ValidatedNotNullAttribute.cs b/src/Validations/Utilities/ValidatedNotNullAttribute.cs deleted file mode 100644 index 67547ee..0000000 --- a/src/Validations/Utilities/ValidatedNotNullAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Triplex.Validations.Utilities; - -// CP to https://github.com/dotnet/corefx/blob/master/src/System.Collections.Immutable/src/Validation/ValidatedNotNullAttribute.cs 2020 - -/// -/// Indicates to Code Analysis that a method validates a particular parameter. -/// -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] -internal sealed class ValidatedNotNullAttribute : Attribute -{ -} diff --git a/tests/unit/Validations.Tests/Algorithms/Checksum/LuhnFormulaFacts/IsValidMessageFacts.cs b/tests/unit/Validations.Tests/Algorithms/Checksum/LuhnFormulaFacts/IsValidMessageFacts.cs index c13204e..cd101b7 100644 --- a/tests/unit/Validations.Tests/Algorithms/Checksum/LuhnFormulaFacts/IsValidMessageFacts.cs +++ b/tests/unit/Validations.Tests/Algorithms/Checksum/LuhnFormulaFacts/IsValidMessageFacts.cs @@ -1,4 +1,5 @@ using Triplex.Validations.Algorithms.Checksum; +using Triplex.Validations.Exceptions; namespace Triplex.Validations.Tests.Algorithms.Checksum.LuhnFormulaFacts; @@ -107,12 +108,12 @@ public void With_Null_As_String_Throws_ArgumentNullException() [TestCase("1+01")] [TestCase("ab")] public void With_Non_Digit_Characters_Throws_FormatException_As_String(string rawDigits) - => Assert.That(() => LuhnFormula.IsValid(rawDigits), Throws.InstanceOf()); + => Assert.That(() => LuhnFormula.IsValid(rawDigits), Throws.InstanceOf()); [TestCase("")] [TestCase(" ")] [TestCase("a")] [TestCase("1")] public void With_Less_Than_Two_Elements_Throws_ArgumentOutOfRangeException(string rawDigits) - => Assert.That(() => LuhnFormula.IsValid(rawDigits), Throws.InstanceOf()); + => Assert.That(() => LuhnFormula.IsValid(rawDigits), Throws.InstanceOf()); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/BetweenMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/BetweenMessageFacts.cs index 0a31718..84c0e25 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/BetweenMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/BetweenMessageFacts.cs @@ -15,7 +15,7 @@ internal sealed class BetweenMessageFacts [TestCase(1, 3, 3)] public void With_Valid_Integers_Throws_Nothing(int from, int value, int to) { - int validatedValue = Arguments.Between(value, from, to, nameof(value), CustomMessage); + int validatedValue = Arguments.BetweenOrExceptionWithMessage(value, from, to, nameof(value), CustomMessage); Assert.That(validatedValue, Is.EqualTo(value)); } @@ -28,7 +28,7 @@ public void With_Invalid_Range_Throws_ArgumentOutOfRangeException(int from, int { (int fromCopy, int valueCopy, int toCopy) = (from, value, to); - Assert.That(() => Arguments.Between(valueCopy, fromCopy, toCopy, nameof(value), CustomMessage), + Assert.That(() => Arguments.BetweenOrExceptionWithMessage(valueCopy, fromCopy, toCopy, nameof(value), CustomMessage), Throws.InstanceOf() .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("min")); } @@ -45,7 +45,7 @@ public void With_Invalid_Value_Throws_ArgumentOutOfRangeException(int from, int { (int fromCopy, int valueCopy, int toCopy) = (from, value, to); - Assert.That(() => Arguments.Between(valueCopy, fromCopy, toCopy, nameof(value), CustomMessage), + Assert.That(() => Arguments.BetweenOrExceptionWithMessage(valueCopy, fromCopy, toCopy, CustomMessage, nameof(value)), Throws.InstanceOf() .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo(nameof(value)) .And.Message.StartsWith(CustomMessage)); diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/BetweenOrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/BetweenOrExceptionMessage.cs new file mode 100644 index 0000000..4db5019 --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/BetweenOrExceptionMessage.cs @@ -0,0 +1,46 @@ +namespace Triplex.Validations.Tests.ArgumentsFacts; + +[TestFixture] +internal sealed class BetweenOrExceptionMessage +{ + [TestCase(1, 2)] + [TestCase(0, 1)] + public void Value_Can_Be_Equals_To_From_And_To(int from, int to) + { + const int val = 1; + + int actual = Arguments.BetweenOrException(val, from, to); + + Assert.That(actual, Is.EqualTo(val)); + } + + [TestCase(1, 0)] + [TestCase(5, 5)] + public void With_Inverted_And_Closed_Range_Throws_ArgumentOutOfRangeException(int from, int to) + => Assert.That(() => Arguments.BetweenOrException(1, from, to), + Throws.InstanceOf()); + + [Test] + public void With_Null_Value_Throws_ArgumentNullException([Values] bool useCustomParamName) + { + const string FakeParameterName = "fakeCodeParam"; + const string code = null!; + + Action act = useCustomParamName ? + () => Arguments.BetweenOrException(code, "000", "002", FakeParameterName) : + () => Arguments.BetweenOrException(code, "000", "002"); + + string finalParamName = useCustomParamName ? FakeParameterName : nameof(code); + + Assert.That(() => act(), Throws.ArgumentNullException + .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(finalParamName)); + } + + [Test] + public void With_Null_From_Throws_ArgumentNullException() + => Assert.That(() => Arguments.BetweenOrException("b", null!, "c"), Throws.ArgumentNullException); + + [Test] + public void With_Null_To_Throws_ArgumentNullException() + => Assert.That(() => Arguments.BetweenOrException("b", "a", null!), Throws.ArgumentNullException); +} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/Colors.cs b/tests/unit/Validations.Tests/ArgumentsFacts/Colors.cs new file mode 100644 index 0000000..1ba3f3c --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/Colors.cs @@ -0,0 +1,3 @@ +namespace Triplex.Validations.Tests.ArgumentsFacts; + +internal enum Color { Black = 0, White = 1 } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs index ddad569..d9bdc0e 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs @@ -5,42 +5,42 @@ internal sealed class CompliesWithMessageFacts { private const string PreconditionDescription = "Must be true."; - [Test] - public void With_True_Throws_Nothing() - { - Assert.That(() => Arguments.CompliesWith(2 + 2 == 4, "someParam", PreconditionDescription), Throws.Nothing); - } + // [Test] + // public void With_True_Throws_Nothing() + // { + // Assert.That(() => Arguments.CompliesWith(2 + 2 == 4, "someParam", PreconditionDescription), Throws.Nothing); + // } - [Test] - public void With_False_Throws_ArgumentException() - { - const string paramName = "someParameter"; - Assert.That(() => Arguments.CompliesWith(2 + 2 == 5, paramName, PreconditionDescription), - Throws.ArgumentException - .With.Property(nameof(ArgumentException.ParamName)).EqualTo(paramName) - .And.Message.Contains(PreconditionDescription)); - } + // [Test] + // public void With_False_Throws_ArgumentException() + // { + // const string paramName = "someParameter"; + // Assert.That(() => Arguments.CompliesWith(2 + 2 == 5, paramName, PreconditionDescription), + // Throws.ArgumentException + // .With.Property(nameof(ArgumentException.ParamName)).EqualTo(paramName) + // .And.Message.Contains(PreconditionDescription)); + // } - [Test] - public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string paramName, - [Values] bool precondition) - { - string paramNameCopy = paramName; + // [Test] + // public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string paramName, + // [Values] bool precondition) + // { + // string paramNameCopy = paramName; - Assert.That(() => Arguments.CompliesWith(precondition, paramNameCopy, PreconditionDescription), - Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo("paramName")); - } + // Assert.That(() => Arguments.CompliesWith(precondition, paramNameCopy, PreconditionDescription), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentException.ParamName)).EqualTo("paramName")); + // } - [Test] - public void With_Invalid_PreconditionDescription_Throws_ArgumentException( - [Values(null, "", " ", "\n\r\t ")] string preconditionDescription, [Values] bool precondition) - { - string preconditionDescriptionCopy = preconditionDescription; + // [Test] + // public void With_Invalid_PreconditionDescription_Throws_ArgumentException( + // [Values(null, "", " ", "\n\r\t ")] string preconditionDescription, [Values] bool precondition) + // { + // string preconditionDescriptionCopy = preconditionDescription; - Assert.That(() => Arguments.CompliesWith(precondition, "someParamName", preconditionDescriptionCopy), - Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo("preconditionDescription")); - } + // Assert.That(() => Arguments.CompliesWith(precondition, "someParamName", preconditionDescriptionCopy), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentException.ParamName)).EqualTo("preconditionDescription")); + // } } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs index 987b92b..065015e 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs @@ -15,7 +15,7 @@ public GreaterThanMessage(bool useCustomErrorMessage) : base(useCustomErrorMessa [TestCase(2, -1)] public void With_Valid_Integers_Throws_Nothing(int theValue, int other) { - int validatedValue = Arguments.GreaterThan(theValue, other, nameof(theValue), CustomError); + int validatedValue = Arguments.GreaterThanOrExceptionWithMessage(theValue, other, nameof(theValue), CustomError); Assert.That(validatedValue, Is.EqualTo(theValue)); } @@ -120,7 +120,7 @@ private static TComparable GreaterThan( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.GreaterThan(value, other, paramName!, customError!) - : Arguments.GreaterThan(value, other, paramName!); + ? Arguments.GreaterThanOrExceptionWithMessage(value, other, customError!, paramName!) + : Arguments.GreaterThanOrException(value, other, paramName!); } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs index 93ca1fb..4a147ac 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs @@ -17,7 +17,7 @@ public GreaterThanOrEqualToMessage(bool useCustomErrorMessage) : base(useCustomE [TestCase(2, -2)] public void With_Valid_Integers_Throws_Nothing(int theValue, int other) { - int validatedValue = Arguments.GreaterThanOrEqualTo(theValue, other, nameof(theValue), CustomError); + int validatedValue = Arguments.GreaterThanOrEqualToOrExceptionWithMessage(theValue, other, nameof(theValue), CustomError); Assert.That(validatedValue, Is.EqualTo(theValue)); } @@ -127,7 +127,7 @@ private static TComparable GreaterThanOrEqualTo( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.GreaterThanOrEqualTo(value, other, paramName, customError) - : Arguments.GreaterThanOrEqualTo(value, other, paramName); + ? Arguments.GreaterThanOrEqualToOrExceptionWithMessage(value, other, customError, paramName) + : Arguments.GreaterThanOrEqualToOrException(value, other, paramName); } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/LessThanMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/LessThanMessage.cs index 7e66479..007ec9a 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/LessThanMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/LessThanMessage.cs @@ -14,7 +14,7 @@ public LessThanMessage(bool useCustomErrorMessage) : base(useCustomErrorMessage) [TestCase(-2, 1)] public void With_Valid_Integers_Throws_Nothing(int theValue, int other) { - int validatedValue = Arguments.LessThan(theValue, other, nameof(theValue), CustomError); + int validatedValue = Arguments.LessThanOrExceptionWithMessage(theValue, other, nameof(theValue), CustomError); Assert.That(validatedValue, Is.EqualTo(theValue)); } @@ -121,7 +121,7 @@ private static TComparable LessThan( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.LessThan(value, other!, paramName!, customError!) - : Arguments.LessThan(value, other!, paramName!); + ? Arguments.LessThanOrExceptionWithMessage(value, other!, customError!, paramName!) + : Arguments.LessThanOrException(value, other!, paramName!); } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs index 2c01439..c33eff5 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs @@ -17,7 +17,7 @@ public LessThanOrEqualToMessage(bool useCustomErrorMessage) : base(useCustomErro [TestCase(-2, 1)] public void With_Valid_Integers_Throws_Nothing(int theValue, int other) { - int validatedValue = Arguments.LessThanOrEqualTo(theValue, other, nameof(theValue), CustomError); + int validatedValue = Arguments.LessThanOrEqualToOrExceptionWithMessage(theValue, other, nameof(theValue), CustomError); Assert.That(validatedValue, Is.EqualTo(theValue)); } @@ -126,7 +126,7 @@ private static TComparable LessThanOrEqualTo( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.LessThanOrEqualTo(value, other, paramName!, customError!) - : Arguments.LessThanOrEqualTo(value, other, paramName!); + ? Arguments.LessThanOrEqualToOrExceptionWithMessage(value, other, customError!, paramName!) + : Arguments.LessThanOrEqualToOrException(value, other, paramName!); } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionMessage.cs new file mode 100644 index 0000000..e55e3bc --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionMessage.cs @@ -0,0 +1,40 @@ +namespace Triplex.Validations.Tests.ArgumentsFacts; + +[TestFixture] +internal sealed class MemberOfOrExceptionMessage +{ + private const string FakeParamName = "someOtherColor"; + + [Test] + public void With_Invalid_Constants_And_Custom_Param_Name_Throws_ArgumentOutOfRangeException( + [Values(-1, 2)] Color someColor, [Values] bool useCustomParamName) + { + string finalParamName = useCustomParamName ? FakeParamName : nameof(someColor); + + Action act = useCustomParamName ? + () => Arguments.MemberOfOrException(someColor, finalParamName) : + () => Arguments.MemberOfOrException(someColor); + + IEnumerable exceptionParts = ExceptionMessagePartsFor(someColor, finalParamName, nameof(Color)); + + Assert.That(() => act(), Throws.InstanceOf() + .WithMessageContainsAll(exceptionParts)); + } + + [Test] + public void With_Valid_Color_Constants_Returns_Value([Values] Color someColor, [Values] bool useCustomParamName) + { + Color validatedColor = useCustomParamName ? + Arguments.MemberOfOrException(someColor, FakeParamName) + : Arguments.MemberOfOrException(someColor); + + Assert.That(validatedColor, Is.EqualTo(someColor)); + } + + private static IEnumerable ExceptionMessagePartsFor(object value, string paramName, string typeName) + { + yield return "Value is not a member of enum {typeName}.".Replace("{typeName}", typeName); + yield return "Parameter '{paramName}'".Replace("{paramName}", paramName); + yield return "Actual value was {actualValue}.".Replace("{actualValue}", value.ToString()); + } +} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionWithMessageMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionWithMessageMessage.cs new file mode 100644 index 0000000..7159cf5 --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/MemberOfOrExceptionWithMessageMessage.cs @@ -0,0 +1,44 @@ +namespace Triplex.Validations.Tests.ArgumentsFacts; + +[TestFixture] +internal sealed class MemberOfOrExceptionWithMessageMessage +{ + private const string CustomMessage = "some custom error msg"; + private const string CustomParamName = "fakeParamName"; + + [Test] + public void With_Invalid_Constants_Throws_ArgumentOutOfRangeException( + [Values(-1, 2)] Color someColor, + [Values] bool useCustomParamName) + { + string finalParamName = useCustomParamName ? CustomParamName : nameof(someColor); + + Action act = useCustomParamName ? + () => Arguments.MemberOfOrExceptionWithMessage(someColor, CustomMessage, finalParamName) : + () => Arguments.MemberOfOrExceptionWithMessage(someColor, CustomMessage); + + IEnumerable exceptionParts = ExceptionMessagePartsFor(someColor, finalParamName); + + Assert.That(() => act(), + Throws.InstanceOf() + .WithMessageContainsAll(exceptionParts)); + } + + [Test] + public void With_Valid_Color_Constants_Returns_Value([Values] Color someColor, [Values] bool useCustomParamName) + { + Color validatedColor = useCustomParamName ? + Arguments.MemberOfOrExceptionWithMessage(someColor, CustomMessage, CustomParamName): + Arguments.MemberOfOrExceptionWithMessage(someColor, CustomMessage); + + Assert.That(validatedColor, Is.EqualTo(someColor)); + } + + private static IEnumerable ExceptionMessagePartsFor(object value, string paramName) + { + yield return CustomMessage; + yield return "Parameter"; + yield return paramName; + yield return "Actual value was {actualValue}.".Replace("{actualValue}", value.ToString()); + } +} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyGuidMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyGuidMessage.cs index b76f884..da383bc 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyGuidMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyGuidMessage.cs @@ -46,6 +46,6 @@ public void With_Null_CustomMessage_Throws_ArgumentNullException() private static Guid NotEmpty(Guid value, string? paramName, string? customMessage, bool useCustomMessage) => useCustomMessage ? - Arguments.NotEmpty(value, paramName!, customMessage!) - : Arguments.NotEmpty(value, paramName!); + Arguments.NotEmptyOrExceptionWithMessage(value, customMessage!, paramName!) + : Arguments.NotEmptyOrException(value, paramName!); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyMessageFacts.cs index 9aa1b66..7cc1f7d 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyMessageFacts.cs @@ -1,4 +1,6 @@ -namespace Triplex.Validations.Tests.ArgumentsFacts; +using Triplex.Validations.Exceptions; + +namespace Triplex.Validations.Tests.ArgumentsFacts; internal static class NotEmptyMessageFacts { @@ -11,7 +13,7 @@ internal sealed class WithInvalidCustomMessage public void With_Null_Throws_ArgumentNullException() { string someInput = "dummyValue"; - Assert.That(() => Arguments.NotEmpty(someInput, nameof(someInput), null!), + Assert.That(() => Arguments.NotEmptyOrExceptionWithMessage(someInput, null!, nameof(someInput)), Throws.ArgumentNullException .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); } @@ -20,9 +22,9 @@ public void With_Null_Throws_ArgumentNullException() public void With_Empty_Throws_ArgumentOutOfRangeException() { string someInput = "dummyValue"; - Assert.That(() => Arguments.NotEmpty(someInput, nameof(someInput), string.Empty), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("customMessage")); + Assert.That(() => Arguments.NotEmptyOrExceptionWithMessage(someInput, string.Empty, nameof(someInput)), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("customMessage")); } } @@ -44,9 +46,8 @@ public void With_Null_ParamName_Throws_ArgumentNullException() public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() { Assert.That(() => NotNullOrEmpty("dummyValue", string.Empty, CustomMessage, UseCustomErrorMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); } } @@ -76,8 +77,8 @@ public void With_Empty_Value_Throws_ArgumentOutOfRangeException() string? dummyParam = string.Empty; Constraint exceptionConstraint = AddMessageConstraint( - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam)), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo(nameof(dummyParam)), UseCustomErrorMessage); Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), @@ -122,6 +123,6 @@ private static string NotNullOrEmpty( string? paramName, string? customMessage, bool useCustomErrorMessage) - => useCustomErrorMessage ? Arguments.NotEmpty(value, paramName!, customMessage!) - : Arguments.NotEmpty(value, paramName!); + => useCustomErrorMessage ? Arguments.NotEmptyOrExceptionWithMessage(value, customMessage!, paramName!) + : Arguments.NotEmptyOrException(value, paramName!); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyNorWhiteSpaceOnlyOrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyNorWhiteSpaceOnlyOrExceptionMessage.cs new file mode 100644 index 0000000..7be962d --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyNorWhiteSpaceOnlyOrExceptionMessage.cs @@ -0,0 +1,75 @@ +using Triplex.Validations.Exceptions; + +namespace Triplex.Validations.Tests.ArgumentsFacts; + +[TestFixture] +internal sealed class NotEmptyNorWhiteSpaceOnlyOrExceptionMessage +{ + private const string FakeParameterName = "veryFakeParameterName"; + + [Test] + public void With_Null_Value_Throws_ArgumentNullException([Values] bool useCustomParamName) + { + string? dummyParam = null; + string finalParamName = useCustomParamName ? FakeParameterName : nameof(dummyParam); + + Action act = useCustomParamName ? + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(dummyParam, FakeParameterName) : + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(dummyParam); + + Assert.That(() => act(), Throws.ArgumentNullException.With + .Property(nameof(ArgumentNullException.ParamName)) + .EqualTo(finalParamName)); + } + + [Test] + public void With_Empty_And_Common_White_Space_Values_Throws_ArgumentFormatException( + [Values("", " ", "\n", "\r", "\t", "\n\r\t")] string? dummyParam, [Values] bool useCustomParamName) + { + string finalParamName = useCustomParamName ? FakeParameterName : nameof(dummyParam); + + Action act = useCustomParamName ? + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(dummyParam, FakeParameterName) : + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(dummyParam); + + Assert.That(() => act(), Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo(finalParamName)); + } + + [Test] + public void With_Valid_Values_Returns_Input_Value( + [Values("peter", "parker ", " Peter Parker Is Spiderman ")] string someValue, [Values] bool useCustomParamName) + { + Func act = BuildCheckForNotNull(someValue, useCustomParamName); + + string validatedValue = act(); + + Assert.That(validatedValue, Is.SameAs(someValue)); + } + + [Test] + public void With_Null_ParamName_Throws_ArgumentNullException() + { + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException("dummyValue", null!), + Throws.ArgumentNullException + .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); + } + + [Test] + public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException( + [Values("", " ", "\n", "\r", "\t", "\n\r\t")] string? paramName, [Values] bool useValidValue) + { + string email = useValidValue ? "peter.parker@marvel.com" : null!; + + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(email, paramName!), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); + } + + private static Func BuildCheckForNotNull(string? notNullValue, bool useCustomParamName) + { + return useCustomParamName ? + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(notNullValue, FakeParameterName) : + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(notNullValue); + } +} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Diadic_MessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Diadic_MessageFacts.cs deleted file mode 100644 index 2dcdf6d..0000000 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Diadic_MessageFacts.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Triplex.Validations.Exceptions; - -namespace Triplex.Validations.Tests.ArgumentsFacts; - -[TestFixture] -internal sealed class NotEmptyOrWhiteSpaceOnly_Diadic_MessageFacts -{ - [Test] - public void With_Null_Value_Throws_ArgumentNullException() - { - string? dummyParam = null; - - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam)), - Throws.ArgumentNullException.With - .Property(nameof(ArgumentNullException.ParamName)) - .EqualTo(nameof(dummyParam))); - } - - [Test] - public void With_Empty_Value_Throws_ArgumentOutOfRangeException() - { - string? dummyParam = string.Empty; - - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam)), - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam))); - } - - [TestCase(" ")] - [TestCase("\n")] - [TestCase("\r")] - [TestCase("\t")] - [TestCase("\n\r\t ")] - public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? dummyParam) - { - string? dummyParamValue = dummyParam; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParamValue, nameof(dummyParam)), - Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo(nameof(dummyParam))); - } - - [TestCase("peter")] - [TestCase("parker ")] - [TestCase(" Peter Parker Is Spiderman ")] - public void With_Valid_Values_Returns_Input_Value(string? someValue) - { - string validatedValue = Arguments.NotEmptyOrWhiteSpaceOnly(someValue, nameof(someValue)); - - Assert.That(validatedValue, Is.SameAs(someValue)); - } - - [Test] - public void With_Null_ParamName_Throws_ArgumentNullException() - { - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly("dummyValue", null!), - Throws.ArgumentNullException - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); - } - - [Test] - public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() - { - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly("dummyValue", string.Empty), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); - } - - [TestCase(" ")] - [TestCase("\n")] - [TestCase("\r")] - [TestCase("\t")] - [TestCase("\n\r\t ")] - public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException(string? paramName) - { - string? paramNameValue = paramName; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly("dummyValue", paramNameValue!), - Throws.InstanceOf() - .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); - } -} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Triadic_MessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Triadic_MessageFacts.cs index e5529fe..72a12f9 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Triadic_MessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotEmptyOrWhiteSpaceOnly_Triadic_MessageFacts.cs @@ -6,25 +6,30 @@ namespace Triplex.Validations.Tests.ArgumentsFacts; internal sealed class NotEmptyOrWhiteSpaceOnly_Triadic_MessageFacts { private const string CustomMessage = "Some error message, caller provided."; + private const string FakeParameterName = "veryFakeParameterName3"; [Test] - public void With_Null_CustomError_Throws_ArgumentNullException() + public void With_Null_CustomMessage_Throws_ArgumentNullException([Values] bool useCustomParamName) { string? dummyParam = "Hello world!"; + string finalParamName = useCustomParamName ? FakeParameterName : nameof(dummyParam); - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), null!), - Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - .EqualTo("customMessage")); + Action act = useCustomParamName ? + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParam, null!, finalParamName) : + () => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParam, null!); + + Assert.That(() => act(), Throws.ArgumentNullException + .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); } [Test] - public void With_Empty_CustomError_Throws_ArgumentOutOfRangeException() + public void With_Empty_CustomMessage_Throws_ArgumentFormatException() { string? dummyParam = "Hello world!"; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), string.Empty), - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParam, string.Empty, nameof(dummyParam)), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("customMessage")); } [TestCase(" ")] @@ -32,12 +37,12 @@ public void With_Empty_CustomError_Throws_ArgumentOutOfRangeException() [TestCase("\r")] [TestCase("\t")] [TestCase("\n\r\t ")] - public void With_Common_White_Space_CustomError_Throws_ArgumentFormatException(string? someErrorMessage) + public void With_Common_White_Space_CustomMessage_Throws_ArgumentFormatException(string? someErrorMessage) { string? dummyParam = "Hello world!"; string? someErrorMessageCopy = someErrorMessage; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), someErrorMessageCopy!), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParam, someErrorMessageCopy!, nameof(dummyParam)), Throws.InstanceOf() .With.Property(nameof(ArgumentException.ParamName)).EqualTo("customMessage")); } @@ -47,7 +52,7 @@ public void With_Null_Value_Throws_ArgumentNullException() { string? dummyParam = null; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParam, CustomMessage, nameof(dummyParam)), Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) .EqualTo(nameof(dummyParam))); } @@ -57,9 +62,9 @@ public void With_Empty_Value_Throws_ArgumentOutOfRangeException() { string? dummyParam = string.Empty; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam))); + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParam, CustomMessage, nameof(dummyParam)), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo(nameof(dummyParam))); } [TestCase(" ")] @@ -70,9 +75,9 @@ public void With_Empty_Value_Throws_ArgumentOutOfRangeException() public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? dummyParam) { string? dummyParamValue = dummyParam; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly(dummyParamValue, nameof(dummyParam), CustomMessage), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(dummyParamValue, CustomMessage, nameof(dummyParam)), Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo(nameof(dummyParam))); + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo(nameof(dummyParam))); } [TestCase("peter")] @@ -80,7 +85,7 @@ public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? [TestCase(" Peter Parker Is Spiderman ")] public void With_Valid_Values_Returns_Input_Value(string? someValue) { - string validatedValue = Arguments.NotEmptyOrWhiteSpaceOnly(someValue, nameof(someValue), CustomMessage); + string validatedValue = Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage(someValue, nameof(someValue), CustomMessage); Assert.That(validatedValue, Is.SameAs(someValue)); } @@ -88,7 +93,7 @@ public void With_Valid_Values_Returns_Input_Value(string? someValue) [Test] public void With_Null_ParamName_Throws_ArgumentNullException() { - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly("dummyValue", null!, CustomMessage), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage("dummyValue", CustomMessage, null!), Throws.ArgumentNullException .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); } @@ -96,10 +101,9 @@ public void With_Null_ParamName_Throws_ArgumentNullException() [Test] public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() { - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly("dummyValue", string.Empty, CustomMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage("dummyValue", CustomMessage, string.Empty), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); } [TestCase(" ")] @@ -110,7 +114,7 @@ public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException(string? paramName) { string? paramNameValue = paramName; - Assert.That(() => Arguments.NotEmptyOrWhiteSpaceOnly("dummyValue", paramNameValue!, CustomMessage), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage("dummyValue", CustomMessage, paramNameValue!), Throws.InstanceOf() .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs index 87a275e..b3d3fa8 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs @@ -8,111 +8,111 @@ internal sealed class NotNullEmptyOrWhiteSpaceOnly_Facts private const string CustomMessage = "Some error message, caller provided."; - [Test] - public void With_Null_CustomError_Throws_ArgumentNullException() - { - string? dummyParam = "Hello world!"; - - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), null!), - Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - .EqualTo("customMessage")); - } - - [Test] - public void With_Empty_CustomError_Throws_ArgumentOutOfRangeException() - { - string? dummyParam = "Hello world!"; - - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), string.Empty), - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); - } - - [TestCase(" ")] - [TestCase("\n")] - [TestCase("\r")] - [TestCase("\t")] - [TestCase("\n\r\t ")] - public void With_Common_White_Space_CustomError_Throws_ArgumentFormatException(string? someErrorMessage) - { - string? dummyParam = "Hello world!"; - string? someErrorMessageCopy = someErrorMessage; - - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), someErrorMessageCopy!), - Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo("customMessage")); - } - - [Test] - public void With_Null_Value_Throws_ArgumentNullException() - { - string? dummyParam = null; - - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), - Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - .EqualTo(nameof(dummyParam))); - } - - [Test] - public void With_Empty_Value_Throws_ArgumentOutOfRangeException() - { - string? dummyParam = string.Empty; - - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam))); - } - - [TestCase(" ")] - [TestCase("\n")] - [TestCase("\r")] - [TestCase("\t")] - [TestCase("\n\r\t ")] - public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? dummyParam) - { - string? dummyParamValue = dummyParam; - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParamValue, nameof(dummyParam), CustomMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo(nameof(dummyParam))); - } - - [TestCase("peter")] - [TestCase("parker ")] - [TestCase(" Peter Parker Is Spiderman ")] - public void With_Valid_Values_Returns_Input_Value(string? someValue) - { - string validatedValue = Arguments.NotNullEmptyOrWhiteSpaceOnly(someValue, nameof(someValue), CustomMessage); - - Assert.That(validatedValue, Is.SameAs(someValue)); - } - - [Test] - public void With_Null_ParamName_Throws_ArgumentNullException() - { - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", null!, CustomMessage), - Throws.ArgumentNullException - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); - } - - [Test] - public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() - { - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", string.Empty, CustomMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); - } - - [TestCase(" ")] - [TestCase("\n")] - [TestCase("\r")] - [TestCase("\t")] - [TestCase("\n\r\t ")] - public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException(string? paramName) - { - string? paramNameValue = paramName; - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", paramNameValue!, CustomMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); - } + // [Test] + // public void With_Null_CustomError_Throws_ArgumentNullException() + // { + // string? dummyParam = "Hello world!"; + + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), null!), + // Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) + // .EqualTo("customMessage")); + // } + + // [Test] + // public void With_Empty_CustomError_Throws_ArgumentOutOfRangeException() + // { + // string? dummyParam = "Hello world!"; + + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), string.Empty), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); + // } + + // [TestCase(" ")] + // [TestCase("\n")] + // [TestCase("\r")] + // [TestCase("\t")] + // [TestCase("\n\r\t ")] + // public void With_Common_White_Space_CustomError_Throws_ArgumentFormatException(string? someErrorMessage) + // { + // string? dummyParam = "Hello world!"; + // string? someErrorMessageCopy = someErrorMessage; + + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), someErrorMessageCopy!), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentException.ParamName)).EqualTo("customMessage")); + // } + + // [Test] + // public void With_Null_Value_Throws_ArgumentNullException() + // { + // string? dummyParam = null; + + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), + // Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) + // .EqualTo(nameof(dummyParam))); + // } + + // [Test] + // public void With_Empty_Value_Throws_ArgumentOutOfRangeException() + // { + // string? dummyParam = string.Empty; + + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam))); + // } + + // [TestCase(" ")] + // [TestCase("\n")] + // [TestCase("\r")] + // [TestCase("\t")] + // [TestCase("\n\r\t ")] + // public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? dummyParam) + // { + // string? dummyParamValue = dummyParam; + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParamValue, nameof(dummyParam), CustomMessage), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentException.ParamName)).EqualTo(nameof(dummyParam))); + // } + + // [TestCase("peter")] + // [TestCase("parker ")] + // [TestCase(" Peter Parker Is Spiderman ")] + // public void With_Valid_Values_Returns_Input_Value(string? someValue) + // { + // string validatedValue = Arguments.NotNullEmptyOrWhiteSpaceOnly(someValue, nameof(someValue), CustomMessage); + + // Assert.That(validatedValue, Is.SameAs(someValue)); + // } + + // [Test] + // public void With_Null_ParamName_Throws_ArgumentNullException() + // { + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", null!, CustomMessage), + // Throws.ArgumentNullException + // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); + // } + + // [Test] + // public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() + // { + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", string.Empty, CustomMessage), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") + // .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); + // } + + // [TestCase(" ")] + // [TestCase("\n")] + // [TestCase("\r")] + // [TestCase("\t")] + // [TestCase("\n\r\t ")] + // public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException(string? paramName) + // { + // string? paramNameValue = paramName; + // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", paramNameValue!, CustomMessage), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); + // } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Without_CustomMessage_Facts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Without_CustomMessage_Facts.cs index bf4a384..1a39aa2 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Without_CustomMessage_Facts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Without_CustomMessage_Facts.cs @@ -10,7 +10,7 @@ public void With_Null_Value_Throws_ArgumentNullException() { string? dummyParam = null; - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam!, nameof(dummyParam)), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(dummyParam!, nameof(dummyParam)), Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) .EqualTo(nameof(dummyParam))); } @@ -20,9 +20,9 @@ public void With_Empty_Value_Throws_ArgumentOutOfRangeException() { string? dummyParam = string.Empty; - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam)), - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam))); + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(dummyParam, nameof(dummyParam)), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo(nameof(dummyParam))); } [TestCase(" ")] @@ -33,9 +33,9 @@ public void With_Empty_Value_Throws_ArgumentOutOfRangeException() public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? dummyParam) { string? copy = dummyParam; - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(copy, nameof(dummyParam)), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException(copy, nameof(dummyParam)), Throws.InstanceOf() - .With.Property(nameof(ArgumentException.ParamName)).EqualTo(nameof(dummyParam))); + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo(nameof(dummyParam))); } [TestCase("peter")] @@ -43,7 +43,7 @@ public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? [TestCase(" Peter Parker Is Spiderman ")] public void With_Valid_Values_Returns_Input_Value(string? someValue) { - string validatedValue = Arguments.NotNullEmptyOrWhiteSpaceOnly(someValue, nameof(someValue)); + string validatedValue = Arguments.NotEmptyNorWhiteSpaceOnlyOrException(someValue, nameof(someValue)); Assert.That(validatedValue, Is.SameAs(someValue)); } @@ -51,7 +51,7 @@ public void With_Valid_Values_Returns_Input_Value(string? someValue) [Test] public void With_Null_ParamName_Throws_ArgumentNullException() { - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", null!), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException("dummyValue", null!), Throws.ArgumentNullException .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); } @@ -59,10 +59,9 @@ public void With_Null_ParamName_Throws_ArgumentNullException() [Test] public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() { - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", string.Empty), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException("dummyValue", string.Empty), + Throws.InstanceOf() + .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); } [TestCase(" ")] @@ -73,7 +72,7 @@ public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException(string? paramName) { string? paramNameValue = paramName; - Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", paramNameValue!), + Assert.That(() => Arguments.NotEmptyNorWhiteSpaceOnlyOrException("dummyValue", paramNameValue!), Throws.InstanceOf() .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullMessage.cs deleted file mode 100644 index f366c9d..0000000 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullMessage.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Triplex.Validations.Tests.ArgumentsFacts; - -internal sealed class NotNullMessage : BaseFixtureForOptionalCustomMessage -{ - private const string CustomMessage = "Look caller: the input can't be null."; - private const string DefaultParameterName = "username"; - - public NotNullMessage(bool useCustomErrorMessage) : base(useCustomErrorMessage) - { - } - - [Test] - public void With_Null_Throws_ArgumentNullException() - { - string expectedMessage = BuildFinalMessage(CustomMessage, DefaultParameterName, UseCustomErrorMessage); - - Assert.That(() => NotNull((string?)null, DefaultParameterName, CustomMessage, UseCustomErrorMessage), - Throws.ArgumentNullException.With.Message.EqualTo(expectedMessage) - .And.Property(nameof(ArgumentNullException.ParamName)).EqualTo(DefaultParameterName)); - } - - [Test] - public void With_Peter_Throws_Nothing() - => Assert.That(() => NotNull("Peter", DefaultParameterName, CustomMessage, UseCustomErrorMessage), - Throws.Nothing); - - [TestCase("")] - [TestCase("peter")] - [TestCase("spider-man")] - public void Returns_Value_When_No_Exception_For_Strings(string value) - { - string name = NotNull(value, nameof(value), CustomMessage, UseCustomErrorMessage); - - Assert.That(name, Is.SameAs(value)); - } - - [Test] - public void Returns_Value_When_No_Exception_For_List() - { - var fibonacci = new List { 1, 1, 2, 3, 5 }; - IList myFibonacci = NotNull(fibonacci, nameof(fibonacci), CustomMessage, UseCustomErrorMessage); - - Assert.That(myFibonacci, Is.SameAs(fibonacci)); - } - -#if NETFRAMEWORK - private const string DefaultErrorTemplate = - $"Value cannot be null.{NewLine}Parameter name: {{paramName}}"; - private static string BuildFinalMessage(string customMessagePrefix, string paramName, bool useCustomErrorMessage) - => useCustomErrorMessage ? $"{customMessagePrefix}{NewLine}Parameter name: {paramName}" - : DefaultErrorTemplate.Replace("{paramName}", paramName); -#endif -#if NETCOREAPP - private const string DefaultErrorTemplate = "Value cannot be null. (Parameter '{paramName}')"; - private static string BuildFinalMessage(string customMessagePrefix, string paramName, bool useCustomErrorMessage) - => useCustomErrorMessage ? $"{customMessagePrefix} (Parameter '{paramName}')" - : DefaultErrorTemplate.Replace("{paramName}", paramName); -#endif - - private static TValue NotNull(TValue? value, string? paramName, string? customMessage, - bool useCustomMessage) - where TValue : class => useCustomMessage - ? Arguments.NotNull(value, paramName!, customMessage!) - : Arguments.NotNull(value, paramName!); -} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs index 78ff009..400db7c 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs @@ -7,23 +7,23 @@ internal static class NotNullOrEmptyMessageFacts [TestFixture] internal sealed class WithInvalidCustomMessage { - [Test] - public void With_Null_Throws_ArgumentNullException() - { - string someInput = "dummyValue"; - Assert.That(() => Arguments.NotNullOrEmpty(someInput, nameof(someInput), null!), - Throws.ArgumentNullException - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); - } - - [Test] - public void With_Empty_Throws_ArgumentOutOfRangeException() - { - string someInput = "dummyValue"; - Assert.That(() => Arguments.NotNullOrEmpty(someInput, nameof(someInput), string.Empty), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("customMessage")); - } + // [Test] + // public void With_Null_Throws_ArgumentNullException() + // { + // string someInput = "dummyValue"; + // Assert.That(() => Arguments.NotNullOrEmpty(someInput, nameof(someInput), null!), + // Throws.ArgumentNullException + // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); + // } + + // [Test] + // public void With_Empty_Throws_ArgumentOutOfRangeException() + // { + // string someInput = "dummyValue"; + // Assert.That(() => Arguments.NotNullOrEmpty(someInput, nameof(someInput), string.Empty), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("customMessage")); + // } } internal sealed class WithInvalidParamName : BaseFixtureForOptionalCustomMessage @@ -32,22 +32,22 @@ public WithInvalidParamName(bool useCustomErrorMessage) : base(useCustomErrorMes { } - [Test] - public void With_Null_ParamName_Throws_ArgumentNullException() - { - Assert.That(() => NotNullOrEmpty("dummyValue", null, CustomMessage, UseCustomErrorMessage), - Throws.ArgumentNullException - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); - } - - [Test] - public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() - { - Assert.That(() => NotNullOrEmpty("dummyValue", string.Empty, CustomMessage, UseCustomErrorMessage), - Throws.InstanceOf() - .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); - } + // [Test] + // public void With_Null_ParamName_Throws_ArgumentNullException() + // { + // Assert.That(() => NotNullOrEmpty("dummyValue", null, CustomMessage, UseCustomErrorMessage), + // Throws.ArgumentNullException + // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); + // } + + // [Test] + // public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() + // { + // Assert.That(() => NotNullOrEmpty("dummyValue", string.Empty, CustomMessage, UseCustomErrorMessage), + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") + // .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); + // } } internal sealed class WithInvalidValueMessage : BaseFixtureForOptionalCustomMessage @@ -56,32 +56,32 @@ public WithInvalidValueMessage(bool useCustomErrorMessage) : base(useCustomError { } - [Test] - public void With_Null_Value_Throws_ArgumentNullException() - { - string? dummyParam = null; + // [Test] + // public void With_Null_Value_Throws_ArgumentNullException() + // { + // string? dummyParam = null; - Constraint exceptionConstraint = AddMessageConstraint( - Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - .EqualTo(nameof(dummyParam)), UseCustomErrorMessage); + // Constraint exceptionConstraint = AddMessageConstraint( + // Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) + // .EqualTo(nameof(dummyParam)), UseCustomErrorMessage); - Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), - exceptionConstraint); - } + // Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), + // exceptionConstraint); + // } - [Test] - public void With_Empty_Value_Throws_ArgumentOutOfRangeException() - { - string? dummyParam = string.Empty; + // [Test] + // public void With_Empty_Value_Throws_ArgumentOutOfRangeException() + // { + // string? dummyParam = string.Empty; - Constraint exceptionConstraint = AddMessageConstraint( - Throws.InstanceOf() - .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam)), - UseCustomErrorMessage); + // Constraint exceptionConstraint = AddMessageConstraint( + // Throws.InstanceOf() + // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam)), + // UseCustomErrorMessage); - Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), - exceptionConstraint); - } + // Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), + // exceptionConstraint); + // } private static Constraint AddMessageConstraint(Constraint messageConstraint, bool useCustomErrorMessage) => useCustomErrorMessage ? messageConstraint.With.Message.StartsWith(CustomMessage) : messageConstraint; @@ -93,34 +93,34 @@ public WithValidValueMessage(bool useCustomErrorMessage) : base(useCustomErrorMe { } - [TestCase(" ")] - [TestCase("\n")] - [TestCase("\r")] - [TestCase("\t")] - [TestCase("\n\r\t ")] - public void With_Common_White_Space_Sequences_Value_Throws_Nothing(string? dummyParam) - { - string myDummyValue = NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage); - - Assert.That(myDummyValue, Is.SameAs(dummyParam)); - } - - [TestCase("peter")] - [TestCase("parker ")] - [TestCase(" Peter Parker Is Spiderman ")] - public void With_Non_Empty_Values_Throws_Nothing(string? someParam) - { - string myDummyValue = NotNullOrEmpty(someParam, nameof(someParam), CustomMessage, UseCustomErrorMessage); - - Assert.That(myDummyValue, Is.SameAs(someParam)); - } + // [TestCase(" ")] + // [TestCase("\n")] + // [TestCase("\r")] + // [TestCase("\t")] + // [TestCase("\n\r\t ")] + // public void With_Common_White_Space_Sequences_Value_Throws_Nothing(string? dummyParam) + // { + // string myDummyValue = NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage); + + // Assert.That(myDummyValue, Is.SameAs(dummyParam)); + // } + + // [TestCase("peter")] + // [TestCase("parker ")] + // [TestCase(" Peter Parker Is Spiderman ")] + // public void With_Non_Empty_Values_Throws_Nothing(string? someParam) + // { + // string myDummyValue = NotNullOrEmpty(someParam, nameof(someParam), CustomMessage, UseCustomErrorMessage); + + // Assert.That(myDummyValue, Is.SameAs(someParam)); + // } } - private static string NotNullOrEmpty( - string? value, - string? paramName, - string? customMessage, - bool useCustomErrorMessage) - => useCustomErrorMessage ? Arguments.NotNullOrEmpty(value, paramName!, customMessage!) - : Arguments.NotNullOrEmpty(value, paramName!); + // private static string NotNullOrEmpty( + // string? value, + // string? paramName, + // string? customMessage, + // bool useCustomErrorMessage) + // => useCustomErrorMessage ? Arguments.NotNullOrEmpty(value, paramName!, customMessage!) + // : Arguments.NotNullOrEmpty(value, paramName!); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs new file mode 100644 index 0000000..9f75fae --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs @@ -0,0 +1,78 @@ +namespace Triplex.Validations.Tests.ArgumentsFacts; + +[TestFixture] +internal sealed class OrExceptionMessage +{ + private const string FakeParameterName = "veryFakeParameterName"; + + [Test] + public void With_Null_Throws_ArgumentNullException_Using_Correct_Param_Name([Values] bool useCustomParamName) + { + string email = null!; + + Action act = useCustomParamName ? + () => Arguments.OrException(email, FakeParameterName) : + () => Arguments.OrException(email); + + string finalParamName = useCustomParamName ? FakeParameterName : nameof(email); + + Assert.That(() => act(), + Throws.ArgumentNullException.With + .Property(nameof(ArgumentNullException.ParamName)).EqualTo(finalParamName)); + } + + [Test] + public void With_Null_Throws_ArgumentNullException_Using_Correct_Message([Values] bool useCustomParamName) + { + object user = null!; + string finalParamName = useCustomParamName ? FakeParameterName : nameof(user); + + Action act = useCustomParamName ? + () => Arguments.OrException(user, FakeParameterName) : + () => Arguments.OrException(user); + + IEnumerable exceptionParts = ExceptionMessagePartsFor(paramName: finalParamName); + + Assert.That(() => act(), Throws.ArgumentNullException.WithMessageContainsAll(exceptionParts)); + } + + [Test] + public void With_Peter_Throws_Nothing([Values] bool useCustomParamName) + { + Func act = BuildCheckForNotNull("Peter", useCustomParamName); + + Assert.That(() => _ = act(), Throws.Nothing); + } + + [Test] + public void Returns_Value_When_No_Exception_For_Strings([Values("", "peter", "spider-man")] string value, + [Values] bool useCustomParamName) + { + Func nameCheck = BuildCheckForNotNull(value, useCustomParamName); + + Assert.That(nameCheck(), Is.SameAs(value)); + } + + [Test] + public void Returns_Value_When_No_Exception_For_List([Values] bool useCustomParamName) + { + List fibonacci = new() { 1, 1, 2, 3, 5 }; + + Func> listCheck = BuildCheckForNotNull(fibonacci, useCustomParamName); + + Assert.That(listCheck(), Is.SameAs(fibonacci)); + } + + private static Func BuildCheckForNotNull(T notNullValue, bool useCustomParamName) where T : class + { + return useCustomParamName ? + () => Arguments.OrException(notNullValue, FakeParameterName) : + () => Arguments.OrException(notNullValue); + } + + private static IEnumerable ExceptionMessagePartsFor(string paramName) + { + yield return "Value cannot be null."; + yield return "Parameter '{paramName}'".Replace("{paramName}", paramName); + } +} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionWithMessageMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionWithMessageMessage.cs new file mode 100644 index 0000000..b58a87b --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionWithMessageMessage.cs @@ -0,0 +1,79 @@ +namespace Triplex.Validations.Tests.ArgumentsFacts; + +[TestFixture] +internal sealed class OrExceptionWithMessageMessage +{ + private const string CustomMessage = "Bad programmer, do not use NULLs."; + private const string FakeParameterName = "veryFakeParameterName2"; + + [Test] + public void With_Null_Throws_ArgumentNullException_Using_Correct_Param_Name([Values] bool useCustomParamName) + { + string email = null!; + + string finalParamName = useCustomParamName ? FakeParameterName : nameof(email); + + Action act = useCustomParamName ? + () => Arguments.OrExceptionWithMessage(email, CustomMessage, finalParamName) : + () => Arguments.OrExceptionWithMessage(email, CustomMessage); + + Assert.That(() => act(), + Throws.ArgumentNullException.With + .Property(nameof(ArgumentNullException.ParamName)).EqualTo(finalParamName)); + } + + [Test] + public void With_Null_Throws_ArgumentNullException_Using_Correct_Message([Values] bool useCustomParamName) + { + object user = null!; + string finalParamName = useCustomParamName ? FakeParameterName : nameof(user); + + Action act = useCustomParamName ? + () => Arguments.OrExceptionWithMessage(user, CustomMessage, finalParamName) : + () => Arguments.OrExceptionWithMessage(user, CustomMessage); + + IEnumerable exceptionParts = ExceptionMessagePartsFor(paramName: finalParamName); + + Assert.That(() => act(), Throws.ArgumentNullException.WithMessageContainsAll(exceptionParts)); + } + + [Test] + public void With_Peter_Throws_Nothing([Values] bool useCustomParamName) + { + Func act = BuildCheckForNotNull("Peter", useCustomParamName); + + Assert.That(() => _ = act(), Throws.Nothing); + } + + [Test] + public void Returns_Value_When_No_Exception_For_Strings([Values("", "peter", "spider-man")] string value, + [Values] bool useCustomParamName) + { + Func nameCheck = BuildCheckForNotNull(value, useCustomParamName); + + Assert.That(nameCheck(), Is.SameAs(value)); + } + + [Test] + public void Returns_Value_When_No_Exception_For_List([Values] bool useCustomParamName) + { + List fibonacci = new() { 1, 1, 2, 3, 5 }; + + Func> listCheck = BuildCheckForNotNull(fibonacci, useCustomParamName); + + Assert.That(listCheck(), Is.SameAs(fibonacci)); + } + + private static Func BuildCheckForNotNull(T notNullValue, bool useCustomParamName) where T : class + { + return useCustomParamName ? + () => Arguments.OrExceptionWithMessage(notNullValue, CustomMessage, FakeParameterName) : + () => Arguments.OrExceptionWithMessage(notNullValue, CustomMessage); + } + + private static IEnumerable ExceptionMessagePartsFor(string paramName) + { + yield return CustomMessage; + yield return "Parameter '{paramName}'".Replace("{paramName}", paramName); + } +} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/ValidBase64MessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/ValidBase64MessageFacts.cs index c9300d0..361e780 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/ValidBase64MessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/ValidBase64MessageFacts.cs @@ -53,6 +53,6 @@ private static string BuildFinalMessage(string customMessagePrefix, string param private static string ValidBase64(string? value, string? paramName, string? customMessage, bool useCustomMessage) => useCustomMessage - ? Arguments.ValidBase64(value, paramName!, customMessage!) + ? Arguments.ValidBase64(value, customMessage!, paramName!) : Arguments.ValidBase64(value, paramName!); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/ValidEnumerationMemberMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/ValidEnumerationMemberMessage.cs deleted file mode 100644 index 8f62b68..0000000 --- a/tests/unit/Validations.Tests/ArgumentsFacts/ValidEnumerationMemberMessage.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace Triplex.Validations.Tests.ArgumentsFacts; - -internal sealed class ValidEnumerationMemberMessage : BaseFixtureForOptionalCustomMessage -{ - private const string CustomMessage = "some custom error msg"; - - public enum Color { Black = 0, White = 1 } - - public ValidEnumerationMemberMessage(bool useCustomErrorMessage) : base(useCustomErrorMessage) - { - } - - [Test] - public void With_Invalid_Constants_Throws_ArgumentOutOfRangeException([Values(-1, 2)] Color someColor) - { - string expectedMessage = BuildExpectedMessageForValidEnumerationMember(someColor, nameof(Color), - nameof(someColor), CustomMessage, UseCustomErrorMessage); - - Assert.That(() => ValidEnumerationMember(someColor, nameof(someColor), CustomMessage, UseCustomErrorMessage), - Throws.InstanceOf() - .With.Message.EqualTo(expectedMessage)); - } - - [Test] - public void With_Valid_Color_Constants_Returns_Value([Values] Color someColor) - { - Color validatedColor = - ValidEnumerationMember(someColor, nameof(someColor), CustomMessage, UseCustomErrorMessage); - - Assert.That(validatedColor, Is.EqualTo(someColor)); - } - - [Test] - public void With_Valid_StringComparison_Constants_Returns_Value([Values] StringComparison someComparison) - { - StringComparison validatedComparisonStrategy = - ValidEnumerationMember(someComparison, nameof(someComparison), CustomMessage, UseCustomErrorMessage); - - Assert.That(validatedComparisonStrategy, Is.EqualTo(someComparison)); - } - -#if NETFRAMEWORK - private const string DefaultErrorTemplate = - $"Value is not a member of enum {{typeName}}.{NewLine}Parameter name: {{paramName}}{NewLine}Actual value was {{actualValue}}."; - private const string CustomErrorTemplate = - $"{{prefix}}{NewLine}Parameter name: {{paramName}}{NewLine}Actual value was {{actualValue}}."; -#endif -#if NETCOREAPP - private static readonly string DefaultErrorTemplate = - $"Value is not a member of enum {{typeName}}. (Parameter '{{paramName}}'){NewLine}Actual value was {{actualValue}}."; - private static readonly string CustomErrorTemplate = - $"{{prefix}} (Parameter '{{paramName}}'){NewLine}Actual value was {{actualValue}}."; -#endif - private static string BuildExpectedMessageForValidEnumerationMember(object value, string typeName, string paramName, - string customErrorMessage, bool useCustomErrorMessage) - { - string baseMessage = useCustomErrorMessage - ? CustomErrorTemplate.Replace("{prefix}", customErrorMessage) - : DefaultErrorTemplate.Replace("{typeName}", typeName); - - return baseMessage.Replace("{paramName}", paramName) - .Replace("{actualValue}", value.ToString()); - } - - private static TValue ValidEnumerationMember(TValue value, string paramName, string customMessage, - bool useCustomMessage) where TValue : Enum - { - return useCustomMessage ? - Arguments.ValidEnumerationMember(value, paramName, customMessage) - : Arguments.ValidEnumerationMember(value, paramName); - } -} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/ValidLuhnChecksumMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/ValidLuhnChecksumMessageFacts.cs index 4862ffc..5d0b4fa 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/ValidLuhnChecksumMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/ValidLuhnChecksumMessageFacts.cs @@ -35,7 +35,7 @@ public void With_Valid_Values_Throws_Nothing(string? value) public void With_Invalid_Value_Throws_FormatException(string? value) { string? copy = value; - Assert.That(() => Arguments.ValidLuhnChecksum(copy, nameof(value), CustomMessage), + Assert.That(() => Arguments.ValidLuhnChecksum(copy, CustomMessage, nameof(value)), Throws.InstanceOf() .With.Message.EqualTo(CustomMessage)); } diff --git a/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs new file mode 100644 index 0000000..6f7aba3 --- /dev/null +++ b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs @@ -0,0 +1,28 @@ +namespace Triplex.Validations.Tests; + +/// +/// Utility to help build NUnit constraints. +/// +public static class FluentConstraintsExtensions +{ + /// + /// Creates a constraint for Message:string property containing all the enumerated parts. + /// + /// Base exception constraint + /// + /// Expected parts to be found inside the property. + /// + /// + public static Constraint WithMessageContainsAll(this TypeConstraint exceptionConstraint, + IEnumerable expectedParts) + { + ConstraintExpression messageExpresion = exceptionConstraint.With; + string[] parts = expectedParts.ToArray(); + for (int i = 0; i < parts.Length - 1; i++) + { + messageExpresion = messageExpresion.Message.Contains(parts[i]).And; + } + + return messageExpresion.Message.Contains(parts[^1]); + } +} diff --git a/tests/unit/Validations.Tests/StateFacts/IsNotNullMessageFacts.cs b/tests/unit/Validations.Tests/StateFacts/IsNotNullMessageFacts.cs new file mode 100644 index 0000000..2f069d4 --- /dev/null +++ b/tests/unit/Validations.Tests/StateFacts/IsNotNullMessageFacts.cs @@ -0,0 +1,37 @@ +namespace Triplex.Validations.Tests.StateFacts; + +[TestFixture] +internal sealed class IsNotNullMessageFacts +{ + [Test] + public void With_Null_Message_Throws_ArgumentNullException([Values("Some", null!)] object stateElement) + { + const string argumentName = "elementName"; + Assert.That(() => State.IsNotNull(stateElement, elementName: null!), + Throws.ArgumentNullException + .With.Message.Contains(argumentName) + .And.Property(nameof(ArgumentNullException.ParamName)).EqualTo(argumentName)); + } + + [Test] + public void With_Not_Null_StateElement_And_Not_Null_Message_Throws_Nothing() + => Assert.That(() => State.IsNotNull("Some", "Message when null."), Throws.Nothing); + + [Test] + public void With_Null_StateElement_And_Not_Null_Name_Throws_ArgumentNullException() + { + object someThing = null!; + + Assert.That(() => State.IsNotNull(someThing, nameof(someThing)), + Throws.InvalidOperationException.With.Message.Contains(nameof(someThing))); + } + + [Test] + public void With_Null_StateElement_And_Without_Name_Throws_ArgumentNullException() + { + object someThing = null!; + + Assert.That(() => State.IsNotNull(someThing), + Throws.InvalidOperationException.With.Message.Contains(nameof(someThing))); + } +} From 2ca8b7e786c9257eed9a37dc24c2692fd867815b Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Wed, 11 May 2022 22:30:34 -0400 Subject: [PATCH 05/25] =?UTF-8?q?#30=20style-=F0=9F=8C=88:=20Reduce=20code?= =?UTF-8?q?=20samples=20indent=20on=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d71b4ce..e418e76 100644 --- a/README.md +++ b/README.md @@ -67,26 +67,26 @@ using Triplex.Validations; public sealed class Email { - private readonly string _value; - - public Email(string value) - { - //a) Simple form (param name for ArgumentException is automatically taken from 1st parameter) - Arguments.OrException(value); - - //b) With custom message (param name for ArgumentException is automatically taken from 1st parameter) - Arguments.OrExceptionWithMessage(value, "Invalid email"); - - // Use value ensuring that it is not null now - _value = value.ToLowerInvariant(); - - // or use returned value from argument check - _value = Arguments.OrException(value); //return same input (literally same reference) - } - - // Collapse Check-Then-Assign pattern using returned value from checks. - public Email(string username, string domain) - => _value = $"{Arguments.OrException(username)}@{Arguments.OrException(domain)}"; + private readonly string _value; + + public Email(string value) + { + //a) Simple form (param name for ArgumentException is automatically taken from 1st parameter) + Arguments.OrException(value); + + //b) With custom message (param name for ArgumentException is automatically taken from 1st parameter) + Arguments.OrExceptionWithMessage(value, "Invalid email"); + + // Use value ensuring that it is not null now + _value = value.ToLowerInvariant(); + + // or use returned value from argument check + _value = Arguments.OrException(value); //return same input (literally same reference) + } + + // Collapse Check-Then-Assign pattern using returned value from checks. + public Email(string username, string domain) + => _value = $"{Arguments.OrException(username)}@{Arguments.OrException(domain)}"; } ``` @@ -110,21 +110,21 @@ using Triplex.Validations; public sealed class MyList { - public T Remove(int index) { - //0. Pre-condition - State.IsTrue(_size > 0, "Unable to remove elements when empty."); + public T Remove(int index) { + //0. Pre-condition + State.IsTrue(_size > 0, "Unable to remove elements when empty."); - //1. Contract - Arguments.Between(index, 0, _size - 1, "Index out of bounds."); + //1. Contract + Arguments.Between(index, 0, _size - 1, "Index out of bounds."); - //2. Do your stuff - T removed = DoRemoveElementAt(index); - - //3. After removing size must be zero or greater - State.StillHolds(_size >= 0, "Internal error, size must not be negative."); + //2. Do your stuff + T removed = DoRemoveElementAt(index); + + //3. After removing size must be zero or greater + State.StillHolds(_size >= 0, "Internal error, size must not be negative."); - return removed; - } + return removed; + } } ``` State checks semantics: From 3d9707e82f62efa4357de2e7c3e6e3f55eb0315a Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Fri, 16 May 2025 18:02:36 -0400 Subject: [PATCH 06/25] SDK and Language version bump --- src/Validations/Arguments.cs | 70 +++++++++++-------- .../ArgumentsHelpers/Extensions.cs | 10 ++- src/Validations/State.cs | 8 +++ .../IsExternalInit.cs | 42 +++++++++++ src/Validations/Utilities/SimpleOption.cs | 6 +- src/Validations/Validations.csproj | 5 +- .../ArgumentsFacts/OrExceptionMessage.cs | 4 +- .../FluentConstraintsExtensions.cs | 8 +-- tests/unit/Validations.Tests/GlobalUsings.cs | 1 - .../Validations.Tests.csproj | 19 ++--- 10 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index 7cdaa9b..ca6a979 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -3,6 +3,10 @@ namespace Triplex.Validations; +#if (NETSTANDARD || NETCOREAPP) +#pragma warning disable CS0436 //CallerArgumentExpressionAttribute type conflicts +#endif + /// /// Utility class used to validate arguments. Useful to check constructor and public methods arguments. /// If checks are violated an instance of is thrown. @@ -24,7 +28,7 @@ public static class Arguments [return: NotNull] public static TParamType OrException( [NotNull] TParamType? value, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TParamType : class + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName); /// @@ -50,7 +54,7 @@ public static TParamType OrException( public static TParamType OrExceptionWithMessage( [NotNull] TParamType? value, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName, customMessage); @@ -61,7 +65,7 @@ public static TParamType OrExceptionWithMessage( /// Value to check /// Parameter name, from caller's context. /// - /// If any paramete is . + /// If any parameter is . /// If length is zero. /// /// If contains only white-space characters @@ -70,7 +74,7 @@ public static TParamType OrExceptionWithMessage( [return: NotNull] public static string NotEmptyNorWhiteSpaceOnlyOrException( [NotNull] string? value, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName); /// @@ -81,7 +85,7 @@ public static string NotEmptyNorWhiteSpaceOnlyOrException( /// Parameter name, from caller's context. /// Custom exception error message /// - /// If any paramete is . + /// If any parameter is . /// If length is zero. /// /// If contains only white-space characters @@ -92,7 +96,7 @@ public static string NotEmptyNorWhiteSpaceOnlyOrException( public static string NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( [NotNull] string? value, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); /// @@ -101,7 +105,7 @@ public static string NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( /// Value to check /// Parameter name, from caller's context. /// - /// If any paramete is . + /// If any parameter is . /// If length is zero. /// /// If contains only white-space characters @@ -111,7 +115,7 @@ public static string NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( //TODO: Refactor tests for NotEmptyOrException public static string NotEmptyOrException( [NotNull] string? value, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); /// @@ -121,7 +125,7 @@ public static string NotEmptyOrException( /// Parameter's name, can not be /// Custom message, can not be /// - /// If any paramete is . + /// If any parameter is . /// If length is zero. [DebuggerStepThrough] [return: NotNull] @@ -129,7 +133,7 @@ public static string NotEmptyOrException( public static string NotEmptyOrExceptionWithMessage( [NotNull] string? value, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); #endregion @@ -145,7 +149,7 @@ public static string NotEmptyOrExceptionWithMessage( [DebuggerStepThrough] //TODO: Refactor tests for NotEmptyOrException public static Guid NotEmptyOrException(Guid value, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); @@ -173,7 +177,7 @@ public static Guid NotEmptyOrException(Guid value, //TODO: Refactor tests for NotEmptyOrExceptionWithMessage public static Guid NotEmptyOrExceptionWithMessage(Guid value, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { (string validParamName, string validCustomMessage) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), @@ -204,7 +208,7 @@ public static Guid NotEmptyOrExceptionWithMessage(Guid value, [return: NotNull] //TODO: Refactor tests for MemberOfOrException public static TEnumType MemberOfOrException(TEnumType value, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName); @@ -224,7 +228,7 @@ public static TEnumType MemberOfOrException(TEnumType value, //TODO: Refactor tests for MemberOfOrExceptionWithMessage public static TEnumType MemberOfOrExceptionWithMessage(TEnumType value, string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TEnumType : Enum + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName, customMessage); #endregion @@ -250,7 +254,7 @@ public static TEnumType MemberOfOrExceptionWithMessage(TEnumType valu public static TComparable LessThanOrException( [NotNull] TComparable? value, [NotNull] TComparable? other, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName); /// @@ -274,7 +278,7 @@ public static TComparable LessThanOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable? other, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName, customMessage); /// @@ -297,7 +301,7 @@ public static TComparable LessThanOrExceptionWithMessage( public static TComparable LessThanOrEqualToOrException( [NotNull] TComparable? value, [NotNull] TComparable? other, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName); /// @@ -321,7 +325,7 @@ public static TComparable LessThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable? other, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName, customMessage); /// @@ -343,7 +347,7 @@ public static TComparable LessThanOrEqualToOrExceptionWithMessage( public static TComparable GreaterThanOrException( [NotNull] TComparable? value, [NotNull] TComparable? other, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName); /// @@ -367,7 +371,7 @@ public static TComparable GreaterThanOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable? other, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName, customMessage); /// @@ -389,8 +393,8 @@ public static TComparable GreaterThanOrExceptionWithMessage( //TODO: Refactor tests for GreaterThanOrEqualToOrException public static TComparable GreaterThanOrEqualToOrException( [NotNull] TComparable? value, - [NotNull] TComparable? other, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull] TComparable? other, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName); @@ -414,9 +418,9 @@ public static TComparable GreaterThanOrEqualToOrException( //TODO: Refactor tests for GreaterThanOrEqualToOrExceptionWithMessage public static TComparable GreaterThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable? other, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName, customMessage); /// @@ -441,7 +445,7 @@ public static TComparable BetweenOrException( [NotNull] TComparable? value, [NotNull] TComparable? fromInclusive, [NotNull] TComparable? toInclusive, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName); /// @@ -469,7 +473,7 @@ public static TComparable BetweenOrExceptionWithMessage( [NotNull] TComparable? fromInclusive, [NotNull] TComparable? toInclusive, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") where TComparable : IComparable + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName, customMessage); #endregion @@ -497,7 +501,7 @@ public static TComparable BetweenOrExceptionWithMessage( [return: NotNull] //TODO: Refactor tests for ValidLuhnChecksum public static string ValidLuhnChecksum([NotNull] string? value, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { (string validParamName, string validCustomMessage) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), @@ -540,7 +544,7 @@ private static int[] ToDigitsArray(string notNullValue) /// If is not a valid Base64 String. [DebuggerStepThrough] //TODO: Refactor tests for ValidBase64 - public static string ValidBase64([NotNull] string? value, [NotNull, CallerArgumentExpression("value")] string paramName = "") + public static string ValidBase64([NotNull] string? value, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); @@ -563,7 +567,7 @@ public static string ValidBase64([NotNull] string? value, [NotNull, CallerArgume [DebuggerStepThrough] //TODO: Refactor tests for ValidBase64 public static string ValidBase64([NotNull] string? value, [NotNull] string customMessage, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { (string validParamName, string validCustomMessage) = (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), @@ -602,7 +606,7 @@ public static TNullable CompliesWith( [NotNull] TNullable? value, [NotNull] Func validator, [NotNull] string preconditionDescription, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, true); @@ -621,7 +625,7 @@ public static TNullable DoesNotComplyWith( [NotNull] TNullable? value, [NotNull] Func validator, [NotNull] string preconditionDescription, - [NotNull, CallerArgumentExpression("value")] string paramName = "") + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, false); @@ -649,3 +653,7 @@ string notNullPreconditionDescription #endregion //General Purpose Checks } + +#if (NETSTANDARD || NETCOREAPP) +#pragma warning restore CS0436 //CallerArgumentExpressionAttribute type conflicts +#endif \ No newline at end of file diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index f0f4fd0..6a6c537 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -2,11 +2,15 @@ namespace Triplex.Validations.ArgumentsHelpers; +#if (NETSTANDARD || NETCOREAPP) +#pragma warning disable CS0436 //CallerArgumentExpressionAttribute type conflicts +#endif + #pragma warning disable CA1303 // Do not pass literals as localized parameters internal static class Extensions { [return: NotNull] - internal static T ValueOrThrowIfNull([NotNull] this T? value, + internal static T ValueOrThrowIfNull([NotNull] this T? value, [CallerArgumentExpression("value")] string paramName = "") => value ?? throw new ArgumentNullException(paramName); @@ -121,3 +125,7 @@ internal static TType[] ValueOrThrowIfNullOrWithLessThanElements( } } #pragma warning restore CA1303 // Do not pass literals as localized parameters + +#if (NETSTANDARD || NETCOREAPP) +#pragma warning restore CS0436 //CallerArgumentExpressionAttribute type conflicts +#endif \ No newline at end of file diff --git a/src/Validations/State.cs b/src/Validations/State.cs index 00620cf..3caf1db 100644 --- a/src/Validations/State.cs +++ b/src/Validations/State.cs @@ -2,6 +2,10 @@ namespace Triplex.Validations; +#if (NETSTANDARD || NETCOREAPP) +#pragma warning disable CS0436 //CallerArgumentExpressionAttribute type conflicts +#endif + /// /// Object internal state checks. Use it to check Preconditions and Invariants. /// Always throw or some derivative. @@ -106,3 +110,7 @@ public static void StillNotHolds(bool invariant, [NotNull] string message) #endregion //Invariants } + +#if (NETSTANDARD || NETCOREAPP) +#pragma warning restore CS0436 //CallerArgumentExpressionAttribute type conflicts +#endif \ No newline at end of file diff --git a/src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs b/src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs new file mode 100644 index 0000000..871a066 --- /dev/null +++ b/src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs @@ -0,0 +1,42 @@ +namespace System.Runtime.CompilerServices; + +#if (NETSTANDARD || NETCOREAPP) +/// +/// Dummy class to solve compilation bug. +/// See +/// +/// Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported [duplicate] +/// +/// and +/// +/// Error CS0518 Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported +/// +/// +/// +/// Error details: after running dotnet build without this dummy type, got the following compiler error for netstandard2.1 and netcoreapp3.1 +/// +/// Restore complete (0.4s) +/// Validations netstandard2.1 failed with 2 error(s) (0.1s) +/// ~/Validations/Utilities/SimpleOption.cs(29,33): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported +/// ~/Validations/Utilities/SimpleOption.cs(29,45): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported +/// Validations netcoreapp3.1 failed with 2 error(s) (0.1s) +/// ~/Validations/Utilities/SimpleOption.cs(29,33): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported +/// ~/Validations/Utilities/SimpleOption.cs(29,45): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported +/// +/// + +public class IsExternalInit { } + +/// +/// Another type not found within netstandard2.1 and netcoreapp3.1. +/// Credit for hack to Matthew Watson +/// +/// How can I use CallerArgumentExpression with Visual Studio 2022 and .net Standard 2.0 or .net 4.8? +/// +/// +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +internal sealed class CallerArgumentExpressionAttribute(string parameterName) : Attribute +{ + public string ParameterName { get; } = parameterName; +} +#endif \ No newline at end of file diff --git a/src/Validations/Utilities/SimpleOption.cs b/src/Validations/Utilities/SimpleOption.cs index 93598f6..8973057 100644 --- a/src/Validations/Utilities/SimpleOption.cs +++ b/src/Validations/Utilities/SimpleOption.cs @@ -28,7 +28,7 @@ public static SimpleOption None() //NOSONAR /// public record SimpleOption(T Value, bool HasValue) : IEquatable> { - private const int DefaultHashcode = 31; + private const int DefaultHashCode = 31; #pragma warning disable CA1303 // Do not pass literals as localized parameters /// @@ -40,8 +40,8 @@ public record SimpleOption(T Value, bool HasValue) : IEquatable - /// Returns a hash comming from wrapped value or a default value for empty options. + /// Returns a hash coming from wrapped value or a default value for empty options. /// /// - public override int GetHashCode() => HasValue ? ValueOrFailure!.GetHashCode() : DefaultHashcode; + public override int GetHashCode() => HasValue ? ValueOrFailure!.GetHashCode() : DefaultHashCode; } diff --git a/src/Validations/Validations.csproj b/src/Validations/Validations.csproj index f3309e3..99cd14a 100644 --- a/src/Validations/Validations.csproj +++ b/src/Validations/Validations.csproj @@ -3,8 +3,8 @@ 4.0.0 alpha - net5.0;net6.0 - latest + net6.0;net7.0;net8.0;net9.0;netstandard2.1;netcoreapp3.1 + 13.0 Triplex.Validations $(RootNamespace) @@ -25,6 +25,7 @@ true latest AllEnabledByDefault + true git https://github.com/lsolano/triplex diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs index 9f75fae..7f5f496 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs @@ -1,7 +1,7 @@ namespace Triplex.Validations.Tests.ArgumentsFacts; [TestFixture] -internal sealed class OrExceptionMessage +public sealed class OrExceptionMessage { private const string FakeParameterName = "veryFakeParameterName"; @@ -56,7 +56,7 @@ public void Returns_Value_When_No_Exception_For_Strings([Values("", "peter", "sp [Test] public void Returns_Value_When_No_Exception_For_List([Values] bool useCustomParamName) { - List fibonacci = new() { 1, 1, 2, 3, 5 }; + List fibonacci = [1, 1, 2, 3, 5]; Func> listCheck = BuildCheckForNotNull(fibonacci, useCustomParamName); diff --git a/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs index 6f7aba3..ba28cfe 100644 --- a/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs +++ b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs @@ -16,13 +16,13 @@ public static class FluentConstraintsExtensions public static Constraint WithMessageContainsAll(this TypeConstraint exceptionConstraint, IEnumerable expectedParts) { - ConstraintExpression messageExpresion = exceptionConstraint.With; - string[] parts = expectedParts.ToArray(); + ConstraintExpression messageExpression = exceptionConstraint.With; + string[] parts = [.. expectedParts]; for (int i = 0; i < parts.Length - 1; i++) { - messageExpresion = messageExpresion.Message.Contains(parts[i]).And; + messageExpression = messageExpression.Message.Contains(parts[i]).And; } - return messageExpresion.Message.Contains(parts[^1]); + return messageExpression.Message.Contains(parts[^1]); } } diff --git a/tests/unit/Validations.Tests/GlobalUsings.cs b/tests/unit/Validations.Tests/GlobalUsings.cs index 9189f4d..4b16561 100644 --- a/tests/unit/Validations.Tests/GlobalUsings.cs +++ b/tests/unit/Validations.Tests/GlobalUsings.cs @@ -5,4 +5,3 @@ global using NUnit.Framework.Constraints; -global using static System.Environment; diff --git a/tests/unit/Validations.Tests/Validations.Tests.csproj b/tests/unit/Validations.Tests/Validations.Tests.csproj index 617be64..226b683 100644 --- a/tests/unit/Validations.Tests/Validations.Tests.csproj +++ b/tests/unit/Validations.Tests/Validations.Tests.csproj @@ -1,7 +1,7 @@  - net5.0;net6.0 - latest + net6.0;net7.0;net8.0;net9.0 + 13.0 disable true false @@ -13,6 +13,7 @@ latest Default enable + true @@ -20,16 +21,16 @@ - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - - all + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + From 1da191d63c5871d8de7c73c9294d6ca9984db641 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Fri, 16 May 2025 18:13:39 -0400 Subject: [PATCH 07/25] Upgrading actions --- .github/workflows/build.yml | 14 +++++++------- .github/workflows/dotnetcore.yml | 16 ++++++++++------ .github/workflows/nuget_push.yml | 10 +++++++--- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5afafb3..66dc0d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,26 +11,26 @@ jobs: runs-on: windows-latest steps: - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.11 - - uses: actions/checkout@v2 + java-version: 21 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x include-prerelease: false - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~\sonar\cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache SonarCloud scanner id: cache-sonar-scanner - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: .\.sonar\scanner key: ${{ runner.os }}-sonar-scanner diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 2d6166d..56b563b 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -18,21 +18,25 @@ jobs: language: [ 'csharp' ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.100 + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Build and run Unit Tests with dotnet - run: dotnet test --framework net6.0 --configuration Release + run: dotnet test --framework net9.0 --configuration Release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/nuget_push.yml b/.github/workflows/nuget_push.yml index e9db7de..b8929d1 100644 --- a/.github/workflows/nuget_push.yml +++ b/.github/workflows/nuget_push.yml @@ -14,12 +14,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x include-prerelease: false - name: Clean Last Build From 214a280f9264f8f0c897e1e6333bce8157504737 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Fri, 16 May 2025 18:16:13 -0400 Subject: [PATCH 08/25] Upgrading actions --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66dc0d0..f8c901b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 21 + distribution: zulu - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis From 7405247535adc9ebbc6ce89c4c3348e90a60741f Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Fri, 16 May 2025 18:28:49 -0400 Subject: [PATCH 09/25] Refreshing Sonar config --- .github/workflows/build.yml | 87 +++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8c901b..5d2acc1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,63 @@ -name: Analyse Code Quality +# name: Analyse Code Quality +# on: +# push: +# branches: +# - master +# pull_request: +# types: [opened, synchronize, reopened] +# jobs: +# build: +# name: Build +# runs-on: windows-latest +# steps: +# - name: Set up JDK 11 +# uses: actions/setup-java@v4 +# with: +# java-version: 21 +# distribution: zulu +# - uses: actions/checkout@v4 +# with: +# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis +# - name: Setup .NET +# uses: actions/setup-dotnet@v4 +# with: +# dotnet-version: 9.0.x +# include-prerelease: false +# - name: Cache SonarCloud packages +# uses: actions/cache@v4 +# with: +# path: ~\sonar\cache +# key: ${{ runner.os }}-sonar +# restore-keys: ${{ runner.os }}-sonar +# - name: Cache SonarCloud scanner +# id: cache-sonar-scanner +# uses: actions/cache@v4 +# with: +# path: .\.sonar\scanner +# key: ${{ runner.os }}-sonar-scanner +# restore-keys: ${{ runner.os }}-sonar-scanner +# - name: Install SonarCloud scanner +# if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' +# shell: powershell +# run: | +# New-Item -Path .\.sonar\scanner -ItemType Directory +# dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner +# - name: Build and analyze +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any +# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +# DOTNET_ROLL_FORWARD: Major +# shell: powershell +# run: | +# $workingDir=pwd +# .\.sonar\scanner\dotnet-sonarscanner begin /k:"lsolano_triplex" /o:"lsolano" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.dotnet.excludeTestProjects=true /d:sonar.exclusions=tests/**,src/**/GlobalUsings.cs /d:sonar.cs.opencover.reportsPaths="$workingDir/test-results/coverage.net6.0.opencover.xml" +# if ("$GITHUB_REF" -ceq "refs/heads/master") {$configuration="Release"} else {$configuration="Debug"} +# dotnet clean -c "$configuration" +# dotnet build -c "$configuration" +# dotnet test --no-restore -c "$configuration" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2copencover /p:MergeWith="$workingDir/test-results/coverage.json" /p:CoverletOutput="$workingDir/test-results/" +# .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + +name: SonarQube on: push: branches: @@ -7,14 +66,14 @@ on: types: [opened, synchronize, reopened] jobs: build: - name: Build + name: Build and analyze runs-on: windows-latest steps: - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: 21 - distribution: zulu + java-version: 17 + distribution: 'zulu' # Alternative distribution options are available. - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis @@ -23,20 +82,20 @@ jobs: with: dotnet-version: 9.0.x include-prerelease: false - - name: Cache SonarCloud packages + - name: Cache SonarQube Cloud packages uses: actions/cache@v4 with: path: ~\sonar\cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Cache SonarCloud scanner + - name: Cache SonarQube Cloud scanner id: cache-sonar-scanner uses: actions/cache@v4 with: path: .\.sonar\scanner key: ${{ runner.os }}-sonar-scanner restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install SonarCloud scanner + - name: Install SonarQube Cloud scanner if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' shell: powershell run: | @@ -44,15 +103,9 @@ jobs: dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner - name: Build and analyze env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - DOTNET_ROLL_FORWARD: Major shell: powershell run: | - $workingDir=pwd - .\.sonar\scanner\dotnet-sonarscanner begin /k:"lsolano_triplex" /o:"lsolano" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.dotnet.excludeTestProjects=true /d:sonar.exclusions=tests/**,src/**/GlobalUsings.cs /d:sonar.cs.opencover.reportsPaths="$workingDir/test-results/coverage.net6.0.opencover.xml" - if ("$GITHUB_REF" -ceq "refs/heads/master") {$configuration="Release"} else {$configuration="Debug"} - dotnet clean -c "$configuration" - dotnet build -c "$configuration" - dotnet test --no-restore -c "$configuration" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2copencover /p:MergeWith="$workingDir/test-results/coverage.json" /p:CoverletOutput="$workingDir/test-results/" - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + .\.sonar\scanner\dotnet-sonarscanner begin /k:"lsolano_triplex" /o:"lsolano" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + dotnet build + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file From 63b6c919ec96167e01ce713d21e5c043ba9c33af Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Fri, 16 May 2025 18:32:12 -0400 Subject: [PATCH 10/25] Fixing build --- .../unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs index 7f5f496..8ac893e 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs @@ -1,7 +1,7 @@ namespace Triplex.Validations.Tests.ArgumentsFacts; [TestFixture] -public sealed class OrExceptionMessage +internal sealed class OrExceptionMessage { private const string FakeParameterName = "veryFakeParameterName"; From 89b0b3d43d3a3bbc2a648f8356e83e6d2ced2b36 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Wed, 21 May 2025 22:16:41 -0400 Subject: [PATCH 11/25] Build setup --- .github/workflows/build.yml | 10 +++-- .gitignore | 1 + .vscode/settings.json | 7 +++- src/Validations/Arguments.cs | 8 ++-- .../ArgumentsHelpers/Extensions.cs | 4 +- .../ArgumentsHelpers/OutOfRangeChecks.cs | 12 +++--- .../IsExternalInit.cs | 42 ------------------- src/Validations/Utilities/ComparableRange.cs | 2 +- src/Validations/Validations.csproj | 12 +++++- .../CompliesWithUsingLambdaMessageFacts.cs | 10 ++--- .../DoesNotComplyWithMessageFacts.cs | 10 ++--- .../ArgumentsFacts/GreaterThanMessage.cs | 2 +- .../ArgumentsFacts/OrExceptionMessage.cs | 35 ++++++++++------ .../FluentConstraintsExtensions.cs | 2 + .../Validations.Tests/OneTimeSetupFixture.cs | 2 + .../Validations.Tests.csproj | 11 ++++- 16 files changed, 83 insertions(+), 87 deletions(-) delete mode 100644 src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d2acc1..d676959 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,7 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - include-prerelease: false + dotnet-quality: preview - name: Cache SonarQube Cloud packages uses: actions/cache@v4 with: @@ -106,6 +106,10 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"lsolano_triplex" /o:"lsolano" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - dotnet build + $workingDir=pwd + .\.sonar\scanner\dotnet-sonarscanner begin /k:"lsolano_triplex" /o:"lsolano" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.dotnet.excludeTestProjects=true /d:sonar.exclusions=tests/**,src/**/GlobalUsings.cs /d:sonar.cs.opencover.reportsPaths="$workingDir/test-results/coverage.net9.0.opencover.xml" + if ("$GITHUB_REF" -ceq "refs/heads/master") {$configuration="Release"} else {$configuration="Debug"} + dotnet clean -c "$configuration" + dotnet build -c "$configuration" + dotnet test --no-restore -c "$configuration" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2copencover /p:MergeWith="$workingDir/test-results/coverage.json" /p:CoverletOutput="$workingDir/test-results/" .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 85ea2f5..9b365d2 100644 --- a/.gitignore +++ b/.gitignore @@ -357,3 +357,4 @@ MigrationBackup/ *.orig coverage.*.json +*.cobertura.xml diff --git a/.vscode/settings.json b/.vscode/settings.json index f2bbbb0..aaec045 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,10 @@ "**/.project": true, "**/.settings": true, "**/.factorypath": true - } + }, + "cSpell.words": [ + "NETCOREAPP", + "NETSTANDARD", + "Parallelizable" + ] } \ No newline at end of file diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index ca6a979..96b006c 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -11,7 +11,7 @@ namespace Triplex.Validations; /// Utility class used to validate arguments. Useful to check constructor and public methods arguments. /// If checks are violated an instance of is thrown. /// All checks imply an initial Not-Null check for all values checked, -/// so Arguments.OrException(someParam); means "Give 'someParam' value or and exception if it is null." +/// so Arguments.OrException(someParam); means "Give 'someParam' value back or throw exception if it is ." /// public static class Arguments { @@ -138,7 +138,7 @@ public static string NotEmptyOrExceptionWithMessage( #endregion - #region Emptyness + #region Emptiness /// /// Checks that the provided value is not empty. /// @@ -190,7 +190,7 @@ public static Guid NotEmptyOrExceptionWithMessage(Guid value, private static bool IsEmpty(Guid value) => value == default; - #endregion //Emptyness + #endregion //Emptiness #region Enumerations Checks @@ -587,8 +587,6 @@ private static bool IsBase64String(string base64) #endregion // Known Encodings - - #region General Purpose Checks /// diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index 6a6c537..1a678eb 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -11,7 +11,7 @@ internal static class Extensions { [return: NotNull] internal static T ValueOrThrowIfNull([NotNull] this T? value, - [CallerArgumentExpression("value")] string paramName = "") + [CallerArgumentExpression(nameof(value))] string paramName = "") => value ?? throw new ArgumentNullException(paramName); [return: NotNull] @@ -77,7 +77,7 @@ internal static string ValueOrThrowIfNullOrZeroLength([NotNull] this string? val [return: NotNull] internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly( [NotNull] this string? value, - [CallerArgumentExpression("value")] string paramName = "") + [CallerArgumentExpression(nameof(value))] string paramName = "") => ValueOrThrowIfNull(value, paramName) .ValueOrThrowIfZeroLength(paramName) .ValueOrThrowIfWhiteSpaceOnly(paramName); diff --git a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs index f28cd33..f64377f 100644 --- a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs +++ b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs @@ -98,12 +98,12 @@ internal static TComparable Between( [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = new( - SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull(nameof(fromInclusive))), - SimpleOption.SomeNotNull(toInclusive.ValueOrThrowIfNull(nameof(toInclusive)))); + SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull()), + SimpleOption.SomeNotNull(toInclusive.ValueOrThrowIfNull())); - string notNullParamName = paramName.ValueOrThrowIfNull(nameof(paramName)); + string notNullParamName = paramName.ValueOrThrowIfNull(); - return range.IsWithin( + return range.Contains( value.ValueOrThrowIfNull(notNullParamName), notNullParamName, customMessage: null!); @@ -122,7 +122,7 @@ internal static TComparable Between( SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull(nameof(fromInclusive))), SimpleOption.SomeNotNull(toInclusive.ValueOrThrowIfNull(nameof(toInclusive)))); - return range.IsWithin( + return range.Contains( value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(customMessage)), customMessage.ValueOrThrowIfNull(nameof(customMessage))); @@ -136,7 +136,7 @@ private static TComparable CheckBoundaries( [NotNull] string paramName, string? customMessage) where TComparable : IComparable { - return range.IsWithin(value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(paramName)), + return range.Contains(value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(paramName)), customMessage); } } diff --git a/src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs b/src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs deleted file mode 100644 index 871a066..0000000 --- a/src/Validations/System.Runtime.CompilerServices/IsExternalInit.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace System.Runtime.CompilerServices; - -#if (NETSTANDARD || NETCOREAPP) -/// -/// Dummy class to solve compilation bug. -/// See -/// -/// Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported [duplicate] -/// -/// and -/// -/// Error CS0518 Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported -/// -/// -/// -/// Error details: after running dotnet build without this dummy type, got the following compiler error for netstandard2.1 and netcoreapp3.1 -/// -/// Restore complete (0.4s) -/// Validations netstandard2.1 failed with 2 error(s) (0.1s) -/// ~/Validations/Utilities/SimpleOption.cs(29,33): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported -/// ~/Validations/Utilities/SimpleOption.cs(29,45): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported -/// Validations netcoreapp3.1 failed with 2 error(s) (0.1s) -/// ~/Validations/Utilities/SimpleOption.cs(29,33): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported -/// ~/Validations/Utilities/SimpleOption.cs(29,45): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported -/// -/// - -public class IsExternalInit { } - -/// -/// Another type not found within netstandard2.1 and netcoreapp3.1. -/// Credit for hack to Matthew Watson -/// -/// How can I use CallerArgumentExpression with Visual Studio 2022 and .net Standard 2.0 or .net 4.8? -/// -/// -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] -internal sealed class CallerArgumentExpressionAttribute(string parameterName) : Attribute -{ - public string ParameterName { get; } = parameterName; -} -#endif \ No newline at end of file diff --git a/src/Validations/Utilities/ComparableRange.cs b/src/Validations/Utilities/ComparableRange.cs index 47481a5..bec4501 100644 --- a/src/Validations/Utilities/ComparableRange.cs +++ b/src/Validations/Utilities/ComparableRange.cs @@ -98,7 +98,7 @@ private static void ThrowIfRequiredEndIsMissing(SimpleOption min, b private bool MinInclusive { get; } private bool MaxInclusive { get; } - internal TComparable IsWithin(TComparable value, string paramName, string? customMessage) + internal TComparable Contains(TComparable value, string paramName, string? customMessage) { CheckLowerBoundary(value, paramName, customMessage); diff --git a/src/Validations/Validations.csproj b/src/Validations/Validations.csproj index 99cd14a..b187cd6 100644 --- a/src/Validations/Validations.csproj +++ b/src/Validations/Validations.csproj @@ -1,9 +1,9 @@  4.0.0 - alpha + preview - net6.0;net7.0;net8.0;net9.0;netstandard2.1;netcoreapp3.1 + net9.0 13.0 Triplex.Validations @@ -26,6 +26,7 @@ latest AllEnabledByDefault true + true git https://github.com/lsolano/triplex @@ -74,6 +75,13 @@ Improvements + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + true diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithUsingLambdaMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithUsingLambdaMessageFacts.cs index 53c8041..9569ab1 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithUsingLambdaMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithUsingLambdaMessageFacts.cs @@ -20,7 +20,7 @@ public void Returns_Same_Instance() string notNullValue = Arguments.CompliesWith(someString, val => val.Length > 2, nameof(someString), PreconditionDescription); - Assert.That(someString, Is.SameAs(notNullValue)); + Assert.That(notNullValue, Is.SameAs(someString)); } [Test] @@ -35,23 +35,23 @@ public void With_False_Throws_ArgumentException() } [Test] - public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string paramName, + public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string? paramName, [Values] bool precondition) { const string? someString = "Hello World 1235"; - Assert.That(() => Arguments.CompliesWith(someString, val => precondition, paramName, PreconditionDescription), + Assert.That(() => Arguments.CompliesWith(someString, val => precondition, paramName!, PreconditionDescription), Throws.InstanceOf() .With.Property(nameof(ArgumentException.ParamName)).EqualTo("paramName")); } [Test] public void With_Invalid_Description_ParamName_Throws_ArgumentException( - [Values(null, "", " ", "\n\r\t ")] string description, [Values] bool precondition) + [Values(null, "", " ", "\n\r\t ")] string? description, [Values] bool precondition) { const string? someString = "Hello World 1235"; - Assert.That(() => Arguments.CompliesWith(someString, val => precondition, nameof(someString), description), + Assert.That(() => Arguments.CompliesWith(someString, val => precondition, nameof(someString), description!), Throws.InstanceOf() .With.Property(nameof(ArgumentException.ParamName)).EqualTo("preconditionDescription")); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/DoesNotComplyWithMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/DoesNotComplyWithMessageFacts.cs index 36b268e..c6d96ba 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/DoesNotComplyWithMessageFacts.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/DoesNotComplyWithMessageFacts.cs @@ -20,7 +20,7 @@ public void Returns_Same_Instance() string notNullValue = Arguments.DoesNotComplyWith(someString, val => val.Length == 0, nameof(someString), PreconditionDescription); - Assert.That(someString, Is.SameAs(notNullValue)); + Assert.That(notNullValue, Is.SameAs(someString)); } [Test] @@ -35,12 +35,12 @@ public void With_True_Throws_ArgumentException() } [Test] - public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string paramName, + public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string? paramName, [Values] bool precondition) { const string? someString = "Hello World 1235"; - Assert.That(() => Arguments.DoesNotComplyWith(someString, val => precondition, paramName, + Assert.That(() => Arguments.DoesNotComplyWith(someString, val => precondition, paramName!, PreconditionDescription), Throws.InstanceOf() .With.Property(nameof(ArgumentException.ParamName)).EqualTo("paramName")); @@ -48,11 +48,11 @@ public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " [Test] public void With_Invalid_Description_ParamName_Throws_ArgumentException( - [Values(null, "", " ", "\n\r\t ")] string description, [Values] bool precondition) + [Values(null, "", " ", "\n\r\t ")] string? description, [Values] bool precondition) { const string? someString = "Hello World 1235"; - Assert.That(() => Arguments.DoesNotComplyWith(someString, val => precondition, nameof(someString), description), + Assert.That(() => Arguments.DoesNotComplyWith(someString, val => precondition, nameof(someString), description!), Throws.InstanceOf() .With.Property(nameof(ArgumentException.ParamName)).EqualTo("preconditionDescription")); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs index 065015e..19dd3bf 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs @@ -15,7 +15,7 @@ public GreaterThanMessage(bool useCustomErrorMessage) : base(useCustomErrorMessa [TestCase(2, -1)] public void With_Valid_Integers_Throws_Nothing(int theValue, int other) { - int validatedValue = Arguments.GreaterThanOrExceptionWithMessage(theValue, other, nameof(theValue), CustomError); + int validatedValue = GreaterThan(theValue, other, nameof(theValue), CustomError, UseCustomErrorMessage); Assert.That(validatedValue, Is.EqualTo(theValue)); } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs index 8ac893e..ee289be 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs @@ -14,11 +14,17 @@ public void With_Null_Throws_ArgumentNullException_Using_Correct_Param_Name([Val () => Arguments.OrException(email, FakeParameterName) : () => Arguments.OrException(email); - string finalParamName = useCustomParamName ? FakeParameterName : nameof(email); + ArgumentNullException? exception = Assert.Throws(() => act()); - Assert.That(() => act(), - Throws.ArgumentNullException.With - .Property(nameof(ArgumentNullException.ParamName)).EqualTo(finalParamName)); + string finalParamName = useCustomParamName ? FakeParameterName : nameof(email); + (string messagePartOne, string messagePartTwo) = ExceptionMessagePartsFor(paramName: finalParamName); + + using (Assert.EnterMultipleScope()) + { + Assert.That(exception, Has.Property(nameof(ArgumentException.ParamName)).EqualTo(finalParamName)); + Assert.That(exception, Has.Property(nameof(ArgumentException.Message)).Contains(messagePartOne)); + Assert.That(exception, Has.Property(nameof(ArgumentException.Message)).Contains(messagePartTwo)); + } } [Test] @@ -31,11 +37,17 @@ public void With_Null_Throws_ArgumentNullException_Using_Correct_Message([Values () => Arguments.OrException(user, FakeParameterName) : () => Arguments.OrException(user); - IEnumerable exceptionParts = ExceptionMessagePartsFor(paramName: finalParamName); + ArgumentNullException? exception = Assert.Throws(() => act()); - Assert.That(() => act(), Throws.ArgumentNullException.WithMessageContainsAll(exceptionParts)); - } + (string messagePartOne, string messagePartTwo) = ExceptionMessagePartsFor(paramName: finalParamName); + using (Assert.EnterMultipleScope()) + { + Assert.That(exception, Has.Property(nameof(ArgumentException.Message)).Contains(messagePartOne)); + Assert.That(exception, Has.Property(nameof(ArgumentException.Message)).Contains(messagePartTwo)); + } + } + [Test] public void With_Peter_Throws_Nothing([Values] bool useCustomParamName) { @@ -62,7 +74,7 @@ public void Returns_Value_When_No_Exception_For_List([Values] bool useCustomPara Assert.That(listCheck(), Is.SameAs(fibonacci)); } - + private static Func BuildCheckForNotNull(T notNullValue, bool useCustomParamName) where T : class { return useCustomParamName ? @@ -70,9 +82,6 @@ private static Func BuildCheckForNotNull(T notNullValue, bool useCustomPar () => Arguments.OrException(notNullValue); } - private static IEnumerable ExceptionMessagePartsFor(string paramName) - { - yield return "Value cannot be null."; - yield return "Parameter '{paramName}'".Replace("{paramName}", paramName); - } + private static (string first, string second) ExceptionMessagePartsFor(string paramName) + => ("Value cannot be null.", "Parameter '{paramName}'".Replace("{paramName}", paramName)); } diff --git a/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs index ba28cfe..4d55ed8 100644 --- a/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs +++ b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs @@ -1,3 +1,5 @@ +[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + namespace Triplex.Validations.Tests; /// diff --git a/tests/unit/Validations.Tests/OneTimeSetupFixture.cs b/tests/unit/Validations.Tests/OneTimeSetupFixture.cs index 9317562..3e2a40f 100644 --- a/tests/unit/Validations.Tests/OneTimeSetupFixture.cs +++ b/tests/unit/Validations.Tests/OneTimeSetupFixture.cs @@ -3,7 +3,9 @@ /// [SetUpFixture] [Parallelizable(scope: ParallelScope.All)] +#pragma warning disable CA1050 // Declare types in namespaces public class OneTimeSetupFixture +#pragma warning restore CA1050 // Declare types in namespaces { /// /// Setup fixture diff --git a/tests/unit/Validations.Tests/Validations.Tests.csproj b/tests/unit/Validations.Tests/Validations.Tests.csproj index 226b683..a091a00 100644 --- a/tests/unit/Validations.Tests/Validations.Tests.csproj +++ b/tests/unit/Validations.Tests/Validations.Tests.csproj @@ -1,6 +1,6 @@  - net6.0;net7.0;net8.0;net9.0 + net9.0 13.0 disable true @@ -29,8 +29,17 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + From 67a25d9f5cefb4569b097d6d8af5d3f2cf48a6a4 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Thu, 22 May 2025 11:44:32 -0400 Subject: [PATCH 12/25] Solving Sonar issues and using GeneratedRegex --- .github/workflows/build.yml | 61 +----------------------------------- README.md | 2 +- src/Validations/Arguments.cs | 56 ++++++++++++++------------------- 3 files changed, 26 insertions(+), 93 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d676959..e2616f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,62 +1,3 @@ -# name: Analyse Code Quality -# on: -# push: -# branches: -# - master -# pull_request: -# types: [opened, synchronize, reopened] -# jobs: -# build: -# name: Build -# runs-on: windows-latest -# steps: -# - name: Set up JDK 11 -# uses: actions/setup-java@v4 -# with: -# java-version: 21 -# distribution: zulu -# - uses: actions/checkout@v4 -# with: -# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis -# - name: Setup .NET -# uses: actions/setup-dotnet@v4 -# with: -# dotnet-version: 9.0.x -# include-prerelease: false -# - name: Cache SonarCloud packages -# uses: actions/cache@v4 -# with: -# path: ~\sonar\cache -# key: ${{ runner.os }}-sonar -# restore-keys: ${{ runner.os }}-sonar -# - name: Cache SonarCloud scanner -# id: cache-sonar-scanner -# uses: actions/cache@v4 -# with: -# path: .\.sonar\scanner -# key: ${{ runner.os }}-sonar-scanner -# restore-keys: ${{ runner.os }}-sonar-scanner -# - name: Install SonarCloud scanner -# if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' -# shell: powershell -# run: | -# New-Item -Path .\.sonar\scanner -ItemType Directory -# dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner -# - name: Build and analyze -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any -# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -# DOTNET_ROLL_FORWARD: Major -# shell: powershell -# run: | -# $workingDir=pwd -# .\.sonar\scanner\dotnet-sonarscanner begin /k:"lsolano_triplex" /o:"lsolano" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.dotnet.excludeTestProjects=true /d:sonar.exclusions=tests/**,src/**/GlobalUsings.cs /d:sonar.cs.opencover.reportsPaths="$workingDir/test-results/coverage.net6.0.opencover.xml" -# if ("$GITHUB_REF" -ceq "refs/heads/master") {$configuration="Release"} else {$configuration="Debug"} -# dotnet clean -c "$configuration" -# dotnet build -c "$configuration" -# dotnet test --no-restore -c "$configuration" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2copencover /p:MergeWith="$workingDir/test-results/coverage.json" /p:CoverletOutput="$workingDir/test-results/" -# .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" - name: SonarQube on: push: @@ -111,5 +52,5 @@ jobs: if ("$GITHUB_REF" -ceq "refs/heads/master") {$configuration="Release"} else {$configuration="Debug"} dotnet clean -c "$configuration" dotnet build -c "$configuration" - dotnet test --no-restore -c "$configuration" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2copencover /p:MergeWith="$workingDir/test-results/coverage.json" /p:CoverletOutput="$workingDir/test-results/" + dotnet test --no-restore -c "$configuration" /p:ExcludeByAttribute=GeneratedCode /p:CollectCoverage=true /p:CoverletOutputFormat=json%2copencover /p:MergeWith="$workingDir/test-results/coverage.json" /p:CoverletOutput="$workingDir/test-results/" .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file diff --git a/README.md b/README.md index e418e76..6605914 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ We like to get help from everybody, if you want to contribute to this tool, foun 1. Fork + Clone this repo 2. Build and Run Tests locally 1. Build only `dotnet build` - 2. Full clean, build and test `dotnet clean && dotnet test /p:CollectCoverage=true` + 2. Full clean, build and test `dotnet clean && dotnet test /p:ExcludeByAttribute=GeneratedCode /p:CollectCoverage=true` 3. Incremental build and test (regular dev workflow) 1. `dotnet test /p:CollectCoverage=true` diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index 96b006c..088b40e 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -3,17 +3,13 @@ namespace Triplex.Validations; -#if (NETSTANDARD || NETCOREAPP) -#pragma warning disable CS0436 //CallerArgumentExpressionAttribute type conflicts -#endif - /// /// Utility class used to validate arguments. Useful to check constructor and public methods arguments. /// If checks are violated an instance of is thrown. /// All checks imply an initial Not-Null check for all values checked, /// so Arguments.OrException(someParam); means "Give 'someParam' value back or throw exception if it is ." /// -public static class Arguments +public static partial class Arguments { #region Null and Empty Checks /// @@ -118,27 +114,6 @@ public static string NotEmptyOrException( [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); - /// - /// Checks that the provided value is not or empty (zero length). - /// - /// Value to check - /// Parameter's name, can not be - /// Custom message, can not be - /// - /// If any parameter is . - /// If length is zero. - [DebuggerStepThrough] - [return: NotNull] - //TODO: Refactor tests for NotEmptyOrExceptionWithMessage - public static string NotEmptyOrExceptionWithMessage( - [NotNull] string? value, - [NotNull] string customMessage, - [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") - => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); - - #endregion - - #region Emptiness /// /// Checks that the provided value is not empty. /// @@ -157,6 +132,24 @@ public static Guid NotEmptyOrException(Guid value, } /// + /// Checks that the provided value is not or empty (zero length). + /// + /// Value to check + /// Parameter's name, can not be + /// Custom message, can not be + /// + /// If any parameter is . + /// If length is zero. + [DebuggerStepThrough] + [return: NotNull] + //TODO: Refactor tests for NotEmptyOrExceptionWithMessage + public static string NotEmptyOrExceptionWithMessage( + [NotNull] string? value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") + => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); + + /// /// Checks that the provided value is not empty. /// /// @@ -190,7 +183,7 @@ public static Guid NotEmptyOrExceptionWithMessage(Guid value, private static bool IsEmpty(Guid value) => value == default; - #endregion //Emptiness + #endregion #region Enumerations Checks @@ -480,7 +473,7 @@ public static TComparable BetweenOrExceptionWithMessage( #region Checksum algorithms - private static readonly Regex LuhnDigitsRegex = new("[0-9]{2}", RegexOptions.Compiled); + private static readonly Regex LuhnDigitsRegex = TwoOrMoreDigitsRegex(); /// /// Validates that the given argument () has a valid checksum digit as described by the @@ -649,9 +642,8 @@ string notNullPreconditionDescription return notNullValue; } + [GeneratedRegex("[0-9]{2}"), ExcludeFromCodeCoverage] + private static partial Regex TwoOrMoreDigitsRegex(); + #endregion //General Purpose Checks } - -#if (NETSTANDARD || NETCOREAPP) -#pragma warning restore CS0436 //CallerArgumentExpressionAttribute type conflicts -#endif \ No newline at end of file From dfd813421e9ca53241a600dddae946406c1f966e Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 16:36:57 -0400 Subject: [PATCH 13/25] Fix Sonar issues and refactor range checks --- src/Validations/Arguments.cs | 26 +++++++++---------- .../ArgumentsHelpers/Extensions.cs | 15 +++++------ .../ArgumentsHelpers/OutOfRangeChecks.cs | 24 ++++++++--------- src/Validations/Validations.csproj | 12 +++------ .../ArgumentsFacts/GreaterThanMessage.cs | 4 +-- .../GreaterThanOrEqualToMessage.cs | 4 +-- .../LessThanOrEqualToMessage.cs | 4 +-- 7 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index 088b40e..f90c3ae 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -246,7 +246,7 @@ public static TEnumType MemberOfOrExceptionWithMessage(TEnumType valu //TODO: Refactor tests for LessThanOrException public static TComparable LessThanOrException( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName); @@ -269,7 +269,7 @@ public static TComparable LessThanOrException( //TODO: Refactor tests for LessThanOrExceptionWithMessage public static TComparable LessThanOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName, customMessage); @@ -293,7 +293,7 @@ public static TComparable LessThanOrExceptionWithMessage( //TODO: Refactor tests for LessThanOrEqualToOrException public static TComparable LessThanOrEqualToOrException( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName); @@ -316,7 +316,7 @@ public static TComparable LessThanOrEqualToOrException( //TODO: Refactor tests for LessThanOrEqualToOrExceptionWithMessage public static TComparable LessThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName, customMessage); @@ -339,7 +339,7 @@ public static TComparable LessThanOrEqualToOrExceptionWithMessage( //TODO: Refactor tests for GreaterThanOrException public static TComparable GreaterThanOrException( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName); @@ -362,7 +362,7 @@ public static TComparable GreaterThanOrException( //TODO: Refactor tests for GreaterThanOrExceptionWithMessage public static TComparable GreaterThanOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName, customMessage); @@ -386,7 +386,7 @@ public static TComparable GreaterThanOrExceptionWithMessage( //TODO: Refactor tests for GreaterThanOrEqualToOrException public static TComparable GreaterThanOrEqualToOrException( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName); @@ -411,7 +411,7 @@ public static TComparable GreaterThanOrEqualToOrException( //TODO: Refactor tests for GreaterThanOrEqualToOrExceptionWithMessage public static TComparable GreaterThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? other, + [NotNull] TComparable other, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName, customMessage); @@ -436,8 +436,8 @@ public static TComparable GreaterThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? fromInclusive, - [NotNull] TComparable? toInclusive, + [NotNull] TComparable fromInclusive, + [NotNull] TComparable toInclusive, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName); @@ -463,8 +463,8 @@ public static TComparable BetweenOrException( //TODO: Refactor tests for BetweenOrExceptionWithMessage public static TComparable BetweenOrExceptionWithMessage( [NotNull] TComparable? value, - [NotNull] TComparable? fromInclusive, - [NotNull] TComparable? toInclusive, + [NotNull] TComparable fromInclusive, + [NotNull] TComparable toInclusive, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName, customMessage); @@ -520,7 +520,7 @@ private static void ValidateLuhnSequenceFormat(string notNullValue, string valid private static int[] ToDigitsArray(string notNullValue) { const int zeroAsciiCode = '0'; - return notNullValue.Select(ch => ch - zeroAsciiCode).ToArray(); + return [.. notNullValue.Select(ch => ch - zeroAsciiCode)]; } #endregion diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index 1a678eb..0c1daef 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -15,14 +15,7 @@ internal static T ValueOrThrowIfNull([NotNull] this T? value, => value ?? throw new ArgumentNullException(paramName); [return: NotNull] - internal static T ValueOrThrowInvalidOperationIfNull([NotNull] this T? stateElement, - string elementName) - => stateElement - ?? throw new InvalidOperationException($"Operation not allowed when {elementName} is null."); - - [return: NotNull] - internal static T ValueOrThrowIfNull([NotNull] this T? value, string paramName, - string customMessage) + internal static T ValueOrThrowIfNull([NotNull] this T? value, string paramName, string customMessage) { if (value is not null) { @@ -32,6 +25,12 @@ internal static T ValueOrThrowIfNull([NotNull] this T? value, string paramNam throw new ArgumentNullException(paramName, customMessage); } + [return: NotNull] + internal static T ValueOrThrowInvalidOperationIfNull([NotNull] this T? stateElement, + string elementName) + => stateElement + ?? throw new InvalidOperationException($"Operation not allowed when {elementName} is null."); + [return: NotNull] internal static string ValueOrThrowIfZeroLength(this string value, string paramName) => ValueOrThrowIfZeroLength(value, paramName, "Can not be empty (zero length)."); diff --git a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs index f64377f..81550c4 100644 --- a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs +++ b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs @@ -4,7 +4,7 @@ internal static class OutOfRangeChecks { [return: NotNull] internal static TComparable LessThan([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName) + [NotNull] TComparable other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxExclusiveOnly( @@ -14,7 +14,7 @@ internal static TComparable LessThan([NotNull] TComparable? value, [return: NotNull] internal static TComparable LessThan([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] TComparable other, [NotNull] string paramName, [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxExclusiveOnly( @@ -25,7 +25,7 @@ internal static TComparable LessThan([NotNull] TComparable? value, [return: NotNull] internal static TComparable LessThanOrEqualTo([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName) + [NotNull] TComparable other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMaxInclusiveOnly( @@ -36,7 +36,7 @@ internal static TComparable LessThanOrEqualTo([NotNull] TComparable [return: NotNull] internal static TComparable LessThanOrEqualTo([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] TComparable other, [NotNull] string paramName, [NotNull] string customMessage) where TComparable : IComparable { @@ -48,7 +48,7 @@ internal static TComparable LessThanOrEqualTo([NotNull] TComparable [return: NotNull] internal static TComparable GreaterThan([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName) + [NotNull] TComparable other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinExclusiveOnly( @@ -59,7 +59,7 @@ internal static TComparable GreaterThan([NotNull] TComparable? valu [return: NotNull] internal static TComparable GreaterThan([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] TComparable other, [NotNull] string paramName, [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinExclusiveOnly( @@ -70,7 +70,7 @@ internal static TComparable GreaterThan([NotNull] TComparable? valu [return: NotNull] internal static TComparable GreaterThanOrEqualTo([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName) + [NotNull] TComparable other, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( @@ -81,7 +81,7 @@ internal static TComparable GreaterThanOrEqualTo([NotNull] TCompara [return: NotNull] internal static TComparable GreaterThanOrEqualTo([NotNull] TComparable? value, - [NotNull] TComparable? other, [NotNull] string paramName, + [NotNull] TComparable other, [NotNull] string paramName, [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( @@ -93,8 +93,8 @@ internal static TComparable GreaterThanOrEqualTo([NotNull] TCompara [return: NotNull] internal static TComparable Between( [NotNull] TComparable? value, - [NotNull] TComparable? fromInclusive, - [NotNull] TComparable? toInclusive, + [NotNull] TComparable fromInclusive, + [NotNull] TComparable toInclusive, [NotNull] string paramName) where TComparable : IComparable { ComparableRange range = new( @@ -113,8 +113,8 @@ internal static TComparable Between( [return: NotNull] internal static TComparable Between( [NotNull] TComparable? value, - [NotNull] TComparable? fromInclusive, - [NotNull] TComparable? toInclusive, + [NotNull] TComparable fromInclusive, + [NotNull] TComparable toInclusive, [NotNull] string paramName, [NotNull] string customMessage) where TComparable : IComparable { diff --git a/src/Validations/Validations.csproj b/src/Validations/Validations.csproj index b187cd6..2b1f1ee 100644 --- a/src/Validations/Validations.csproj +++ b/src/Validations/Validations.csproj @@ -46,12 +46,8 @@ Breaking Changes - - Obsolete members from Triplex.Validations.Arguments: - - string NotNullEmptyOrWhiteSpaceOnly(in string?, in string) - - string NotNullEmptyOrWhiteSpaceOnly(in string?, in string, in string) - - string NotNullOrEmpty(in string?, in string) - - string NotNullOrEmpty(in string?, in string, in string) - - void CompliesWith(in bool, in string, in string) + - Obsolete members removed + - Out-of-range Checks signature now do not allow null on other, min, and max parameters Features - Introduce implicit null check for NotNullEmptyOrWhiteSpaceOnly (now NotEmptyOrWhiteSpaceOnly) and NotNullOrEmpty (now NotEmpty). @@ -61,9 +57,9 @@ Features - Remove small quality gate status badge and leave only the big one (#16) Improvements - - Migrate to .NET 6 + - Migrate to .NET 9 - Increase test coverage to 100 percent - - Analyse project using sonarcloud.io (see https://sonarcloud.io/project/overview?id=lsolano_triplex) + - Analyze project using sonarcloud.io (see https://sonarcloud.io/project/overview?id=lsolano_triplex) diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs index 19dd3bf..d217373 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanMessage.cs @@ -120,7 +120,7 @@ private static TComparable GreaterThan( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.GreaterThanOrExceptionWithMessage(value, other, customError!, paramName!) - : Arguments.GreaterThanOrException(value, other, paramName!); + ? Arguments.GreaterThanOrExceptionWithMessage(value, other!, customError!, paramName!) + : Arguments.GreaterThanOrException(value, other!, paramName!); } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs index 4a147ac..342da22 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/GreaterThanOrEqualToMessage.cs @@ -127,7 +127,7 @@ private static TComparable GreaterThanOrEqualTo( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.GreaterThanOrEqualToOrExceptionWithMessage(value, other, customError, paramName) - : Arguments.GreaterThanOrEqualToOrException(value, other, paramName); + ? Arguments.GreaterThanOrEqualToOrExceptionWithMessage(value, other!, customError, paramName) + : Arguments.GreaterThanOrEqualToOrException(value, other!, paramName); } } diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs index c33eff5..892a7ee 100644 --- a/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs +++ b/tests/unit/Validations.Tests/ArgumentsFacts/LessThanOrEqualToMessage.cs @@ -126,7 +126,7 @@ private static TComparable LessThanOrEqualTo( bool useCustomErrorMessage) where TComparable : IComparable { return useCustomErrorMessage - ? Arguments.LessThanOrEqualToOrExceptionWithMessage(value, other, customError!, paramName!) - : Arguments.LessThanOrEqualToOrException(value, other, paramName!); + ? Arguments.LessThanOrEqualToOrExceptionWithMessage(value, other!, customError!, paramName!) + : Arguments.LessThanOrEqualToOrException(value, other!, paramName!); } } From 6e97c06d798e82defde0de038a9c4f252b1fd6b5 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 16:42:16 -0400 Subject: [PATCH 14/25] Update Quality Gate badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6605914..8607581 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Validation library inspired by the concepts of ***Secure by Design***, by Dan Be ## Project's Code Health ## ### Overall ### -[![Quality Gate](https://sonarcloud.io/api/project_badges/quality_gate?project=lsolano_triplex&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=lsolano_triplex) ### Ratings ### [![SQALE Rating](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=sqale_rating&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) [![SQALE Index](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=sqale_index&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=lsolano_triplex&metric=reliability_rating&branch=master)](https://sonarcloud.io/dashboard?id=lsolano_triplex) From 6f544dcb896d2c5fbfe3cfba7e534f78ba902ec9 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 16:54:22 -0400 Subject: [PATCH 15/25] Upgrading actions --- .github/workflows/build.yml | 2 +- .github/workflows/dotnetcore.yml | 7 ++----- .github/workflows/nuget_push.yml | 15 ++++++--------- .github/workflows/nuget_push_on_demand.yml | 14 +++++++------- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2616f8..9528851 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: SonarQube +name: Build and SonarQube on: push: branches: diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 56b563b..7cd0a4c 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -23,11 +23,8 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - 9.0.x + dotnet-version: 9.0.x + dotnet-quality: preview # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/nuget_push.yml b/.github/workflows/nuget_push.yml index b8929d1..1f00e8f 100644 --- a/.github/workflows/nuget_push.yml +++ b/.github/workflows/nuget_push.yml @@ -7,6 +7,7 @@ on: - '1.*' - '2.*' - '3.*' + - '4.*' jobs: build: @@ -19,15 +20,11 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - 9.0.x - include-prerelease: false + dotnet-version: 9.0.x + dotnet-quality: preview - name: Clean Last Build - run: dotnet clean --framework net6.0 --configuration Release + run: dotnet clean --framework net9.0 --configuration Release - name: Clean old Packages run: rm --force src/Validations/bin/Release/Triplex.Validations*.nupkg @@ -36,10 +33,10 @@ jobs: run: dotnet restore - name: Build - run: dotnet build --framework net6.0 --configuration Release + run: dotnet build --framework net9.0 --configuration Release - name: Test - run: dotnet test --no-build --verbosity normal --framework net6.0 --configuration Release + run: dotnet test --no-build --verbosity normal --framework net9.0 --configuration Release - name: Package run: dotnet pack --configuration Release diff --git a/.github/workflows/nuget_push_on_demand.yml b/.github/workflows/nuget_push_on_demand.yml index 86b27be..7da19b4 100644 --- a/.github/workflows/nuget_push_on_demand.yml +++ b/.github/workflows/nuget_push_on_demand.yml @@ -9,16 +9,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x - include-prerelease: false + dotnet-version: 9.0.x + dotnet-quality: preview - name: Clean Last Build - run: dotnet clean --framework net6.0 --configuration Release + run: dotnet clean --framework net9.0 --configuration Release - name: Clean old Packages run: rm --force src/Validations/bin/Release/Triplex.Validations*.nupkg @@ -27,10 +27,10 @@ jobs: run: dotnet restore - name: Build - run: dotnet build --framework net6.0 --configuration Release + run: dotnet build --framework net9.0 --configuration Release - name: Test - run: dotnet test --no-build --verbosity normal --framework net6.0 --configuration Release + run: dotnet test --no-build --verbosity normal --framework net9.0 --configuration Release - name: Package run: dotnet pack --configuration Release From f0257f3ccf7aa3fd3bd57d632b408567d38a7e00 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 17:06:43 -0400 Subject: [PATCH 16/25] Adding .editorconfig --- .editorconfig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ad11b2a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = always_for_clarity +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +csharp_style_namespace_declarations = file_scoped \ No newline at end of file From ac82b20561ee463378a300ad1830422637470a39 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 17:12:37 -0400 Subject: [PATCH 17/25] Configuring code style --- .editorconfig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index ad11b2a..c45aa79 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,4 +17,8 @@ dotnet_style_parentheses_in_other_binary_operators = always_for_clarity dotnet_style_parentheses_in_other_operators = always_for_clarity dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity -csharp_style_namespace_declarations = file_scoped \ No newline at end of file +csharp_style_namespace_declarations = file_scoped + +# Expression-bodied members +csharp_style_expression_bodied_local_functions = true +csharp_style_expression_bodied_methods = true \ No newline at end of file From 5efd2af1bbce3dcbc0b14e116634fda6fea5b7e1 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 17:18:48 -0400 Subject: [PATCH 18/25] Resolving code style issues --- .../ArgumentsHelpers/Extensions.cs | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index 0c1daef..1ab6483 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -16,14 +16,7 @@ internal static T ValueOrThrowIfNull([NotNull] this T? value, [return: NotNull] internal static T ValueOrThrowIfNull([NotNull] this T? value, string paramName, string customMessage) - { - if (value is not null) - { - return value; - } - - throw new ArgumentNullException(paramName, customMessage); - } + => value ?? throw new ArgumentNullException(paramName, customMessage); [return: NotNull] internal static T ValueOrThrowInvalidOperationIfNull([NotNull] this T? stateElement, @@ -37,14 +30,9 @@ internal static string ValueOrThrowIfZeroLength(this string value, string paramN [return: NotNull] internal static string ValueOrThrowIfZeroLength(this string value, string paramName, string customMessage) - { - if (value.Length is not 0) - { - return value; - } - - throw new ArgumentFormatException(paramName: paramName, message: customMessage); - } + => value.Length is not 0 + ? value + : throw new ArgumentFormatException(paramName: paramName, message: customMessage); [return: NotNull] internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string paramName) @@ -52,14 +40,9 @@ internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string pa [return: NotNull] internal static string ValueOrThrowIfWhiteSpaceOnly(this string value, string paramName, string customMessage) - { - if (value.Any(ch => ch.IsNotWhiteSpace())) - { - return value; - } - - throw new ArgumentFormatException(paramName: paramName, message: customMessage); - } + => value.Any(ch => ch.IsNotWhiteSpace()) + ? value + : throw new ArgumentFormatException(paramName: paramName, message: customMessage); [return: NotNull] internal static string ValueOrThrowIfNullOrZeroLength([NotNull] this string? value, From 60982588e4f672b9201bddf29e7c0b979fbdfc63 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 17:29:20 -0400 Subject: [PATCH 19/25] Solving code-style issues --- src/Validations/ArgumentsHelpers/Extensions.cs | 10 +--------- src/Validations/State.cs | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index 1ab6483..ad4ab12 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -2,10 +2,6 @@ namespace Triplex.Validations.ArgumentsHelpers; -#if (NETSTANDARD || NETCOREAPP) -#pragma warning disable CS0436 //CallerArgumentExpressionAttribute type conflicts -#endif - #pragma warning disable CA1303 // Do not pass literals as localized parameters internal static class Extensions { @@ -106,8 +102,4 @@ internal static TType[] ValueOrThrowIfNullOrWithLessThanElements( return value!; } } -#pragma warning restore CA1303 // Do not pass literals as localized parameters - -#if (NETSTANDARD || NETCOREAPP) -#pragma warning restore CS0436 //CallerArgumentExpressionAttribute type conflicts -#endif \ No newline at end of file +#pragma warning restore CA1303 // Do not pass literals as localized parameters \ No newline at end of file diff --git a/src/Validations/State.cs b/src/Validations/State.cs index 3caf1db..7ebf7e1 100644 --- a/src/Validations/State.cs +++ b/src/Validations/State.cs @@ -23,11 +23,11 @@ public static class State [DebuggerStepThrough] public static void IsTrue(bool stateQuery, [NotNull] string message) { - NullAndEmptyChecks.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (!stateQuery) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } From 940eb9962ffd6ff14bfb3b65e9fa59b074b91844 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 17:39:33 -0400 Subject: [PATCH 20/25] Solving code-style issues --- src/Validations/Arguments.cs | 11 +++----- .../ArgumentsHelpers/OutOfRangeChecks.cs | 8 +++--- src/Validations/State.cs | 26 +++++++------------ 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index f90c3ae..03d3f54 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -149,7 +149,7 @@ public static string NotEmptyOrExceptionWithMessage( [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); - /// + /// /// Checks that the provided value is not empty. /// /// @@ -634,12 +634,9 @@ private static TNullable CompliesWithExpected( string notNullPreconditionDescription = preconditionDescription.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); - if (notNullValidator(notNullValue) != expected) - { - throw new ArgumentException(paramName: notNullParamName, message: notNullPreconditionDescription); - } - - return notNullValue; + return notNullValidator(notNullValue) != expected + ? throw new ArgumentException(paramName: notNullParamName, message: notNullPreconditionDescription) + : notNullValue; } [GeneratedRegex("[0-9]{2}"), ExcludeFromCodeCoverage] diff --git a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs index 81550c4..0caf3df 100644 --- a/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs +++ b/src/Validations/ArgumentsHelpers/OutOfRangeChecks.cs @@ -135,8 +135,8 @@ private static TComparable CheckBoundaries( ComparableRange range, [NotNull] string paramName, string? customMessage) where TComparable : IComparable - { - return range.Contains(value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(paramName)), - customMessage); - } + => range.Contains( + value.ValueOrThrowIfNull(nameof(value)), + paramName.ValueOrThrowIfNull(nameof(paramName)), + customMessage); } diff --git a/src/Validations/State.cs b/src/Validations/State.cs index 7ebf7e1..51eb9c7 100644 --- a/src/Validations/State.cs +++ b/src/Validations/State.cs @@ -2,10 +2,6 @@ namespace Triplex.Validations; -#if (NETSTANDARD || NETCOREAPP) -#pragma warning disable CS0436 //CallerArgumentExpressionAttribute type conflicts -#endif - /// /// Object internal state checks. Use it to check Preconditions and Invariants. /// Always throw or some derivative. @@ -40,11 +36,11 @@ public static void IsTrue(bool stateQuery, [NotNull] string message) [DebuggerStepThrough] public static void IsFalse(bool stateQuery, [NotNull] string message) { - NullAndEmptyChecks.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (stateQuery) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } @@ -64,11 +60,11 @@ public static void IsFalse(bool stateQuery, [NotNull] string message) [return: NotNull] public static T IsNotNull( [NotNull] T stateElement, - [NotNull, CallerArgumentExpression("stateElement")] string elementName = "") + [NotNull, CallerArgumentExpression(nameof(stateElement))] string elementName = "") { - NullAndEmptyChecks.NotNull(elementName, nameof(elementName)); + string notNullElementName = NullAndEmptyChecks.NotNull(elementName, nameof(elementName)); - return stateElement.ValueOrThrowInvalidOperationIfNull(elementName); + return stateElement.ValueOrThrowInvalidOperationIfNull(notNullElementName); } #endregion //Preconditions @@ -84,11 +80,11 @@ public static T IsNotNull( [DebuggerStepThrough] public static void StillHolds(bool invariant, [NotNull] string message) { - NullAndEmptyChecks.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (!invariant) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } @@ -100,17 +96,13 @@ public static void StillHolds(bool invariant, [NotNull] string message) [DebuggerStepThrough] public static void StillNotHolds(bool invariant, [NotNull] string message) { - NullAndEmptyChecks.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (invariant) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } #endregion //Invariants } - -#if (NETSTANDARD || NETCOREAPP) -#pragma warning restore CS0436 //CallerArgumentExpressionAttribute type conflicts -#endif \ No newline at end of file From 5ac84bd1f264676189bc4a7ab07e4cbca748c1f4 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 18:05:24 -0400 Subject: [PATCH 21/25] Solving code-style issues --- .../Algorithms/Checksum/LuhnFormula.cs | 18 +++++++++--------- src/Validations/ArgumentsHelpers/Extensions.cs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Validations/Algorithms/Checksum/LuhnFormula.cs b/src/Validations/Algorithms/Checksum/LuhnFormula.cs index 154f4e1..f0c6cba 100644 --- a/src/Validations/Algorithms/Checksum/LuhnFormula.cs +++ b/src/Validations/Algorithms/Checksum/LuhnFormula.cs @@ -3,10 +3,10 @@ /// /// Implementation of the Luhn algorithm in its two variants: as validation and as checksum generator. /// -public static class LuhnFormula +public static partial class LuhnFormula { private const int MinimumElements = 2; - private static readonly Regex DigitsRegex = new("^[0-9]+$", RegexOptions.Compiled); + private static readonly Regex DigitsRegex = BuildDigitsRegex(); /// /// Indicates is a sequence of numbers is valid using the Luhn formula. @@ -55,7 +55,7 @@ public static bool IsValid([NotNull] string? fullDigits) { string notNullDigits = ValidateDigitsAsString(fullDigits); - int[] validatedDigits = notNullDigits.Select(ch => ch - '0').ToArray(); + int[] validatedDigits = [.. notNullDigits.Select(ch => ch - '0')]; return DoDigitCheck(validatedDigits); } @@ -71,12 +71,9 @@ private static string ValidateDigitsAsString([NotNull] string? fullDigits) $"Length must be at least {MinimumElements} elements."); } - if (!DigitsRegex.IsMatch(notNullDigits)) - { - throw new ArgumentFormatException(nameof(fullDigits), "Invalid input, only digits [0-9] are allowed."); - } - - return notNullDigits; + return !DigitsRegex.IsMatch(notNullDigits) + ? throw new ArgumentFormatException(nameof(fullDigits), "Invalid input, only digits [0-9] are allowed.") + : notNullDigits; } private static bool DoDigitCheck(int[] sanitizedDigits) @@ -133,4 +130,7 @@ private static int CalculateCheck(int[] digits, bool isFullSequence) return (10 - (sum % 10)) % 10; } + + [GeneratedRegex("^[0-9]+$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)] + private static partial Regex BuildDigitsRegex(); } diff --git a/src/Validations/ArgumentsHelpers/Extensions.cs b/src/Validations/ArgumentsHelpers/Extensions.cs index ad4ab12..a8ec4f1 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -97,7 +97,7 @@ internal static TEnumType ValueOrThrowIfNotDefined(this TEnumType val internal static TType[] ValueOrThrowIfNullOrWithLessThanElements( [NotNull] this TType[]? value, int minimumElements, string paramName) { - OutOfRangeChecks.GreaterThanOrEqualTo(ValueOrThrowIfNull(value, paramName).Length, minimumElements, paramName); + _ = OutOfRangeChecks.GreaterThanOrEqualTo(ValueOrThrowIfNull(value, paramName).Length, minimumElements, paramName); return value!; } From 53f565b156848df7514e35b2a4c61e21e45b181d Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 18:14:42 -0400 Subject: [PATCH 22/25] Solving code-style issues --- .../Algorithms/Checksum/LuhnFormula.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Validations/Algorithms/Checksum/LuhnFormula.cs b/src/Validations/Algorithms/Checksum/LuhnFormula.cs index f0c6cba..d213ba7 100644 --- a/src/Validations/Algorithms/Checksum/LuhnFormula.cs +++ b/src/Validations/Algorithms/Checksum/LuhnFormula.cs @@ -28,12 +28,9 @@ public static bool IsValid([NotNull] int[]? fullDigits) nameof(fullDigits)); bool hasInvalidElements = validatedDigits.Any(d => d < 0 || d > 9); - if (hasInvalidElements) - { - throw new FormatException("Only values between zero and nine ( [0-9] ) are allowed as input."); - } - - return DoDigitCheck(validatedDigits); + return hasInvalidElements + ? throw new FormatException("Only values between zero and nine ( [0-9] ) are allowed as input.") + : DoDigitCheck(validatedDigits); } /// @@ -65,15 +62,15 @@ private static string ValidateDigitsAsString([NotNull] string? fullDigits) { string notNullDigits = fullDigits.ValueOrThrowIfNullOrZeroLength(nameof(fullDigits)); - if (notNullDigits.Length < MinimumElements) + return notNullDigits.Length switch { - throw new ArgumentFormatException(nameof(fullDigits), - $"Length must be at least {MinimumElements} elements."); - } - - return !DigitsRegex.IsMatch(notNullDigits) - ? throw new ArgumentFormatException(nameof(fullDigits), "Invalid input, only digits [0-9] are allowed.") - : notNullDigits; + < MinimumElements => + throw new ArgumentFormatException(nameof(fullDigits), + $"Length must be at least {MinimumElements} elements."), + _ => !DigitsRegex.IsMatch(notNullDigits) + ? throw new ArgumentFormatException(nameof(fullDigits), "Invalid input, only digits [0-9] are allowed.") + : notNullDigits + }; } private static bool DoDigitCheck(int[] sanitizedDigits) From 377702a22f2cb13a0bc44222fdb597d0afad7d47 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 18:20:36 -0400 Subject: [PATCH 23/25] Solving code-style issues --- src/Validations/Algorithms/Checksum/LuhnFormula.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Validations/Algorithms/Checksum/LuhnFormula.cs b/src/Validations/Algorithms/Checksum/LuhnFormula.cs index d213ba7..96207f5 100644 --- a/src/Validations/Algorithms/Checksum/LuhnFormula.cs +++ b/src/Validations/Algorithms/Checksum/LuhnFormula.cs @@ -27,7 +27,13 @@ public static bool IsValid([NotNull] int[]? fullDigits) int[] validatedDigits = fullDigits.ValueOrThrowIfNullOrWithLessThanElements(MinimumElements, nameof(fullDigits)); - bool hasInvalidElements = validatedDigits.Any(d => d < 0 || d > 9); + bool hasInvalidElements = validatedDigits.Any(d => d switch + { + < 0 => true, + > 9 => true, + _ => false + }); + return hasInvalidElements ? throw new FormatException("Only values between zero and nine ( [0-9] ) are allowed as input.") : DoDigitCheck(validatedDigits); From 0c7d29121df28691e1caf75c4ff4d9d8d6e47934 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Mon, 26 May 2025 18:45:53 -0400 Subject: [PATCH 24/25] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 8607581..0e0dfac 100644 --- a/README.md +++ b/README.md @@ -147,9 +147,6 @@ We like to get help from everybody, if you want to contribute to this tool, foun At the end of a test run you should see something like ```sh -Calculating coverage result... - Generating report '~/tests/unit/Validations.Tests/coverage.net6.0.json' - +---------------------+------+--------+--------+ | Module | Line | Branch | Method | +---------------------+------+--------+--------+ From 63efe5f8669a0e32760b3c1db2b1552c77624f06 Mon Sep 17 00:00:00 2001 From: Lorenzo Solano Martinez Date: Tue, 3 Jun 2025 22:32:16 -0400 Subject: [PATCH 25/25] PR comments --- src/Validations/Arguments.cs | 21 --- .../CompliesWithMessageFacts.cs | 46 ------- .../NotNullEmptyOrWhiteSpaceOnly_Facts.cs | 118 ---------------- .../NotNullOrEmptyMessageFacts.cs | 126 ------------------ 4 files changed, 311 deletions(-) delete mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs delete mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs delete mode 100644 tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs diff --git a/src/Validations/Arguments.cs b/src/Validations/Arguments.cs index 03d3f54..2423d97 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -88,7 +88,6 @@ public static string NotEmptyNorWhiteSpaceOnlyOrException( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage public static string NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( [NotNull] string? value, [NotNull] string customMessage, @@ -108,7 +107,6 @@ public static string NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for NotEmptyOrException public static string NotEmptyOrException( [NotNull] string? value, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") @@ -122,7 +120,6 @@ public static string NotEmptyOrException( /// /// If is an empty . [DebuggerStepThrough] - //TODO: Refactor tests for NotEmptyOrException public static Guid NotEmptyOrException(Guid value, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { @@ -142,7 +139,6 @@ public static Guid NotEmptyOrException(Guid value, /// If length is zero. [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for NotEmptyOrExceptionWithMessage public static string NotEmptyOrExceptionWithMessage( [NotNull] string? value, [NotNull] string customMessage, @@ -167,7 +163,6 @@ public static string NotEmptyOrExceptionWithMessage( /// /// If is an empty . [DebuggerStepThrough] - //TODO: Refactor tests for NotEmptyOrExceptionWithMessage public static Guid NotEmptyOrExceptionWithMessage(Guid value, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") @@ -199,7 +194,6 @@ public static Guid NotEmptyOrExceptionWithMessage(Guid value, /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for MemberOfOrException public static TEnumType MemberOfOrException(TEnumType value, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TEnumType : Enum @@ -218,7 +212,6 @@ public static TEnumType MemberOfOrException(TEnumType value, /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for MemberOfOrExceptionWithMessage public static TEnumType MemberOfOrExceptionWithMessage(TEnumType value, string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TEnumType : Enum @@ -243,7 +236,6 @@ public static TEnumType MemberOfOrExceptionWithMessage(TEnumType valu /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for LessThanOrException public static TComparable LessThanOrException( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -266,7 +258,6 @@ public static TComparable LessThanOrException( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for LessThanOrExceptionWithMessage public static TComparable LessThanOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -290,7 +281,6 @@ public static TComparable LessThanOrExceptionWithMessage( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for LessThanOrEqualToOrException public static TComparable LessThanOrEqualToOrException( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -313,7 +303,6 @@ public static TComparable LessThanOrEqualToOrException( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for LessThanOrEqualToOrExceptionWithMessage public static TComparable LessThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -336,7 +325,6 @@ public static TComparable LessThanOrEqualToOrExceptionWithMessage( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for GreaterThanOrException public static TComparable GreaterThanOrException( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -359,7 +347,6 @@ public static TComparable GreaterThanOrException( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for GreaterThanOrExceptionWithMessage public static TComparable GreaterThanOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -383,7 +370,6 @@ public static TComparable GreaterThanOrExceptionWithMessage( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for GreaterThanOrEqualToOrException public static TComparable GreaterThanOrEqualToOrException( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -408,7 +394,6 @@ public static TComparable GreaterThanOrEqualToOrException( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for GreaterThanOrEqualToOrExceptionWithMessage public static TComparable GreaterThanOrEqualToOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable other, @@ -460,7 +445,6 @@ public static TComparable BetweenOrException( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for BetweenOrExceptionWithMessage public static TComparable BetweenOrExceptionWithMessage( [NotNull] TComparable? value, [NotNull] TComparable fromInclusive, @@ -492,7 +476,6 @@ public static TComparable BetweenOrExceptionWithMessage( /// [DebuggerStepThrough] [return: NotNull] - //TODO: Refactor tests for ValidLuhnChecksum public static string ValidLuhnChecksum([NotNull] string? value, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { @@ -536,7 +519,6 @@ private static int[] ToDigitsArray(string notNullValue) /// When any parameter is /// If is not a valid Base64 String. [DebuggerStepThrough] - //TODO: Refactor tests for ValidBase64 public static string ValidBase64([NotNull] string? value, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { string validParamName = @@ -558,7 +540,6 @@ public static string ValidBase64([NotNull] string? value, [NotNull, CallerArgume /// When any parameter is /// If is not a valid Base64 String. [DebuggerStepThrough] - //TODO: Refactor tests for ValidBase64 public static string ValidBase64([NotNull] string? value, [NotNull] string customMessage, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { @@ -592,7 +573,6 @@ private static bool IsBase64String(string base64) /// Description for the custom precondition. /// [DebuggerStepThrough] - //TODO: Refactor tests for CompliesWith public static TNullable CompliesWith( [NotNull] TNullable? value, [NotNull] Func validator, @@ -611,7 +591,6 @@ public static TNullable CompliesWith( /// Description for the custom precondition. /// [DebuggerStepThrough] - //TODO: Refactor tests for DoesNotComplyWith public static TNullable DoesNotComplyWith( [NotNull] TNullable? value, [NotNull] Func validator, diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs deleted file mode 100644 index d9bdc0e..0000000 --- a/tests/unit/Validations.Tests/ArgumentsFacts/CompliesWithMessageFacts.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Triplex.Validations.Tests.ArgumentsFacts -{ - [TestFixture] - internal sealed class CompliesWithMessageFacts - { - private const string PreconditionDescription = "Must be true."; - - // [Test] - // public void With_True_Throws_Nothing() - // { - // Assert.That(() => Arguments.CompliesWith(2 + 2 == 4, "someParam", PreconditionDescription), Throws.Nothing); - // } - - // [Test] - // public void With_False_Throws_ArgumentException() - // { - // const string paramName = "someParameter"; - // Assert.That(() => Arguments.CompliesWith(2 + 2 == 5, paramName, PreconditionDescription), - // Throws.ArgumentException - // .With.Property(nameof(ArgumentException.ParamName)).EqualTo(paramName) - // .And.Message.Contains(PreconditionDescription)); - // } - - // [Test] - // public void With_Invalid_ParamName_Throws_ArgumentException([Values(null, "", " ", "\n\r\t ")] string paramName, - // [Values] bool precondition) - // { - // string paramNameCopy = paramName; - - // Assert.That(() => Arguments.CompliesWith(precondition, paramNameCopy, PreconditionDescription), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentException.ParamName)).EqualTo("paramName")); - // } - - // [Test] - // public void With_Invalid_PreconditionDescription_Throws_ArgumentException( - // [Values(null, "", " ", "\n\r\t ")] string preconditionDescription, [Values] bool precondition) - // { - // string preconditionDescriptionCopy = preconditionDescription; - - // Assert.That(() => Arguments.CompliesWith(precondition, "someParamName", preconditionDescriptionCopy), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentException.ParamName)).EqualTo("preconditionDescription")); - // } - } -} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs deleted file mode 100644 index b3d3fa8..0000000 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullEmptyOrWhiteSpaceOnly_Facts.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Triplex.Validations.Exceptions; - -namespace Triplex.Validations.Tests.ArgumentsFacts; - -[TestFixture] -internal sealed class NotNullEmptyOrWhiteSpaceOnly_Facts -{ - private const string CustomMessage = "Some error message, caller provided."; - - - // [Test] - // public void With_Null_CustomError_Throws_ArgumentNullException() - // { - // string? dummyParam = "Hello world!"; - - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), null!), - // Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - // .EqualTo("customMessage")); - // } - - // [Test] - // public void With_Empty_CustomError_Throws_ArgumentOutOfRangeException() - // { - // string? dummyParam = "Hello world!"; - - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), string.Empty), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); - // } - - // [TestCase(" ")] - // [TestCase("\n")] - // [TestCase("\r")] - // [TestCase("\t")] - // [TestCase("\n\r\t ")] - // public void With_Common_White_Space_CustomError_Throws_ArgumentFormatException(string? someErrorMessage) - // { - // string? dummyParam = "Hello world!"; - // string? someErrorMessageCopy = someErrorMessage; - - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), someErrorMessageCopy!), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentException.ParamName)).EqualTo("customMessage")); - // } - - // [Test] - // public void With_Null_Value_Throws_ArgumentNullException() - // { - // string? dummyParam = null; - - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), - // Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - // .EqualTo(nameof(dummyParam))); - // } - - // [Test] - // public void With_Empty_Value_Throws_ArgumentOutOfRangeException() - // { - // string? dummyParam = string.Empty; - - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParam, nameof(dummyParam), CustomMessage), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam))); - // } - - // [TestCase(" ")] - // [TestCase("\n")] - // [TestCase("\r")] - // [TestCase("\t")] - // [TestCase("\n\r\t ")] - // public void With_Common_White_Space_Value_Throws_ArgumentFormatException(string? dummyParam) - // { - // string? dummyParamValue = dummyParam; - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly(dummyParamValue, nameof(dummyParam), CustomMessage), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentException.ParamName)).EqualTo(nameof(dummyParam))); - // } - - // [TestCase("peter")] - // [TestCase("parker ")] - // [TestCase(" Peter Parker Is Spiderman ")] - // public void With_Valid_Values_Returns_Input_Value(string? someValue) - // { - // string validatedValue = Arguments.NotNullEmptyOrWhiteSpaceOnly(someValue, nameof(someValue), CustomMessage); - - // Assert.That(validatedValue, Is.SameAs(someValue)); - // } - - // [Test] - // public void With_Null_ParamName_Throws_ArgumentNullException() - // { - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", null!, CustomMessage), - // Throws.ArgumentNullException - // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); - // } - - // [Test] - // public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() - // { - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", string.Empty, CustomMessage), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - // .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); - // } - - // [TestCase(" ")] - // [TestCase("\n")] - // [TestCase("\r")] - // [TestCase("\t")] - // [TestCase("\n\r\t ")] - // public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException(string? paramName) - // { - // string? paramNameValue = paramName; - // Assert.That(() => Arguments.NotNullEmptyOrWhiteSpaceOnly("dummyValue", paramNameValue!, CustomMessage), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentFormatException.ParamName)).EqualTo("paramName")); - // } -} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs b/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs deleted file mode 100644 index 400db7c..0000000 --- a/tests/unit/Validations.Tests/ArgumentsFacts/NotNullOrEmptyMessageFacts.cs +++ /dev/null @@ -1,126 +0,0 @@ -namespace Triplex.Validations.Tests.ArgumentsFacts; - -internal static class NotNullOrEmptyMessageFacts -{ - private const string CustomMessage = "Can't be null or empty from test case"; - - [TestFixture] - internal sealed class WithInvalidCustomMessage - { - // [Test] - // public void With_Null_Throws_ArgumentNullException() - // { - // string someInput = "dummyValue"; - // Assert.That(() => Arguments.NotNullOrEmpty(someInput, nameof(someInput), null!), - // Throws.ArgumentNullException - // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("customMessage")); - // } - - // [Test] - // public void With_Empty_Throws_ArgumentOutOfRangeException() - // { - // string someInput = "dummyValue"; - // Assert.That(() => Arguments.NotNullOrEmpty(someInput, nameof(someInput), string.Empty), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("customMessage")); - // } - } - - internal sealed class WithInvalidParamName : BaseFixtureForOptionalCustomMessage - { - public WithInvalidParamName(bool useCustomErrorMessage) : base(useCustomErrorMessage) - { - } - - // [Test] - // public void With_Null_ParamName_Throws_ArgumentNullException() - // { - // Assert.That(() => NotNullOrEmpty("dummyValue", null, CustomMessage, UseCustomErrorMessage), - // Throws.ArgumentNullException - // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("paramName")); - // } - - // [Test] - // public void With_Empty_ParamName_Throws_ArgumentOutOfRangeException() - // { - // Assert.That(() => NotNullOrEmpty("dummyValue", string.Empty, CustomMessage, UseCustomErrorMessage), - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentOutOfRangeException.ParamName)).EqualTo("paramName") - // .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(0)); - // } - } - - internal sealed class WithInvalidValueMessage : BaseFixtureForOptionalCustomMessage - { - public WithInvalidValueMessage(bool useCustomErrorMessage) : base(useCustomErrorMessage) - { - } - - // [Test] - // public void With_Null_Value_Throws_ArgumentNullException() - // { - // string? dummyParam = null; - - // Constraint exceptionConstraint = AddMessageConstraint( - // Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)) - // .EqualTo(nameof(dummyParam)), UseCustomErrorMessage); - - // Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), - // exceptionConstraint); - // } - - // [Test] - // public void With_Empty_Value_Throws_ArgumentOutOfRangeException() - // { - // string? dummyParam = string.Empty; - - // Constraint exceptionConstraint = AddMessageConstraint( - // Throws.InstanceOf() - // .With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(nameof(dummyParam)), - // UseCustomErrorMessage); - - // Assert.That(() => NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage), - // exceptionConstraint); - // } - - private static Constraint AddMessageConstraint(Constraint messageConstraint, bool useCustomErrorMessage) - => useCustomErrorMessage ? messageConstraint.With.Message.StartsWith(CustomMessage) : messageConstraint; - } - - internal sealed class WithValidValueMessage : BaseFixtureForOptionalCustomMessage - { - public WithValidValueMessage(bool useCustomErrorMessage) : base(useCustomErrorMessage) - { - } - - // [TestCase(" ")] - // [TestCase("\n")] - // [TestCase("\r")] - // [TestCase("\t")] - // [TestCase("\n\r\t ")] - // public void With_Common_White_Space_Sequences_Value_Throws_Nothing(string? dummyParam) - // { - // string myDummyValue = NotNullOrEmpty(dummyParam, nameof(dummyParam), CustomMessage, UseCustomErrorMessage); - - // Assert.That(myDummyValue, Is.SameAs(dummyParam)); - // } - - // [TestCase("peter")] - // [TestCase("parker ")] - // [TestCase(" Peter Parker Is Spiderman ")] - // public void With_Non_Empty_Values_Throws_Nothing(string? someParam) - // { - // string myDummyValue = NotNullOrEmpty(someParam, nameof(someParam), CustomMessage, UseCustomErrorMessage); - - // Assert.That(myDummyValue, Is.SameAs(someParam)); - // } - } - - // private static string NotNullOrEmpty( - // string? value, - // string? paramName, - // string? customMessage, - // bool useCustomErrorMessage) - // => useCustomErrorMessage ? Arguments.NotNullOrEmpty(value, paramName!, customMessage!) - // : Arguments.NotNullOrEmpty(value, paramName!); -}