diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c45aa79 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# 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 + +# Expression-bodied members +csharp_style_expression_bodied_local_functions = true +csharp_style_expression_bodied_methods = true \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5afafb3..9528851 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Analyse Code Quality +name: Build and SonarQube on: push: branches: @@ -7,35 +7,36 @@ on: types: [opened, synchronize, reopened] jobs: build: - name: Build + name: Build and analyze runs-on: windows-latest steps: - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: 1.11 - - uses: actions/checkout@v2 + 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 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x - include-prerelease: false - - name: Cache SonarCloud packages - uses: actions/cache@v1 + dotnet-version: 9.0.x + dotnet-quality: preview + - 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@v1 + 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: | @@ -43,15 +44,13 @@ 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" + .\.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.login="${{ secrets.SONAR_TOKEN }}" + 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/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 2d6166d..7cd0a4c 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -18,21 +18,22 @@ 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: 9.0.x + dotnet-quality: preview # 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..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: @@ -14,16 +15,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 @@ -32,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 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/README.md b/README.md index caa5180..0e0dfac 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # 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 ## ### 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 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) @@ -18,57 +20,145 @@ 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 +{ + 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(); - } - } + 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(); -All methods has three forms: + // or use returned value from argument check + _value = Arguments.OrException(value); //return same input (literally same reference) + } -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");` + // Collapse Check-Then-Assign pattern using returned value from checks. + public Email(string username, string domain) + => _value = $"{Arguments.OrException(username)}@{Arguments.OrException(domain)}"; +} +``` +All checks has two forms: -### Object's state ### - using Triplex.Validations; +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");` - public sealed class Point - { - //private Point stuff here... +If you need to use a different argument name, both forms support a last optional parameter: - //Useful constructor(s) here - //public Point(...) { } +1. `Arguments.OrException(someArg, "manualArgumentName");` +2. `Arguments.OrExceptionWithMessage(someArg, "Custom ex. message", "manualArgumentName");` - 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)); +**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));` - //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 - } - } +### Object's state ### +```csharp +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."); + + //1. Contract + Arguments.Between(index, 0, _size - 1, "Index out of bounds."); -All methods has two forms: + //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."); -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");` + return removed; + } +} +``` +State checks semantics: + +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:ExcludeByAttribute=GeneratedCode /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 ++---------------------+------+--------+--------+ +| 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 0000000..8a2390d Binary files /dev/null and b/docs/imgs/Class_With_Nullable_Warning_01.png differ 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 0000000..475c224 Binary files /dev/null and b/docs/imgs/Class_With_Nullable_Warning_02.png differ diff --git a/docs/imgs/Class_With_Nullable_Warning_03.png b/docs/imgs/Class_With_Nullable_Warning_03.png new file mode 100644 index 0000000..8ce4379 Binary files /dev/null and b/docs/imgs/Class_With_Nullable_Warning_03.png differ diff --git a/docs/imgs/Removing_Nullable_Warning_01.png b/docs/imgs/Removing_Nullable_Warning_01.png new file mode 100644 index 0000000..376e843 Binary files /dev/null and b/docs/imgs/Removing_Nullable_Warning_01.png differ diff --git a/docs/imgs/Removing_Nullable_Warning_02.png b/docs/imgs/Removing_Nullable_Warning_02.png new file mode 100644 index 0000000..661a153 Binary files /dev/null and b/docs/imgs/Removing_Nullable_Warning_02.png differ diff --git a/src/Validations/Algorithms/Checksum/LuhnFormula.cs b/src/Validations/Algorithms/Checksum/LuhnFormula.cs index 8b8224e..96207f5 100644 --- a/src/Validations/Algorithms/Checksum/LuhnFormula.cs +++ b/src/Validations/Algorithms/Checksum/LuhnFormula.cs @@ -1,15 +1,12 @@ -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. /// -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. @@ -25,18 +22,21 @@ public static class LuhnFormula /// /// If contains elements not within range [0-9]. /// - public static bool IsValid([ValidatedNotNull] int[]? fullDigits) + public static bool IsValid([NotNull] int[]? fullDigits) { int[] validatedDigits = fullDigits.ValueOrThrowIfNullOrWithLessThanElements(MinimumElements, nameof(fullDigits)); - bool hasInvalidElements = validatedDigits.Any(d => d < 0 || d > 9); - if (hasInvalidElements) + bool hasInvalidElements = validatedDigits.Any(d => d switch { - throw new FormatException("Only values between zero and nine ( [0-9] ) are allowed as input."); - } - - return DoDigitCheck(validatedDigits); + < 0 => true, + > 9 => true, + _ => false + }); + + return hasInvalidElements + ? throw new FormatException("Only values between zero and nine ( [0-9] ) are allowed as input.") + : DoDigitCheck(validatedDigits); } /// @@ -54,31 +54,29 @@ 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] string? fullDigits) { string notNullDigits = ValidateDigitsAsString(fullDigits); - int[] validatedDigits = notNullDigits.Select(ch => ch - '0').ToArray(); + int[] validatedDigits = [.. notNullDigits.Select(ch => ch - '0')]; return DoDigitCheck(validatedDigits); } - private static string ValidateDigitsAsString([ValidatedNotNull] string? fullDigits) + [return: NotNull] + private static string ValidateDigitsAsString([NotNull] string? fullDigits) { string notNullDigits = fullDigits.ValueOrThrowIfNullOrZeroLength(nameof(fullDigits)); - if (notNullDigits.Length < MinimumElements) - { - throw new ArgumentOutOfRangeException(nameof(fullDigits), - $"Length must be at least {MinimumElements} elements."); - } - - if (!DigitsRegex.IsMatch(notNullDigits)) + return notNullDigits.Length switch { - throw new FormatException("Invalid input, only digits [0-9] are allowed."); - } - - return 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) @@ -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] int[]? digitsWithoutCheck) { const int minimumElements = 1; @@ -135,4 +133,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/Arguments.cs b/src/Validations/Arguments.cs index 59e8530..2423d97 100644 --- a/src/Validations/Arguments.cs +++ b/src/Validations/Arguments.cs @@ -1,16 +1,15 @@ -using Triplex.Validations.Algorithms.Checksum; -using Triplex.Validations.ArgumentsHelpers; -using Triplex.Validations.Exceptions; -using Triplex.Validations.Utilities; +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 back or throw exception if it is ." /// -public static class Arguments +public static partial class Arguments { #region Null and Empty Checks /// @@ -22,8 +21,10 @@ 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 OrException( + [NotNull] TParamType? value, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName); /// @@ -45,8 +46,12 @@ 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 OrExceptionWithMessage( + [NotNull] TParamType? value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") + where TParamType : class => NullAndEmptyChecks.NotNull(value, paramName, customMessage); /// @@ -56,15 +61,16 @@ public static TParamType NotNull([ValidatedNotNull] TParamType? valu /// 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 /// - [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 NotEmptyNorWhiteSpaceOnlyOrException( + [NotNull] string? value, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName); /// @@ -75,27 +81,17 @@ public static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? val /// 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 /// - [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) - => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); - - /// - [DebuggerStepThrough] - public static string NotEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [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 NotEmptyNorWhiteSpaceOnlyOrExceptionWithMessage( + [NotNull] string? value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullEmptyOrWhiteSpaceOnly(value, paramName, customMessage); /// @@ -104,64 +100,50 @@ public static string NotEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, /// 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 /// - [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 NotEmptyOrException( + [NotNull] string? value, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); /// - /// Checks that the provided value is not or empty (zero length). + /// Checks that the provided value is not empty. /// /// Value to check - /// Parameter's name, can not be - /// Custom message, can not be + /// Parameter name, from caller's context. /// - /// 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] - public static string NotNullOrEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) - => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); - - /// - [DebuggerStepThrough] - public static string NotEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName) - => NullAndEmptyChecks.NotNullOrEmpty(value, paramName); - - /// + /// If is an empty . [DebuggerStepThrough] - public static string NotEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) - => NullAndEmptyChecks.NotNullOrEmpty(value, paramName, customMessage); + public static Guid NotEmptyOrException(Guid value, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") + { + string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); - #endregion + return IsEmpty(value) ? throw new ArgumentException(validParamName) : value; + } - #region Emptyness /// - /// Checks that the provided value is not empty. + /// Checks that the provided value is not or empty (zero length). /// /// Value to check - /// Parameter name, from caller's context. + /// Parameter's name, can not be + /// Custom message, can not be /// - /// If is an empty . + /// If any parameter is . + /// If length is zero. [DebuggerStepThrough] - public static Guid NotEmpty(Guid value, [ValidatedNotNull] string paramName) - { - string validParamName = paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); - - if (IsEmpty(value)) - { - throw new ArgumentException(validParamName); - } - - return value; - } + [return: NotNull] + 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. @@ -181,24 +163,22 @@ 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 NotEmptyOrExceptionWithMessage(Guid value, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { (string validParamName, string validCustomMessage) = - (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)), - customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(customMessage))); + (paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(), + customMessage.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); - if (IsEmpty(value)) - { - throw new ArgumentException(paramName: validParamName, message: validCustomMessage); - } - - return value; + return IsEmpty(value) ? + throw new ArgumentException(paramName: validParamName, message: validCustomMessage) + : value; } private static bool IsEmpty(Guid value) => value == default; - #endregion //Emptyness + #endregion #region Enumerations Checks @@ -213,7 +193,9 @@ public static Guid NotEmpty(Guid value, [ValidatedNotNull] string paramName, /// When is not within /// [DebuggerStepThrough] - public static TEnumType ValidEnumerationMember(TEnumType value, string paramName) + [return: NotNull] + public static TEnumType MemberOfOrException(TEnumType value, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName); @@ -229,8 +211,10 @@ public static TEnumType ValidEnumerationMember(TEnumType value, strin /// When is not within /// [DebuggerStepThrough] - public static TEnumType ValidEnumerationMember(TEnumType value, string paramName, - string customMessage) where TEnumType : Enum + [return: NotNull] + public static TEnumType MemberOfOrExceptionWithMessage(TEnumType value, + string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TEnumType : Enum => EnumerationChecks.ValidEnumerationMember(value, paramName, customMessage); #endregion @@ -251,9 +235,11 @@ 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) - where TComparable : IComparable + [return: NotNull] + public static TComparable LessThanOrException( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName); /// @@ -271,9 +257,12 @@ 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 LessThanOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThan(value, other, paramName, customMessage); /// @@ -291,9 +280,11 @@ 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) - where TComparable : IComparable + [return: NotNull] + public static TComparable LessThanOrEqualToOrException( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName); /// @@ -311,10 +302,12 @@ 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) - where TComparable : IComparable + [return: NotNull] + public static TComparable LessThanOrEqualToOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.LessThanOrEqualTo(value, other, paramName, customMessage); /// @@ -331,9 +324,11 @@ 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) - where TComparable : IComparable + [return: NotNull] + public static TComparable GreaterThanOrException( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName); /// @@ -351,9 +346,12 @@ 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 GreaterThanOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThan(value, other, paramName, customMessage); /// @@ -371,8 +369,11 @@ 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 GreaterThanOrEqualToOrException( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TComparable : IComparable => OutOfRangeChecks.GreaterThanOrEqualTo(value, other, paramName); @@ -392,11 +393,39 @@ 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 GreaterThanOrEqualToOrExceptionWithMessage( + [NotNull] TComparable? value, + [NotNull] TComparable other, + [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(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(nameof(value))] string paramName = "") where TComparable : IComparable + => OutOfRangeChecks.Between(value, fromInclusive, toInclusive, paramName); + /// ///

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

