Skip to content

Commit 17d8748

Browse files
authored
Merge pull request #3 from deveel/validation-error-handling
Improvements to the Validation Error Handling
2 parents fac5069 + 1c16d2b commit 17d8748

File tree

8 files changed

+221
-37
lines changed

8 files changed

+221
-37
lines changed

.github/workflows/cicd.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
dotnet-version: [6.0.x,7.0.x,8.0.x]
19+
dotnet-version: [6.0.x,7.0.x,8.0.x,9.0.x]
2020

2121
steps:
2222
- name: Checkout Code
@@ -26,15 +26,31 @@ jobs:
2626
uses: actions/setup-dotnet@v4
2727
with:
2828
dotnet-version: ${{ matrix.dotnet-version }}
29-
29+
30+
- name: Emit .NET 6.0 Framework Moniker
31+
if: matrix.dotnet-version == '6.0.x'
32+
run: echo "DOTNET_FX_VERSION=net6.0" >> $GITHUB_ENV
33+
34+
- name: Emit .NET 7.0 Framework Moniker
35+
if: matrix.dotnet-version == '7.0.x'
36+
run: echo "DOTNET_FX_VERSION=net7.0" >> $GITHUB_ENV
37+
38+
- name: Emit .NET 8.0 Framework Moniker
39+
if: matrix.dotnet-version == '8.0.x'
40+
run: echo "DOTNET_FX_VERSION=net8.0" >> $GITHUB_ENV
41+
42+
- name: Emit .NET 9.0 Framework Moniker
43+
if: matrix.dotnet-version == '9.0.x'
44+
run: echo "DOTNET_FX_VERSION=net9.0" >> $GITHUB_ENV
45+
3046
- name: Install Dependencies
31-
run: dotnet restore
47+
run: dotnet restore -p:TargetFrameworks=${{ env.DOTNET_FX_VERSION }}
3248

3349
- name: Build
34-
run: dotnet build --configuration Release --no-restore --nologo
50+
run: dotnet build --configuration Release --no-restore --nologo -f ${{ env.DOTNET_FX_VERSION }}
3551

3652
- name: Test
37-
run: dotnet test --configuration Release --no-build --no-restore --verbosity normal
53+
run: dotnet test --configuration Release --no-build --no-restore --verbosity normal -f ${{ env.DOTNET_FX_VERSION }}
3854

3955
publish:
4056
name: Publish Package
@@ -49,7 +65,7 @@ jobs:
4965
- name: Setup .NET
5066
uses: actions/setup-dotnet@v4
5167
with:
52-
dotnet-version: 8.0.x
68+
dotnet-version: 9.0.x
5369

5470
- name: Install Dependencies
5571
run: dotnet restore

.github/workflows/pr-build.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,37 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
dotnet-version: [6.0.x, 7.0.x, 8.0.x]
13+
dotnet-version: [6.0.x, 7.0.x, 8.0.x,9.0.x]
1414

1515
steps:
1616
- uses: actions/checkout@v4
17-
18-
- name: Setup .NET
17+
18+
- name: Emit .NET 6.0 Framework Moniker
19+
if: matrix.dotnet-version == '6.0.x'
20+
run: echo "DOTNET_FX_VERSION=net6.0" >> $GITHUB_ENV
21+
22+
- name: Emit .NET 7.0 Framework Moniker
23+
if: matrix.dotnet-version == '7.0.x'
24+
run: echo "DOTNET_FX_VERSION=net7.0" >> $GITHUB_ENV
25+
26+
- name: Emit .NET 8.0 Framework Moniker
27+
if: matrix.dotnet-version == '8.0.x'
28+
run: echo "DOTNET_FX_VERSION=net8.0" >> $GITHUB_ENV
29+
30+
- name: Emit .NET 9.0 Framework Moniker
31+
if: matrix.dotnet-version == '9.0.x'
32+
run: echo "DOTNET_FX_VERSION=net9.0" >> $GITHUB_ENV
33+
34+
- name: Setup .NET ${{ matrix.dotnet-version }}
1935
uses: actions/setup-dotnet@v4
2036
with:
2137
dotnet-version: ${{ matrix.dotnet-version }}
2238

2339
- name: Install dependencies
24-
run: dotnet restore
40+
run: dotnet restore -p:TargetFrameworks=${{ env.DOTNET_FX_VERSION }}
2541

2642
- name: Build
27-
run: dotnet build -c Release --no-restore
43+
run: dotnet build -c Release --no-restore -f ${{ env.DOTNET_FX_VERSION }}
2844

2945
- name: Test
30-
run: dotnet test -c Release --no-build --no-restore
46+
run: dotnet test -c Release --no-build --no-restore -f ${{ env.DOTNET_FX_VERSION }}

.github/workflows/release.yml

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,41 @@ jobs:
1919
runs-on: ubuntu-latest
2020
strategy:
2121
matrix:
22-
dotnet-version: [6.0.x, 7.0.x, 8.0.x]
22+
dotnet-version: [6.0.x, 7.0.x, 8.0.x,9.0.x]
2323

