Skip to content

Commit ad172b5

Browse files
committed
refactoring for results and problems
1 parent 348c75d commit ad172b5

12 files changed

Lines changed: 381 additions & 93 deletions

File tree

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
<RepositoryUrl>https://github.com/managedcode/Communication</RepositoryUrl>
2626
<PackageProjectUrl>https://github.com/managedcode/Communication</PackageProjectUrl>
2727
<Product>Managed Code - Communication</Product>
28-
<Version>9.5.0</Version>
29-
<PackageVersion>9.5.0</PackageVersion>
28+
<Version>9.5.1</Version>
29+
<PackageVersion>9.5.1</PackageVersion>
3030

3131
</PropertyGroup>
3232
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">

ManagedCode.Communication.Tests/Results/CollectionResultTests.cs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,10 @@ public void ThrowIfFail_WithFailedResult_ShouldThrow()
379379
// Act & Assert
380380
result.Invoking(r => r.ThrowIfFail())
381381
.Should()
382-
.Throw<Exception>()
383-
.WithMessage("Operation failed - Something went wrong - (HTTP 400)");
382+
.Throw<ProblemException>()
383+
.Which.Problem.Title
384+
.Should()
385+
.Be("Operation failed");
384386
}
385387

386388
[Fact]
@@ -396,4 +398,89 @@ public void ImplicitOperator_ToBool_ShouldReturnIsSuccess()
396398
((bool)failResult).Should()
397399
.BeFalse();
398400
}
401+
402+
[Fact]
403+
public void TryGetProblem_WithSuccessfulResult_ShouldReturnFalse()
404+
{
405+
// Arrange
406+
var result = CollectionResult<string>.Succeed(new[] { "item1", "item2" });
407+
408+
// Act
409+
var hasProblem = result.TryGetProblem(out var problem);
410+
411+
// Assert
412+
hasProblem.Should().BeFalse();
413+
problem.Should().BeNull();
414+
}
415+
416+
[Fact]
417+
public void TryGetProblem_WithFailedResult_ShouldReturnTrueAndProblem()
418+
{
419+
// Arrange
420+
var expectedProblem = Problem.Create("https://httpstatuses.io/503", "Service Unavailable", 503, "Service is temporarily unavailable");
421+
var result = CollectionResult<string>.Fail(expectedProblem);
422+
423+
// Act
424+
var hasProblem = result.TryGetProblem(out var problem);
425+
426+
// Assert
427+
hasProblem.Should().BeTrue();
428+
problem.Should().NotBeNull();
429+
problem.Should().Be(expectedProblem);
430+
}
431+
432+
[Fact]
433+
public void TryGetProblem_WithEmptyCollection_ButSuccessful_ShouldReturnFalse()
434+
{
435+
// Arrange
436+
var result = CollectionResult<string>.Empty();
437+
438+
// Act
439+
var hasProblem = result.TryGetProblem(out var problem);
440+
441+
// Assert
442+
hasProblem.Should().BeFalse();
443+
problem.Should().BeNull();
444+
result.IsEmpty.Should().BeTrue();
445+
result.IsSuccess.Should().BeTrue();
446+
}
447+
448+
[Fact]
449+
public void ThrowIfFail_WithProblemException_ShouldPreserveProblemDetails()
450+
{
451+
// Arrange
452+
var problem = Problem.Create("https://httpstatuses.io/429", "Too Many Requests", 429, "Rate limit exceeded");
453+
problem.Extensions["retryAfter"] = 60;
454+
var result = CollectionResult<string>.Fail(problem);
455+
456+
// Act & Assert
457+
var exception = result.Invoking(r => r.ThrowIfFail())
458+
.Should()
459+
.Throw<ProblemException>()
460+
.Which;
461+
462+
exception.Problem.Should().BeEquivalentTo(problem);
463+
exception.Problem.Extensions["retryAfter"].Should().Be(60);
464+
}
465+
466+
[Fact]
467+
public void ThrowIfFail_WithValidationFailure_ShouldThrowWithValidationDetails()
468+
{
469+
// Arrange
470+
var result = CollectionResult<string>.FailValidation(("filter", "Invalid filter format"), ("pageSize", "Page size must be between 1 and 100"));
471+
472+
// Act & Assert
473+
var exception = result.Invoking(r => r.ThrowIfFail())
474+
.Should()
475+
.Throw<ProblemException>()
476+
.Which;
477+
478+
exception.Problem.Title.Should().Be("Validation Failed");
479+
exception.Problem.StatusCode.Should().Be(400);
480+
481+
var validationErrors = exception.Problem.GetValidationErrors();
482+
validationErrors.Should().NotBeNull();
483+
validationErrors!["filter"].Should().Contain("Invalid filter format");
484+
validationErrors!["pageSize"].Should().Contain("Page size must be between 1 and 100");
485+
}
399486
}

