Skip to content

Commit adc3a91

Browse files
committed
Handle result type casting
1 parent a5952ef commit adc3a91

File tree

3 files changed

+190
-2
lines changed

3 files changed

+190
-2
lines changed

src/Foundatio.Mediator.Abstractions/Result.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Reflection;
2-
31
namespace Foundatio.Mediator;
42

53
/// <summary>
@@ -69,6 +67,22 @@ protected Result(ResultStatus status)
6967
/// <returns>null for non-generic Result.</returns>
7068
public virtual object? GetValue() => null;
7169

70+
/// <summary>
71+
/// Converts this Result to a Result&lt;T&gt; with the same status and properties but with default value.
72+
/// </summary>
73+
/// <typeparam name="T">The type of the result value.</typeparam>
74+
/// <returns>A Result&lt;T&gt; with the same status and properties but with default value.</returns>
75+
public Result<T> Cast<T>()
76+
{
77+
var convertedResult = new Result<T>(Status);
78+
convertedResult.Errors = Errors;
79+
convertedResult.SuccessMessage = SuccessMessage;
80+
convertedResult.CorrelationId = CorrelationId;
81+
convertedResult.Location = Location;
82+
convertedResult.ValidationErrors = ValidationErrors;
83+
return convertedResult;
84+
}
85+
7286
/// <summary>
7387
/// Creates a successful result.
7488
/// </summary>