2424
steps:
2525
- uses: actions/checkout@v4
2626

27-
- name: Setup .NET
27+
- name: Emit .NET 6.0 Framework Moniker
28+
if: matrix.dotnet-version == '6.0.x'
29+
run: echo "DOTNET_FX_VERSION=net6.0" >> $GITHUB_ENV
30+
31+
- name: Emit .NET 7.0 Framework Moniker
32+
if: matrix.dotnet-version == '7.0.x'
33+
run: echo "DOTNET_FX_VERSION=net7.0" >> $GITHUB_ENV
34+
35+
- name: Emit .NET 8.0 Framework Moniker
36+
if: matrix.dotnet-version == '8.0.x'
37+
run: echo "DOTNET_FX_VERSION=net8.0" >> $GITHUB_ENV
38+
39+
- name: Emit .NET 9.0 Framework Moniker
40+
if: matrix.dotnet-version == '9.0.x'
41+
run: echo "DOTNET_FX_VERSION=net9.0" >> $GITHUB_ENV
42+
43+
44+
- name: Setup .NET ${{ matrix.dotnet-version }}
2845
uses: actions/setup-dotnet@v4
2946
with:
3047
dotnet-version: ${{ matrix.dotnet-version }}
3148

3249
- name: Install dependencies
33-
run: dotnet restore
50+
run: dotnet restore -p:TargetFrameworks=${{ env.DOTNET_FX_VERSION }}
3451

3552
- name: Build
36-
run: dotnet build -c Release --no-restore
53+
run: dotnet build -c Release --no-restore -f ${{ env.DOTNET_FX_VERSION }}
3754

3855
- name: Test
39-
run: dotnet test -c Release --no-build --no-restore
56+
run: dotnet test -c Release --no-build --no-restore -f ${{ env.DOTNET_FX_VERSION }}
4057

4158
publish:
4259
runs-on: ubuntu-latest
@@ -48,7 +65,7 @@ jobs:
4865
- name: Setup .NET
4966
uses: actions/setup-dotnet@v4
5067
with:
51-
dotnet-version: 8.0.x
68+
dotnet-version: 9.0.x
5269

5370
- name: Extract the Version
5471
run: echo "VERSION=$(echo ${{ github.event.release.tag_name }} | sed -e 's/^v//')" >> $GITHUB_ENV

src/Deveel.Results/Deveel.Results.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
4+
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<RootNamespace>Deveel</RootNamespace>
@@ -13,7 +13,7 @@
1313
<PropertyGroup>
1414
<Authors>Antonello Provenzano</Authors>
1515
<Company>Deveel</Company>
16-
<Copyright>2024 (C) Antonello Provenzano</Copyright>
16+
<Copyright>2024-2025 (C) Antonello Provenzano</Copyright>
1717
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1818
<Title>Deveel Results</Title>
1919
<Description>A simple and unambitious library to implement the result pattern in services.</Description>

src/Deveel.Results/OperationResultExtensions.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,33 @@ public static class OperationResultExtensions
2020
public static bool IsSuccess(this IOperationResult result)
2121
=> result.ResultType == OperationResultType.Success;
2222

23-
/// <summary>
24-
/// Determines if the operation result is an error.
25-
/// </summary>
26-
/// <param name="result">
27-
/// The operation result to check.
28-
/// </param>
29-
/// <returns>
30-
/// Returns <see langword="true"/> if the operation result is an error,
31-
/// otherwise <see langword="false"/>.
32-
/// </returns>
33-
public static bool IsError(this IOperationResult result)
23+
/// <summary>
24+
/// Determines if the operation result is a success and has a value.
25+
/// </summary>
26+
/// <typeparam name="T">
27+
/// The type of the value that is expected to be returned by the operation.
28+
/// </typeparam>
29+
/// <param name="result">
30+
/// The operation result to check.
31+
/// </param>
32+
/// <returns>
33+
/// Returns <see langword="true"/> if the operation result is a success
34+
/// and the value is not <see langword="null"/>, otherwise <see langword="false"/>.
35+
/// </returns>
36+
public static bool HasValue<T>(this IOperationResult<T> result)
37+
=> result.IsSuccess() && result.Value is not null;
38+
39+
/// <summary>
40+
/// Determines if the operation result is an error.
41+
/// </summary>
42+
/// <param name="result">
43+
/// The operation result to check.
44+
/// </param>
45+
/// <returns>
46+
/// Returns <see langword="true"/> if the operation result is an error,
47+
/// otherwise <see langword="false"/>.
48+
/// </returns>
49+
public static bool IsError(this IOperationResult result)
3450
=> result.ResultType == OperationResultType.Error;
3551

