diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index d54126b..bacf821 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - dotnet-version: [6.0.x,7.0.x,8.0.x] + dotnet-version: [6.0.x,7.0.x,8.0.x,9.0.x] steps: - name: Checkout Code @@ -26,15 +26,31 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ matrix.dotnet-version }} - + + - name: Emit .NET 6.0 Framework Moniker + if: matrix.dotnet-version == '6.0.x' + run: echo "DOTNET_FX_VERSION=net6.0" >> $GITHUB_ENV + + - name: Emit .NET 7.0 Framework Moniker + if: matrix.dotnet-version == '7.0.x' + run: echo "DOTNET_FX_VERSION=net7.0" >> $GITHUB_ENV + + - name: Emit .NET 8.0 Framework Moniker + if: matrix.dotnet-version == '8.0.x' + run: echo "DOTNET_FX_VERSION=net8.0" >> $GITHUB_ENV + + - name: Emit .NET 9.0 Framework Moniker + if: matrix.dotnet-version == '9.0.x' + run: echo "DOTNET_FX_VERSION=net9.0" >> $GITHUB_ENV + - name: Install Dependencies - run: dotnet restore + run: dotnet restore -p:TargetFrameworks=${{ env.DOTNET_FX_VERSION }} - name: Build - run: dotnet build --configuration Release --no-restore --nologo + run: dotnet build --configuration Release --no-restore --nologo -f ${{ env.DOTNET_FX_VERSION }} - name: Test - run: dotnet test --configuration Release --no-build --no-restore --verbosity normal + run: dotnet test --configuration Release --no-build --no-restore --verbosity normal -f ${{ env.DOTNET_FX_VERSION }} publish: name: Publish Package @@ -49,7 +65,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install Dependencies run: dotnet restore diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 24cb5ed..f2a69bc 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -10,21 +10,37 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: [6.0.x, 7.0.x, 8.0.x] + dotnet-version: [6.0.x, 7.0.x, 8.0.x,9.0.x] steps: - uses: actions/checkout@v4 - - - name: Setup .NET + + - name: Emit .NET 6.0 Framework Moniker + if: matrix.dotnet-version == '6.0.x' + run: echo "DOTNET_FX_VERSION=net6.0" >> $GITHUB_ENV + + - name: Emit .NET 7.0 Framework Moniker + if: matrix.dotnet-version == '7.0.x' + run: echo "DOTNET_FX_VERSION=net7.0" >> $GITHUB_ENV + + - name: Emit .NET 8.0 Framework Moniker + if: matrix.dotnet-version == '8.0.x' + run: echo "DOTNET_FX_VERSION=net8.0" >> $GITHUB_ENV + + - name: Emit .NET 9.0 Framework Moniker + if: matrix.dotnet-version == '9.0.x' + run: echo "DOTNET_FX_VERSION=net9.0" >> $GITHUB_ENV + + - name: Setup .NET ${{ matrix.dotnet-version }} uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ matrix.dotnet-version }} - name: Install dependencies - run: dotnet restore + run: dotnet restore -p:TargetFrameworks=${{ env.DOTNET_FX_VERSION }} - name: Build - run: dotnet build -c Release --no-restore + run: dotnet build -c Release --no-restore -f ${{ env.DOTNET_FX_VERSION }} - name: Test - run: dotnet test -c Release --no-build --no-restore + run: dotnet test -c Release --no-build --no-restore -f ${{ env.DOTNET_FX_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26376e9..56caae2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,24 +19,41 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: [6.0.x, 7.0.x, 8.0.x] + dotnet-version: [6.0.x, 7.0.x, 8.0.x,9.0.x] steps: - uses: actions/checkout@v4 - - name: Setup .NET + - name: Emit .NET 6.0 Framework Moniker + if: matrix.dotnet-version == '6.0.x' + run: echo "DOTNET_FX_VERSION=net6.0" >> $GITHUB_ENV + + - name: Emit .NET 7.0 Framework Moniker + if: matrix.dotnet-version == '7.0.x' + run: echo "DOTNET_FX_VERSION=net7.0" >> $GITHUB_ENV + + - name: Emit .NET 8.0 Framework Moniker + if: matrix.dotnet-version == '8.0.x' + run: echo "DOTNET_FX_VERSION=net8.0" >> $GITHUB_ENV + + - name: Emit .NET 9.0 Framework Moniker + if: matrix.dotnet-version == '9.0.x' + run: echo "DOTNET_FX_VERSION=net9.0" >> $GITHUB_ENV + + + - name: Setup .NET ${{ matrix.dotnet-version }} uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ matrix.dotnet-version }} - name: Install dependencies - run: dotnet restore + run: dotnet restore -p:TargetFrameworks=${{ env.DOTNET_FX_VERSION }} - name: Build - run: dotnet build -c Release --no-restore + run: dotnet build -c Release --no-restore -f ${{ env.DOTNET_FX_VERSION }} - name: Test - run: dotnet test -c Release --no-build --no-restore + run: dotnet test -c Release --no-build --no-restore -f ${{ env.DOTNET_FX_VERSION }} publish: runs-on: ubuntu-latest @@ -48,7 +65,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Extract the Version run: echo "VERSION=$(echo ${{ github.event.release.tag_name }} | sed -e 's/^v//')" >> $GITHUB_ENV diff --git a/src/Deveel.Results/Deveel.Results.csproj b/src/Deveel.Results/Deveel.Results.csproj index 13d8750..269819b 100644 --- a/src/Deveel.Results/Deveel.Results.csproj +++ b/src/Deveel.Results/Deveel.Results.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 enable enable Deveel @@ -13,7 +13,7 @@ Antonello Provenzano Deveel - 2024 (C) Antonello Provenzano + 2024-2025 (C) Antonello Provenzano MIT Deveel Results A simple and unambitious library to implement the result pattern in services. diff --git a/src/Deveel.Results/OperationResultExtensions.cs b/src/Deveel.Results/OperationResultExtensions.cs index a9b69b0..a543ee6 100644 --- a/src/Deveel.Results/OperationResultExtensions.cs +++ b/src/Deveel.Results/OperationResultExtensions.cs @@ -20,17 +20,33 @@ public static class OperationResultExtensions public static bool IsSuccess(this IOperationResult result) => result.ResultType == OperationResultType.Success; - /// - /// Determines if the operation result is an error. - /// - /// - /// The operation result to check. - /// - /// - /// Returns if the operation result is an error, - /// otherwise . - /// - public static bool IsError(this IOperationResult result) + /// + /// Determines if the operation result is a success and has a value. + /// + /// + /// The type of the value that is expected to be returned by the operation. + /// + /// + /// The operation result to check. + /// + /// + /// Returns if the operation result is a success + /// and the value is not , otherwise . + /// + public static bool HasValue(this IOperationResult result) + => result.IsSuccess() && result.Value is not null; + + /// + /// Determines if the operation result is an error. + /// + /// + /// The operation result to check. + /// + /// + /// Returns if the operation result is an error, + /// otherwise . + /// + public static bool IsError(this IOperationResult result) => result.ResultType == OperationResultType.Error; /// diff --git a/src/Deveel.Results/ValidationErrorExtensions.cs b/src/Deveel.Results/ValidationErrorExtensions.cs new file mode 100644 index 0000000..669bf77 --- /dev/null +++ b/src/Deveel.Results/ValidationErrorExtensions.cs @@ -0,0 +1,44 @@ +namespace Deveel +{ + /// + /// Extensions for the contract. + /// + public static class ValidationErrorExtensions + { + /// + /// Gets a dictionary of member names and the list of error messages + /// + /// + /// The validation error to get the member errors from. + /// + /// + /// Returns a dictionary where the key is the member name and the value + /// is the list of error messages for that member. + /// + public static IDictionary GetMemberErrors(this IValidationError error) + { + ArgumentNullException.ThrowIfNull(error, nameof(error)); + + var results = new Dictionary>(); + + foreach (var result in error.ValidationResults) + { + foreach (var memberName in result.MemberNames) + { + if (String.IsNullOrWhiteSpace(result.ErrorMessage)) + continue; + + if (!results.TryGetValue(memberName, out var messages)) + { + messages = new List(); + results[memberName] = messages; + } + + messages.Add(result.ErrorMessage); + } + } + + return results.ToDictionary(x => x.Key, x => x.Value.ToArray()); + } + } +} diff --git a/test/Deveel.Results.XUnit/Deveel.Results.XUnit.csproj b/test/Deveel.Results.XUnit/Deveel.Results.XUnit.csproj index fec25d6..f297819 100644 --- a/test/Deveel.Results.XUnit/Deveel.Results.XUnit.csproj +++ b/test/Deveel.Results.XUnit/Deveel.Results.XUnit.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 enable enable @@ -11,10 +11,16 @@ - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Deveel.Results.XUnit/OperationErrorTests.cs b/test/Deveel.Results.XUnit/OperationErrorTests.cs index 9e51b36..3716e81 100644 --- a/test/Deveel.Results.XUnit/OperationErrorTests.cs +++ b/test/Deveel.Results.XUnit/OperationErrorTests.cs @@ -133,5 +133,74 @@ public static void OperationException_InnerExceptionIsOperationError() Assert.Equal("err.2", innerError.Code); Assert.Equal("biz", innerError.Domain); } - } + + [Fact] + public static void ValidationError_WithNullCode() + { + Assert.Throws(() => new OperationValidationError(null, "biz", Array.Empty())); + } + + [Fact] + public static void ValidationError_WithNullDomain() + { + Assert.Throws(() => new OperationValidationError("err.1", null, Array.Empty())); + } + + [Fact] + public static void ValidationError_WithNullResults() + { + Assert.Throws(() => new OperationValidationError("err.1", "biz", null)); + } + + [Fact] + public static void ValidationError_WithResults_GetMemberErrors() + { + var results = new[] { + new ValidationResult("First error of the validation", new []{ "Member1" }), + new ValidationResult("Second error of the validation", new []{"Member2"}) + }; + + var error = new OperationValidationError("err.1", "biz", results); + var memberErrors = error.GetMemberErrors(); + Assert.NotNull(memberErrors); + Assert.Equal(2, memberErrors.Count); + Assert.True(memberErrors.TryGetValue("Member1", out var member1Errors)); + Assert.NotNull(member1Errors); + Assert.Equal(1, member1Errors.Length); + Assert.Equal("First error of the validation", member1Errors[0]); + Assert.True(memberErrors.TryGetValue("Member2", out var member2Errors)); + Assert.NotNull(member2Errors); + Assert.Equal(1, member2Errors.Length); + Assert.Equal("Second error of the validation", member2Errors[0]); + } + + [Fact] + public static void ValidationError_WithResults_GetMemberErrors_Empty() + { + var error = new OperationValidationError("err.1", "biz", Array.Empty()); + var memberErrors = error.GetMemberErrors(); + Assert.NotNull(memberErrors); + Assert.Empty(memberErrors); + } + + [Fact] + public static void ValidationError_WithResultsForSameMember() + { + var results = new[] { + new ValidationResult("First error of the validation", new []{ "Member" }), + new ValidationResult("Second error of the validation", new []{"Member"}) + }; + + var error = new OperationValidationError("err.1", "biz", results); + var memberErrors = error.GetMemberErrors(); + + Assert.NotNull(memberErrors); + Assert.Single(memberErrors); + Assert.True(memberErrors.TryGetValue("Member", out var memberErrorsList)); + Assert.NotNull(memberErrorsList); + Assert.Equal(2, memberErrorsList.Length); + Assert.Equal("First error of the validation", memberErrorsList[0]); + Assert.Equal("Second error of the validation", memberErrorsList[1]); + } + } }