src/Foundatio.Mediator/HandlerWrapperGenerator.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,13 @@ private static string GetSafeCastExpression(string handlerResultVar, HandlerInfo
12981298
? GetUnwrappedReturnType(handler)
12991299
: handler.OriginalReturnTypeName;
13001300

1301+
// Special handling for Result to Result<T> conversion
1302+
if (returnType.StartsWith("Foundatio.Mediator.Result<") && returnType != "Foundatio.Mediator.Result")
1303+
{
1304+
// Check if the value might be a non-generic Result that needs conversion to Result<T>
1305+
return $"{handlerResultVar}.Value is Foundatio.Mediator.Result result ? ({returnType})result : ({returnType}?){handlerResultVar}.Value ?? default({returnType})!";
1306+
}
1307+
13011308
if (IsReferenceType(returnType))
13021309
{
13031310
// For reference types, provide a null-coalescing fallback to satisfy non-nullable return types
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
using Xunit;
2+
3+
namespace Foundatio.Mediator.Tests;
4+
5+
public class ResultCastingTest
6+
{
7+
public class User
8+
{
9+
public string Name { get; set; } = string.Empty;
10+
public int Id { get; set; }
11+
}
12+
13+
[Fact]
14+
public void ExplicitCast_FromResultToGenericResult_ShouldWork()
15+
{
16+
// Arrange
17+
var originalResult = Result.Conflict("Resource is locked", "Another user is editing this resource");
18+
19+
// Act - This is the syntax we want to support: (Result<T>)Result.Conflict()
20+
var castedResult = (Result<User>)originalResult;
21+
22+
// Assert
23+
Assert.NotNull(castedResult);
24+
Assert.Equal(ResultStatus.Conflict, castedResult.Status);
25+
Assert.False(castedResult.IsSuccess);
26+
Assert.Null(castedResult.Value); // Should be default(User) which is null
27+
Assert.Equal(typeof(User), castedResult.ValueType);
28+
29+
// Verify all properties are copied
30+
Assert.Equal(originalResult.Errors, castedResult.Errors);
31+
Assert.Equal(originalResult.SuccessMessage, castedResult.SuccessMessage);
32+
Assert.Equal(originalResult.CorrelationId, castedResult.CorrelationId);
33+
Assert.Equal(originalResult.Location, castedResult.Location);
34+
Assert.Equal(originalResult.ValidationErrors, castedResult.ValidationErrors);
35+
}
36+
37+
[Fact]
38+
public void ExplicitCast_FromSuccessResult_ShouldPreserveSuccessStatus()
39+
{
40+
// Arrange
41+
var originalResult = Result.Success("Operation completed successfully");
42+
43+
// Act
44+
var castedResult = (Result<User>)originalResult;
45+
46+
// Assert
47+
Assert.NotNull(castedResult);
48+
Assert.Equal(ResultStatus.Ok, castedResult.Status);
49+
Assert.True(castedResult.IsSuccess);
50+
Assert.Null(castedResult.Value); // Should be default(User) which is null
51+
Assert.Equal("Operation completed successfully", castedResult.SuccessMessage);
52+
}
53+
54+
[Fact]
55+
public void ExplicitCast_FromCreatedResult_ShouldPreserveLocation()
56+
{
57+
// Arrange
58+
var originalResult = Result.Created("/api/users/123");
59+
60+
// Act
61+
var castedResult = (Result<User>)originalResult;
62+
63+
// Assert
64+
Assert.NotNull(castedResult);
65+
Assert.Equal(ResultStatus.Created, castedResult.Status);
66+
Assert.True(castedResult.IsSuccess);
67+
Assert.Null(castedResult.Value); // Should be default(User) which is null
68+
Assert.Equal("/api/users/123", castedResult.Location);
69+
}
70+
71+
[Fact]
72+
public void ExplicitCast_FromErrorResult_ShouldPreserveErrors()
73+
{
74+
// Arrange
75+
var originalResult = Result.Error("Database connection failed", "Timeout occurred");
76+
77+
// Act
78+
var castedResult = (Result<User>)originalResult;
79+
80+
// Assert
81+
Assert.NotNull(castedResult);
82+
Assert.Equal(ResultStatus.Error, castedResult.Status);
83+
Assert.False(castedResult.IsSuccess);
84+
Assert.Null(castedResult.Value); // Should be default(User) which is null
85+
Assert.Equal(2, castedResult.Errors.Count());
86+
Assert.Contains("Database connection failed", castedResult.Errors);
87+
Assert.Contains("Timeout occurred", castedResult.Errors);
88+
}
89+
90+
[Fact]
91+
public void ExplicitCast_FromInvalidResult_ShouldPreserveValidationErrors()
92+
{
93+
// Arrange
94+
var validationError = new ValidationError
95+
{
96+
Identifier = "Name",
97+
ErrorMessage = "Name is required",
98+
Severity = ValidationSeverity.Error
99+
};
100+
var originalResult = Result.Invalid(validationError);
101+
102+
// Act
103+
var castedResult = (Result<User>)originalResult;
104+
105+
// Assert
106+
Assert.NotNull(castedResult);
107+
Assert.Equal(ResultStatus.Invalid, castedResult.Status);
108+
Assert.False(castedResult.IsSuccess);
109+
Assert.Null(castedResult.Value); // Should be default(User) which is null
110+
Assert.Single(castedResult.ValidationErrors);
111+
Assert.Contains(validationError, castedResult.ValidationErrors);
112+
}
113+
114+
[Fact]
115+
public void ExplicitCast_FromNotFoundResult_ShouldWork()
116+
{
117+
// Arrange
118+
var originalResult = Result.NotFound("User not found");
119+
120+
// Act
121+
var castedResult = (Result<User>)originalResult;
122+
123+
// Assert
124+
Assert.NotNull(castedResult);
125+
Assert.Equal(ResultStatus.NotFound, castedResult.Status);
126+
Assert.False(castedResult.IsSuccess);
127+
Assert.Null(castedResult.Value); // Should be default(User) which is null
128+
Assert.Single(castedResult.Errors);
129+
Assert.Contains("User not found", castedResult.Errors);
130+
}
131+
132+
[Fact]
133+
public void ExplicitCast_ToValueType_ShouldWork()
134+
{
135+
// Arrange
136+
var originalResult = Result.Conflict("Value is locked");
137+
138+
// Act - Cast to a value type
139+
var castedResult = (Result<int>)originalResult;
140+
141+
// Assert
142+
Assert.NotNull(castedResult);
143+
Assert.Equal(ResultStatus.Conflict, castedResult.Status);
144+
Assert.False(castedResult.IsSuccess);
145+
Assert.Equal(0, castedResult.Value); // Should be default(int) which is 0
146+
Assert.Equal(typeof(int), castedResult.ValueType);
147+
Assert.Single(castedResult.Errors);
148+
Assert.Contains("Value is locked", castedResult.Errors);
149+
}
150+
151+
[Fact]
152+
public void ImplicitConversion_ShouldAlsoWork()
153+
{
154+
// Arrange
155+
var originalResult = Result.Success("Test successful");
156+
157+
// Act - Implicit conversion should also work
158+
Result<User> castedResult = originalResult;
159+
160+
// Assert
161+
Assert.NotNull(castedResult);
162+
Assert.Equal(ResultStatus.Ok, castedResult.Status);
163+
Assert.True(castedResult.IsSuccess);
164+
Assert.Null(castedResult.Value); // Should be default(User) which is null
165+
Assert.Equal("Test successful", castedResult.SuccessMessage);
166+
}
167+
}

0 commit comments

Comments
 (0)