ManagedCode.Communication.Tests/Results/ResultTTests.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,83 @@ public void From_WithExceptionThrowingFunc_ShouldCreateFailedResult()
344344
.Should()
345345
.Be("Test exception");
346346
}
347+
348+
[Fact]
349+
public void TryGetProblem_WithSuccessfulResult_ShouldReturnFalse()
350+
{
351+
// Arrange
352+
var result = Result<string>.Succeed("test value");
353+
354+
// Act
355+
var hasProblem = result.TryGetProblem(out var problem);
356+
357+
// Assert
358+
hasProblem.Should().BeFalse();
359+
problem.Should().BeNull();
360+
}
361+
362+
[Fact]
363+
public void TryGetProblem_WithFailedResult_ShouldReturnTrueAndProblem()
364+
{
365+
// Arrange
366+
var expectedProblem = Problem.Create("https://httpstatuses.io/500", "Internal Server Error", 500, "Server error occurred");
367+
var result = Result<string>.Fail(expectedProblem);
368+
369+
// Act
370+
var hasProblem = result.TryGetProblem(out var problem);
371+
372+
// Assert
373+
hasProblem.Should().BeTrue();
374+
problem.Should().NotBeNull();
375+
problem.Should().Be(expectedProblem);
376+
}
377+
378+
[Fact]
379+
public void ThrowIfFail_WithSuccessfulResult_ShouldNotThrow()
380+
{
381+
// Arrange
382+
var result = Result<int>.Succeed(42);
383+
384+
// Act & Assert
385+
result.Invoking(r => r.ThrowIfFail())
386+
.Should()
387+
.NotThrow();
388+
}
389+
390+
[Fact]
391+
public void ThrowIfFail_WithFailedResult_ShouldThrowProblemException()
392+
{
393+
// Arrange
394+
var problem = Problem.Create("https://httpstatuses.io/400", "Bad Request", 400, "Invalid input data");
395+
var result = Result<string>.Fail(problem);
396+
397+
// Act & Assert
398+
result.Invoking(r => r.ThrowIfFail())
399+
.Should()
400+
.Throw<ProblemException>()
401+
.Which.Problem
402+
.Should()
403+
.BeEquivalentTo(problem);
404+
}
405+
406+
[Fact]
407+
public void ThrowIfFail_WithValidationFailure_ShouldThrowWithValidationDetails()
408+
{
409+
// Arrange
410+
var result = Result<string>.FailValidation(("username", "Username is required"), ("email", "Invalid email format"));
411+
412+
// Act & Assert
413+
var exception = result.Invoking(r => r.ThrowIfFail())
414+
.Should()
415+
.Throw<ProblemException>()
416+
.Which;
417+
418+
exception.Problem.Title.Should().Be("Validation Failed");
419+
exception.Problem.StatusCode.Should().Be(400);
420+
421+
var validationErrors = exception.Problem.GetValidationErrors();
422+
validationErrors.Should().NotBeNull();
423+
validationErrors!["username"].Should().Contain("Username is required");
424+
validationErrors!["email"].Should().Contain("Invalid email format");
425+
}
347426
}

ManagedCode.Communication.Tests/Results/ResultTests.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,10 @@ public void ThrowIfFail_WithFailedResult_ShouldThrow()
198198
// Act & Assert
199199
result.Invoking(r => r.ThrowIfFail())
200200
.Should()
201-
.Throw<Exception>()
202-
.WithMessage("Operation failed - Something went wrong - (HTTP 400)");
201+
.Throw<ProblemException>()
202+
.Which.Problem.Title
203+
.Should()
204+
.Be("Operation failed");
203205
}
204206

205207
[Fact]
@@ -296,4 +298,70 @@ public void Try_WithExceptionThrowingAction_ShouldCreateFailedResult()
296298
.Should()
297299
.Be("Test exception");
298300
}
301+
302+
[Fact]
303+
public void TryGetProblem_WithSuccessfulResult_ShouldReturnFalse()
304+
{
305+
// Arrange
306+
var result = Result.Succeed();
307+
308+
// Act
309+
var hasProblem = result.TryGetProblem(out var problem);
310+
311+
// Assert
312+
hasProblem.Should().BeFalse();
313+
problem.Should().BeNull();
314+
}
315+
316+
[Fact]
317+
public void TryGetProblem_WithFailedResult_ShouldReturnTrueAndProblem()
318+
{
319+
// Arrange
320+
var expectedProblem = Problem.Create("https://httpstatuses.io/400", "Bad Request", 400, "Invalid input");
321+
var result = Result.Fail(expectedProblem);
322+
323+
// Act
324+
var hasProblem = result.TryGetProblem(out var problem);
325+
326+
// Assert
327+
hasProblem.Should().BeTrue();
328+
problem.Should().NotBeNull();
329+
problem.Should().Be(expectedProblem);
330+
}
331+
332+
[Fact]
333+
public void TryGetProblem_WithValidationResult_ShouldReturnTrueAndValidationProblem()
334+
{
335+
// Arrange
336+
var result = Result.FailValidation(("email", "Email is required"));
337+
338+
// Act
339+
var hasProblem = result.TryGetProblem(out var problem);
340+
341+
// Assert
342+
hasProblem.Should().BeTrue();
343+
problem.Should().NotBeNull();
344+
problem!.Title.Should().Be("Validation Failed");
345+
problem.StatusCode.Should().Be(400);
346+
347+
var validationErrors = problem.GetValidationErrors();
348+
validationErrors.Should().NotBeNull();
349+
validationErrors!["email"].Should().Contain("Email is required");
350+
}
351+
352+
[Fact]
353+
public void ThrowIfFail_WithProblemException_ShouldPreserveProblemDetails()
354+
{
355+
// Arrange
356+
var problem = Problem.Create("https://httpstatuses.io/404", "Not Found", 404, "User not found");
357+
var result = Result.Fail(problem);
358+
359+
// Act & Assert
360+
result.Invoking(r => r.ThrowIfFail())
361+
.Should()
362+
.Throw<ProblemException>()
363+
.Which.Problem
364+
.Should()
365+
.BeEquivalentTo(problem);
366+
}
299367
}