3652
/// <summary>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace Deveel
2+
{
3+
/// <summary>
4+
/// Extensions for the <see cref="IValidationError"/> contract.
5+
/// </summary>
6+
public static class ValidationErrorExtensions
7+
{
8+
/// <summary>
9+
/// Gets a dictionary of member names and the list of error messages
10+
/// </summary>
11+
/// <param name="error">
12+
/// The validation error to get the member errors from.
13+
/// </param>
14+
/// <returns>
15+
/// Returns a dictionary where the key is the member name and the value
16+
/// is the list of error messages for that member.
17+
/// </returns>
18+
public static IDictionary<string, string[]> GetMemberErrors(this IValidationError error)
19+
{
20+
ArgumentNullException.ThrowIfNull(error, nameof(error));
21+
22+
var results = new Dictionary<string, List<string>>();
23+
24+
foreach (var result in error.ValidationResults)
25+
{
26+
foreach (var memberName in result.MemberNames)
27+
{
28+
if (String.IsNullOrWhiteSpace(result.ErrorMessage))
29+
continue;
30+
31+
if (!results.TryGetValue(memberName, out var messages))
32+
{
33+
messages = new List<string>();
34+
results[memberName] = messages;
35+
}
36+
37+
messages.Add(result.ErrorMessage);
38+
}
39+
}
40+
41+
return results.ToDictionary(x => x.Key, x => x.Value.ToArray());
42+
}
43+
}
44+
}

test/Deveel.Results.XUnit/Deveel.Results.XUnit.csproj

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
4+
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77

@@ -11,10 +11,16 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<PackageReference Include="coverlet.collector" Version="6.0.0" />
15-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
16-
<PackageReference Include="xunit" Version="2.5.3" />
17-
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
14+
<PackageReference Include="coverlet.collector" Version="6.0.4">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
19+
<PackageReference Include="xunit" Version="2.9.3" />
20+
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
1824
</ItemGroup>
1925

2026
<ItemGroup>

test/Deveel.Results.XUnit/OperationErrorTests.cs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,74 @@ public static void OperationException_InnerExceptionIsOperationError()
133133
Assert.Equal("err.2", innerError.Code);
134134
Assert.Equal("biz", innerError.Domain);
135135
}
136-
}
136+
137+
[Fact]
138+
public static void ValidationError_WithNullCode()
139+
{
140+
Assert.Throws<ArgumentNullException>(() => new OperationValidationError(null, "biz", Array.Empty<ValidationResult>()));
141+
}
142+
143+
[Fact]
144+
public static void ValidationError_WithNullDomain()
145+
{
146+
Assert.Throws<ArgumentNullException>(() => new OperationValidationError("err.1", null, Array.Empty<ValidationResult>()));
147+
}
148+
149+
[Fact]
150+
public static void ValidationError_WithNullResults()
151+
{
152+
Assert.Throws<ArgumentNullException>(() => new OperationValidationError("err.1", "biz", null));
153+
}
154+
155+
[Fact]
156+
public static void ValidationError_WithResults_GetMemberErrors()
157+
{
158+
var results = new[] {
159+
new ValidationResult("First error of the validation", new []{ "Member1" }),
160+
new ValidationResult("Second error of the validation", new []{"Member2"})
161+
};
162+
163+
var error = new OperationValidationError("err.1", "biz", results);
164+
var memberErrors = error.GetMemberErrors();
165+
Assert.NotNull(memberErrors);
166+
Assert.Equal(2, memberErrors.Count);
167+
Assert.True(memberErrors.TryGetValue("Member1", out var member1Errors));
168+
Assert.NotNull(member1Errors);
169+
Assert.Equal(1, member1Errors.Length);
170+
Assert.Equal("First error of the validation", member1Errors[0]);
171+
Assert.True(memberErrors.TryGetValue("Member2", out var member2Errors));
172+
Assert.NotNull(member2Errors);
173+
Assert.Equal(1, member2Errors.Length);
174+
Assert.Equal("Second error of the validation", member2Errors[0]);
175+
}
176+
177+
[Fact]
178+
public static void ValidationError_WithResults_GetMemberErrors_Empty()
179+
{
180+
var error = new OperationValidationError("err.1", "biz", Array.Empty<ValidationResult>());
181+
var memberErrors = error.GetMemberErrors();
182+
Assert.NotNull(memberErrors);
183+
Assert.Empty(memberErrors);
184+
}
185+
186+
[Fact]
187+
public static void ValidationError_WithResultsForSameMember()
188+
{
189+
var results = new[] {
190+
new ValidationResult("First error of the validation", new []{ "Member" }),
191+
new ValidationResult("Second error of the validation", new []{"Member"})
192+
};
193+
194+
var error = new OperationValidationError("err.1", "biz", results);
195+
var memberErrors = error.GetMemberErrors();
196+
197+
Assert.NotNull(memberErrors);
198+
Assert.Single(memberErrors);
199+
Assert.True(memberErrors.TryGetValue("Member", out var memberErrorsList));
200+
Assert.NotNull(memberErrorsList);
201+
Assert.Equal(2, memberErrorsList.Length);
202+
Assert.Equal("First error of the validation", memberErrorsList[0]);
203+
Assert.Equal("Second error of the validation", memberErrorsList[1]);
204+
}
205+
}
137206
}

0 commit comments

Comments
 (0)