Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to the Validation Error Handling #3

Merged
merged 7 commits into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
28 changes: 22 additions & 6 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
29 changes: 23 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Deveel.Results/Deveel.Results.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Deveel</RootNamespace>
Expand All @@ -13,7 +13,7 @@
<PropertyGroup>
<Authors>Antonello Provenzano</Authors>
<Company>Deveel</Company>
<Copyright>2024 (C) Antonello Provenzano</Copyright>
<Copyright>2024-2025 (C) Antonello Provenzano</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Title>Deveel Results</Title>
<Description>A simple and unambitious library to implement the result pattern in services.</Description>
Expand Down
38 changes: 27 additions & 11 deletions src/Deveel.Results/OperationResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,33 @@
public static bool IsSuccess(this IOperationResult result)
=> result.ResultType == OperationResultType.Success;

/// <summary>
/// Determines if the operation result is an error.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is an error,
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsError(this IOperationResult result)
/// <summary>
/// Determines if the operation result is a success and has a value.
/// </summary>
/// <typeparam name="T">
/// The type of the value that is expected to be returned by the operation.
/// </typeparam>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is a success
/// and the value is not <see langword="null"/>, otherwise <see langword="false"/>.
/// </returns>
public static bool HasValue<T>(this IOperationResult<T> result)
=> result.IsSuccess() && result.Value is not null;

/// <summary>
/// Determines if the operation result is an error.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is an error,
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsError(this IOperationResult result)
=> result.ResultType == OperationResultType.Error;

/// <summary>
Expand Down
44 changes: 44 additions & 0 deletions src/Deveel.Results/ValidationErrorExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Deveel
{
/// <summary>
/// Extensions for the <see cref="IValidationError"/> contract.
/// </summary>
public static class ValidationErrorExtensions
{
/// <summary>
/// Gets a dictionary of member names and the list of error messages
/// </summary>
/// <param name="error">
/// The validation error to get the member errors from.
/// </param>
/// <returns>
/// Returns a dictionary where the key is the member name and the value
/// is the list of error messages for that member.
/// </returns>
public static IDictionary<string, string[]> GetMemberErrors(this IValidationError error)
{
ArgumentNullException.ThrowIfNull(error, nameof(error));

var results = new Dictionary<string, List<string>>();

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<string>();
results[memberName] = messages;
}

messages.Add(result.ErrorMessage);
}
}

return results.ToDictionary(x => x.Key, x => x.Value.ToArray());
}
}
}
16 changes: 11 additions & 5 deletions test/Deveel.Results.XUnit/Deveel.Results.XUnit.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand All @@ -11,10 +11,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
71 changes: 70 additions & 1 deletion test/Deveel.Results.XUnit/OperationErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@
[Fact]
public static void OperationError_WithNullCode()
{
Assert.Throws<ArgumentNullException>(() => new OperationError(null, "biz"));

Check warning on line 56 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 56 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 56 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 56 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (9.0.x)

Cannot convert null literal to non-nullable reference type.
}

[Fact]
public static void OperationError_WithNullDomain()
{
Assert.Throws<ArgumentNullException>(() => new OperationError("err.1", null));

Check warning on line 62 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 62 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 62 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 62 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (9.0.x)

Cannot convert null literal to non-nullable reference type.
}

[Fact]
Expand Down Expand Up @@ -133,5 +133,74 @@
Assert.Equal("err.2", innerError.Code);
Assert.Equal("biz", innerError.Domain);
}
}

[Fact]
public static void ValidationError_WithNullCode()
{
Assert.Throws<ArgumentNullException>(() => new OperationValidationError(null, "biz", Array.Empty<ValidationResult>()));

Check warning on line 140 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 140 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 140 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 140 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (9.0.x)

Cannot convert null literal to non-nullable reference type.
}

[Fact]
public static void ValidationError_WithNullDomain()
{
Assert.Throws<ArgumentNullException>(() => new OperationValidationError("err.1", null, Array.Empty<ValidationResult>()));

Check warning on line 146 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 146 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 146 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 146 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (9.0.x)

Cannot convert null literal to non-nullable reference type.
}

[Fact]
public static void ValidationError_WithNullResults()
{
Assert.Throws<ArgumentNullException>(() => new OperationValidationError("err.1", "biz", null));

Check warning on line 152 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 152 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 152 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Cannot convert null literal to non-nullable reference type.

Check warning on line 152 in test/Deveel.Results.XUnit/OperationErrorTests.cs

View workflow job for this annotation

GitHub Actions / build (9.0.x)

Cannot convert null literal to non-nullable reference type.
}

[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<ValidationResult>());
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]);
}
}
}
Loading