ManagedCode.Communication/CollectionResultT/CollectionResult.cs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,30 +70,28 @@ internal CollectionResult(bool isSuccess, T[]? collection, int pageNumber, int p
7070
public bool HasItems => Collection?.Length > 0;
7171

7272
[JsonIgnore]
73+
[MemberNotNullWhen(true, nameof(Problem))]
7374
public bool HasProblem => Problem != null;
7475

7576
#region IResultProblem Implementation
7677

7778
public bool ThrowIfFail()
7879
{
79-
if (IsSuccess && !HasProblem)
80+
if (HasProblem)
8081
{
81-
return false;
82+
throw Problem;
8283
}
83-
84-
if (Problem != null)
85-
{
86-
throw new ProblemException(Problem);
87-
}
88-
89-
if (IsFailed)
90-
{
91-
throw new Exception("Operation failed");
92-
}
93-
84+
9485
return false;
9586
}
9687

88+
[MemberNotNullWhen(true, nameof(Problem))]
89+
public bool TryGetProblem([MaybeNullWhen(false)] out Problem problem)
90+
{
91+
problem = Problem;
92+
return HasProblem;
93+
}
94+
9795
#endregion
9896

9997

ManagedCode.Communication/IResultProblem.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
namespace ManagedCode.Communication;
24

35
/// <summary>
@@ -19,4 +21,11 @@ public interface IResultProblem
1921
/// Throws an exception if the result indicates a failure.
2022
/// </summary>
2123
bool ThrowIfFail();
22-
}
24+
25+
/// <summary>
26+
/// Tries to get the problem from the result.
27+
/// </summary>
28+
/// <param name="problem">When this method returns, contains the problem if the result has a problem; otherwise, null.</param>
29+
/// <returns>true if the result has a problem; otherwise, false.</returns>
30+
bool TryGetProblem([MaybeNullWhen(false)] out Problem problem);
31+
}

ManagedCode.Communication/Problem.Extensions.cs renamed to ManagedCode.Communication/Problem/Problem.Extensions.cs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -187,40 +187,3 @@ public Problem WithExtensions(IDictionary<string, object?> additionalExtensions)
187187
}
188188
}
189189

190-
/// <summary>
191-
/// Extension methods for creating Problems from various sources
192-
/// </summary>
193-
public static class ProblemCreationExtensions
194-
{
195-
/// <summary>
196-
/// Creates a Problem from an exception
197-
/// </summary>
198-
public static Problem ToProblem(this Exception exception, int statusCode = 500)
199-
{
200-
return Problem.FromException(exception, statusCode);
201-
}
202-
203-
/// <summary>
204-
/// Creates a Problem from an exception with HttpStatusCode
205-
/// </summary>
206-
public static Problem ToProblem(this Exception exception, HttpStatusCode statusCode)
207-
{
208-
return Problem.FromException(exception, (int)statusCode);
209-
}
210-
211-
/// <summary>
212-
/// Creates a Problem from an enum
213-
/// </summary>
214-
public static Problem ToProblem<TEnum>(this TEnum errorCode, string? detail = null, int statusCode = 400) where TEnum : Enum
215-
{
216-
return Problem.FromEnum(errorCode, detail, statusCode);
217-
}
218-
219-
/// <summary>
220-
/// Creates a Problem from an enum with HttpStatusCode
221-
/// </summary>
222-
public static Problem ToProblem<TEnum>(this TEnum errorCode, string? detail, HttpStatusCode statusCode) where TEnum : Enum
223-
{
224-
return Problem.FromEnum(errorCode, detail, (int)statusCode);
225-
}
226-
}

0 commit comments

Comments
 (0)