@@ -415,19 +444,20 @@ public static TComparable GreaterThanOrEqualTo([ValidatedNotNull] T /// ] /// [DebuggerStepThrough] - public static TComparable Between( - [ValidatedNotNull] TComparable? value, - [ValidatedNotNull] TComparable? fromInclusive, - [ValidatedNotNull] TComparable? toInclusive, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) where TComparable : IComparable + [return: NotNull] + public static TComparable BetweenOrExceptionWithMessage( + [NotNull] TComparable? value, + [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); #endregion #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 @@ -445,12 +475,13 @@ 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] string? value, [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(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); @@ -472,7 +503,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 @@ -488,10 +519,10 @@ 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] string? value, [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") { string validParamName = - paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName)); + paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(); string notNullValue = value.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(validParamName); @@ -509,12 +540,12 @@ 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] string? value, [NotNull] string customMessage, + [NotNull, CallerArgumentExpression(nameof(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); @@ -530,32 +561,8 @@ private static bool IsBase64String(string base64) #endregion // Known Encodings - - #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, [ValidatedNotNull] string paramName, - [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. /// @@ -567,10 +574,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] TNullable? value, + [NotNull] Func validator, + [NotNull] string preconditionDescription, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") where TNullable : class => CompliesWithExpected(value, validator, paramName, preconditionDescription, true); @@ -585,35 +592,34 @@ public static TNullable CompliesWith( /// [DebuggerStepThrough] public static TNullable DoesNotComplyWith( - [ValidatedNotNull] TNullable? value, - [ValidatedNotNull] Func validator, - [ValidatedNotNull] string paramName, - [ValidatedNotNull] string preconditionDescription) + [NotNull] TNullable? value, + [NotNull] Func validator, + [NotNull] string preconditionDescription, + [NotNull, CallerArgumentExpression(nameof(value))] string paramName = "") 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] 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) - { - 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] + private static partial Regex TwoOrMoreDigitsRegex(); + #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 c39ab6a..a8ec4f1 100644 --- a/src/Validations/ArgumentsHelpers/Extensions.cs +++ b/src/Validations/ArgumentsHelpers/Extensions.cs @@ -1,74 +1,67 @@ -using Triplex.Validations.Exceptions; -using Triplex.Validations.Utilities; +using System.Runtime.CompilerServices; 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) - { - if (value is not null) - { - return value; - } - - throw new ArgumentNullException(paramName); - } - - internal static T ValueOrThrowIfNull([ValidatedNotNull] this T? value, string paramName, - string customMessage) - { - if (value is not null) - { - return value; - } - - throw new ArgumentNullException(paramName, customMessage); - } - + [return: NotNull] + internal static T ValueOrThrowIfNull([NotNull] this T? value, + [CallerArgumentExpression(nameof(value))] string paramName = "") + => value ?? throw new ArgumentNullException(paramName); + + [return: NotNull] + internal static T ValueOrThrowIfNull([NotNull] this T? value, string paramName, string customMessage) + => value ?? 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)."); + [return: NotNull] internal static string ValueOrThrowIfZeroLength(this string value, string paramName, string customMessage) - { - if (value.Length is not 0) - { - return value; - } - - throw new ArgumentOutOfRangeException(paramName, value.Length, 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) => 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())) - { - return value; - } - - throw new ArgumentFormatException(paramName: paramName, message: customMessage); - } + => value.Any(ch => ch.IsNotWhiteSpace()) + ? value + : throw new ArgumentFormatException(paramName: paramName, message: customMessage); - internal static string ValueOrThrowIfNullOrZeroLength([ValidatedNotNull] this string? value, string paramName) + [return: NotNull] + internal static string ValueOrThrowIfNullOrZeroLength([NotNull] 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] this string? value, + string paramName, string customMessage) => ValueOrThrowIfNull(value, paramName, customMessage) .ValueOrThrowIfZeroLength(paramName, customMessage); - internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly([ValidatedNotNull] this string? value, - string paramName) + [return: NotNull] + internal static string ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly( + [NotNull] this string? value, + [CallerArgumentExpression(nameof(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] this string? value, string paramName, string customMessage) => ValueOrThrowIfNull(value, paramName, customMessage) .ValueOrThrowIfZeroLength(paramName, customMessage) @@ -100,12 +93,13 @@ 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] 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!; } } -#pragma warning restore CA1303 // Do not pass literals as localized parameters +#pragma warning restore CA1303 // Do not pass literals as localized parameters \ No newline at end of file diff --git a/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs b/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs index 9f0f9eb..57263fb 100644 --- a/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs +++ b/src/Validations/ArgumentsHelpers/NullAndEmptyChecks.cs @@ -1,35 +1,41 @@ -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] TParamType? value, + [NotNull] 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] TParamType? value, + [NotNull] string paramName, [NotNull] 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) - => NotNullOrEmpty(value, paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName))) + [return: NotNull] + internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull] string? value, + [NotNull] string paramName) + => NotNullOrEmpty(value, paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()) .ValueOrThrowIfWhiteSpaceOnly(paramName); - internal static string NotNullEmptyOrWhiteSpaceOnly([ValidatedNotNull] string? value, - [ValidatedNotNull] string paramName, [ValidatedNotNull] string customMessage) + [return: NotNull] + internal static string NotNullEmptyOrWhiteSpaceOnly([NotNull] string? value, + [NotNull] string paramName, [NotNull] 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] string? value, + [NotNull] string paramName) => value.ValueOrThrowIfNullOrZeroLength( - paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly(nameof(paramName))); + paramName.ValueOrThrowIfNullZeroLengthOrWhiteSpaceOnly()); - internal static string NotNullOrEmpty([ValidatedNotNull] string? value, [ValidatedNotNull] string paramName, - [ValidatedNotNull] string customMessage) + [return: NotNull] + 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 957a6c2..0caf3df 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] TComparable? value, + [NotNull] TComparable other, [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] string paramName, + [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] string paramName, + [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] string paramName, + [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] 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] TComparable? value, + [NotNull] TComparable other, [NotNull] string paramName, + [NotNull] string customMessage) where TComparable : IComparable { ComparableRange range = ComparableRangeFactory.WithMinInclusiveOnly( SimpleOption.SomeNotNull(other.ValueOrThrowIfNull(nameof(other)))); @@ -84,31 +90,53 @@ 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] TComparable? value, + [NotNull] TComparable fromInclusive, + [NotNull] TComparable toInclusive, + [NotNull] string paramName) where TComparable : IComparable + { + ComparableRange range = new( + SimpleOption.SomeNotNull(fromInclusive.ValueOrThrowIfNull()), + SimpleOption.SomeNotNull(toInclusive.ValueOrThrowIfNull())); + + string notNullParamName = paramName.ValueOrThrowIfNull(); + + return range.Contains( + 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))), SimpleOption.SomeNotNull(toInclusive.ValueOrThrowIfNull(nameof(toInclusive)))); - return range.IsWithin( + return range.Contains( value.ValueOrThrowIfNull(nameof(value)), paramName.ValueOrThrowIfNull(nameof(customMessage)), customMessage.ValueOrThrowIfNull(nameof(customMessage))); } + [return: NotNull] private static TComparable CheckBoundaries( - [ValidatedNotNull] TComparable? value, + [NotNull] TComparable? value, ComparableRange range, - [ValidatedNotNull] string paramName, + [NotNull] string paramName, string? customMessage) where TComparable : IComparable - { - return range.IsWithin(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/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..51eb9c7 100644 --- a/src/Validations/State.cs +++ b/src/Validations/State.cs @@ -1,4 +1,4 @@ -using Triplex.Validations.Utilities; +using System.Runtime.CompilerServices; namespace Triplex.Validations; @@ -17,13 +17,13 @@ public static class State /// Can not be /// [DebuggerStepThrough] - public static void IsTrue(bool stateQuery, [ValidatedNotNull] string message) + public static void IsTrue(bool stateQuery, [NotNull] string message) { - Arguments.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (!stateQuery) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } @@ -34,16 +34,39 @@ 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] string message) { - Arguments.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (stateQuery) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } + /// + /// 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(nameof(stateElement))] string elementName = "") + { + string notNullElementName = NullAndEmptyChecks.NotNull(elementName, nameof(elementName)); + + return stateElement.ValueOrThrowInvalidOperationIfNull(notNullElementName); + } + #endregion //Preconditions @@ -55,13 +78,13 @@ 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] string message) { - Arguments.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (!invariant) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } @@ -71,13 +94,13 @@ 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] string message) { - Arguments.NotNull(message, nameof(message)); + string notNullMessage = NullAndEmptyChecks.NotNull(message, nameof(message)); if (invariant) { - throw new InvalidOperationException(message); + throw new InvalidOperationException(notNullMessage); } } 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/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/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/src/Validations/Validations.csproj b/src/Validations/Validations.csproj index 1477db2..2b1f1ee 100644 --- a/src/Validations/Validations.csproj +++ b/src/Validations/Validations.csproj @@ -1,60 +1,85 @@  + + 4.0.0 + preview + + net9.0 + 13.0 - - net6.0 - 10.0 - disable - false - Triplex.Validations - Triplex.Validations - 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.0.5-alpha - 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 + true + true + + 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. + 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 + icon.png + + 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). - 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 + - Migrate to .NET 9 - Increase test coverage to 100 percent - - Analyse project using sonarcloud.io (see https://sonarcloud.io/project/overview?id=lsolano_triplex) - - - - - - True - - - - + - Analyze project using sonarcloud.io (see https://sonarcloud.io/project/overview?id=lsolano_triplex) + + + + + + True + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + true + 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 deleted file mode 100644 index ddad569..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/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 987b92b..d217373 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 = GreaterThan(theValue, other, nameof(theValue), CustomError, UseCustomErrorMessage); 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..342da22 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..892a7ee 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 deleted file mode 100644 index 87a275e..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/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 deleted file mode 100644 index 78ff009..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!); -} diff --git a/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs new file mode 100644 index 0000000..ee289be --- /dev/null +++ b/tests/unit/Validations.Tests/ArgumentsFacts/OrExceptionMessage.cs @@ -0,0 +1,87 @@ +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); + + ArgumentNullException? exception = Assert.Throws(() => act()); + + 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] + 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); + + ArgumentNullException? exception = Assert.Throws(() => act()); + + (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) + { + 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 = [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 (string first, string second) ExceptionMessagePartsFor(string paramName) + => ("Value cannot be null.", "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..4d55ed8 --- /dev/null +++ b/tests/unit/Validations.Tests/FluentConstraintsExtensions.cs @@ -0,0 +1,30 @@ +[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + +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 messageExpression = exceptionConstraint.With; + string[] parts = [.. expectedParts]; + for (int i = 0; i < parts.Length - 1; i++) + { + messageExpression = messageExpression.Message.Contains(parts[i]).And; + } + + 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/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/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))); + } +} diff --git a/tests/unit/Validations.Tests/Validations.Tests.csproj b/tests/unit/Validations.Tests/Validations.Tests.csproj index 7745da7..a091a00 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 + net9.0 + 13.0 disable true + false Triplex.Validations.Tests Triplex.Validations.Tests bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml @@ -12,6 +13,7 @@ latest Default enable + true @@ -19,16 +21,25 @@ - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive all + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + +